@@ -810,105 +810,100 @@ public static ClipboardContents FromClipboard() {
810810 // Images
811811 // ======
812812
813- var images = new Dict < string , Image > ( ) ;
814- var extensions = new HashSet < string > ( new [ ] {
815- ImageContent . EXTENSIONS ,
816- TransparentImageContent . EXTENSIONS ,
817- AnimatedImageContent . EXTENSIONS ,
818- VectorImageContent . EXTENSIONS ,
819- } . SelectMany ( i => i ) ) ;
820-
821- // Native clipboard bitmap image
822- if ( Clipboard . GetData ( DataFormats . Dib ) is Image dib ) // device independent bitmap
823- images . Add ( "bmp" , dib ) ;
824- else if ( Clipboard . GetData ( DataFormats . Bitmap ) is Image bmp ) // device specific bitmap
825- images . Add ( "bmp" , bmp ) ;
826- else if ( Clipboard . GetImage ( ) is Image converted ) // anything converted to device specific bitmap
827- images . Add ( "bmp" , converted ) ;
813+ // Collect images (preferred first)
814+ var images = new Dict < string , ImageLikeContent > ( ) ;
828815
829- // Native clipboard tiff image
830- if ( Clipboard . GetData ( DataFormats . Tiff ) is Image tif )
831- images . Add ( "tif" , tif ) ;
832-
833- // Native clipboard metafile (emf or wmf)
834- if ( ReadClipboardMetafile ( ) is Metafile emf )
835- images . Add ( "emf" , emf ) ;
816+ // Generic image from file
817+ if ( Clipboard . ContainsFileDropList ( ) && Clipboard . GetFileDropList ( ) is StringCollection files && files . Count == 1 ) {
818+ var ext = BaseContent . NormalizeExtension ( Path . GetExtension ( files [ 0 ] ) . Trim ( '.' ) ) ;
819+ if ( ImageContentFromBytes ( ext , File . ReadAllBytes ( files [ 0 ] ) ) is ImageLikeContent imageContent )
820+ images . Add ( ext , imageContent ) ;
821+ }
836822
837823 // Mime and file extension formats
838- var formats = extensions . SelectMany ( ext => MimeForImageExtension ( ext ) . Concat ( new [ ] { ext } ) ) ;
839- foreach ( var format in formats ) { // case insensitive
840- if ( Clipboard . ContainsData ( format ) && Clipboard . GetData ( format ) is MemoryStream stream )
841- try {
842- if ( Image . FromStream ( stream ) is Image img )
843- images . Add ( format , img ) ;
844- } catch ( Exception e ) {
845- Console . WriteLine ( e ) ;
824+ foreach ( var types in IMAGE_MIME_TYPES ) {
825+ var ext = BaseContent . NormalizeExtension ( types . Key ) ;
826+ if ( images . ContainsKey ( ext ) )
827+ continue ;
828+ foreach ( var format in types . Value . Concat ( new [ ] { ext } ) ) {
829+ if ( Clipboard . ContainsData ( format )
830+ && Clipboard . GetData ( format ) is MemoryStream stream
831+ && ImageContentFromBytes ( ext , stream . ToArray ( ) ) is ImageLikeContent imageContent
832+ ) {
833+ images . Add ( ext , imageContent ) ;
846834 }
835+ }
847836 }
848837
849- // Generic image from encoded data uri
850- if ( Clipboard . ContainsText ( ) && ImageFromDataUri ( Clipboard . GetText ( ) ) is Image uriImage )
851- images . Add ( uriImage . RawFormat . ToString ( ) . ToLower ( ) , uriImage ) ;
838+ // Image from encoded data uri
839+ if ( Clipboard . ContainsText ( ) ) {
840+ var ( mime_ext , bytes ) = BytesFromDataUri ( Clipboard . GetText ( ) ) ;
841+ if ( bytes != null && ! images . ContainsKey ( mime_ext ) )
842+ if ( ImageContentFromBytes ( mime_ext , bytes ) is ImageLikeContent imageContent )
843+ images . Add ( mime_ext , imageContent ) ;
844+ }
852845
853- // Generic image from file
854- if ( Clipboard . ContainsFileDropList ( ) && Clipboard . GetFileDropList ( ) is StringCollection files && files . Count == 1 ) {
855- try {
856- images . Add ( Path . GetExtension ( files [ 0 ] ) . Trim ( '.' ) . ToLower ( ) , Image . FromFile ( files [ 0 ] ) ) ;
857- } catch { /* format not supported */ }
846+ // Native clipboard bitmap image
847+ if ( ! images . ContainsKey ( "bmp" ) ) {
848+ if ( Clipboard . GetData ( DataFormats . Dib ) is Image dib ) // device independent bitmap
849+ images . Add ( "bmp" , new ImageContent ( dib ) ) ;
850+ else if ( Clipboard . GetData ( DataFormats . Bitmap ) is Image bmp ) // device specific bitmap
851+ images . Add ( "bmp" , new ImageContent ( bmp ) ) ;
852+ else if ( Clipboard . GetImage ( ) is Image converted ) // anything converted to device specific bitmap
853+ images . Add ( "bmp" , new ImageContent ( converted ) ) ;
858854 }
859855
856+ // Native clipboard tiff image
857+ if ( ! images . ContainsKey ( "tif" ) && Clipboard . GetData ( DataFormats . Tiff ) is Image tif )
858+ images . Add ( "tif" , new ImageContent ( tif ) ) ;
859+
860+ // Native clipboard metafile (emf or wmf)
861+ if ( ! images . ContainsKey ( "emf" ) && ReadClipboardMetafile ( ) is Metafile emf )
862+ images . Add ( "emf" , new VectorImageContent ( emf ) ) ;
863+
864+
860865 // Since images can have features (transparency, animations) which are not supported by all file format,
861866 // we handel images with such features separately (in order of priority):
862- var remainingExtensions = new HashSet < string > ( extensions ) ;
867+ var remainingExtensions = new HashSet < string > ( new [ ] {
868+ ImageContent . EXTENSIONS ,
869+ TransparentImageContent . EXTENSIONS ,
870+ AnimatedImageContent . EXTENSIONS ,
871+ VectorImageContent . EXTENSIONS ,
872+ } . SelectMany ( i => i ) ) ; ;
863873
864874 // 0. Vector image (if any)
865- foreach ( var ( ext , img ) in images . Items ) {
866- if ( img is Metafile mf ) {
867- container . Contents . Add ( new VectorImageContent ( mf ) ) ;
868- remainingExtensions . ExceptWith ( VectorImageContent . EXTENSIONS ) ;
869- break ;
870- }
875+ if ( images . Values . FirstOrDefault ( content => content is VectorImageContent ) is ImageLikeContent vectorContent ) {
876+ container . Contents . Add ( vectorContent ) ;
877+ remainingExtensions . ExceptWith ( vectorContent . Extensions ) ;
871878 }
872879
873880 // 1. Animated image (if any)
874- if ( images . GetAll ( AnimatedImageContent . EXTENSIONS ) . FirstOrDefault ( ) is Image animated ) {
875- container . Contents . Add ( new AnimatedImageContent ( animated ) ) ;
876- remainingExtensions . ExceptWith ( AnimatedImageContent . EXTENSIONS ) ;
877- } else {
878- // no direct match, search for anything that looks like it's animated
879- foreach ( var ( ext , img ) in images . Items ) {
880- try {
881- if ( img . GetFrameCount ( FrameDimension . Time ) > 1 ) {
882- container . Contents . Add ( new AnimatedImageContent ( img ) ) ;
883- remainingExtensions . ExceptWith ( AnimatedImageContent . EXTENSIONS ) ;
884- break ;
885- }
886- } catch { /* format does not support frames */
887- }
888- }
881+ if ( images . Values . FirstOrDefault ( content => content is AnimatedImageContent ) is ImageLikeContent animatedContent ) {
882+ container . Contents . Add ( animatedContent ) ;
883+ remainingExtensions . ExceptWith ( animatedContent . Extensions ) ;
889884 }
890885
891886 // 2. Transparent image (if any)
892- if ( images . GetAll ( TransparentImageContent . EXTENSIONS ) . FirstOrDefault ( ) is Image transparent ) {
893- container . Contents . Add ( new TransparentImageContent ( transparent ) ) ;
894- remainingExtensions . ExceptWith ( TransparentImageContent . EXTENSIONS ) ;
887+ if ( images . Values . FirstOrDefault ( content => content is TransparentImageContent ) is ImageLikeContent transparentContent ) {
888+ container . Contents . Add ( transparentContent ) ;
889+ remainingExtensions . ExceptWith ( transparentContent . Extensions ) ;
895890 } else {
896- // no direct match, search for anything that looks like it's transparent
897- foreach ( var ( ext , img ) in images . Items ) {
898- if ( ( ( ImageFlags ) img . Flags ) . HasFlag ( ImageFlags . HasAlpha ) ) {
899- container . Contents . Add ( new TransparentImageContent ( img ) ) ;
891+ // no direct match, search for anything that looks like it's transparent (e.g. transparent animated or vector image)
892+ foreach ( var cnt in images . Values ) {
893+ if ( cnt is ImageContent imgCnt && ( ( ImageFlags ) imgCnt . Image . Flags ) . HasFlag ( ImageFlags . HasAlpha ) ) {
894+ container . Contents . Add ( new TransparentImageContent ( imgCnt . Image ) ) ;
900895 remainingExtensions . ExceptWith ( TransparentImageContent . EXTENSIONS ) ;
901896 break ;
902897 }
903898 }
904899 }
905900
906901 // 3. Remaining image with no special features (if any)
907- if ( images . GetAll ( remainingExtensions ) . FirstOrDefault ( ) is Image image ) {
908- container . Contents . Add ( new ImageContent ( image ) ) ;
909- } else if ( images . Values . FirstOrDefault ( ) is Image anything ) {
902+ if ( images . GetAll ( remainingExtensions ) . FirstOrDefault ( ) is ImageContent imgContent ) {
903+ container . Contents . Add ( new ImageContent ( imgContent . Image ) ) ; // as generic ImageContent
904+ } else if ( images . Values . FirstOrDefault ( ) is ImageContent anything ) {
910905 // no unique match, so accept anything (even if already used as special format)
911- container . Contents . Add ( new ImageContent ( anything ) ) ;
906+ container . Contents . Add ( new ImageContent ( anything . Image ) ) ; // as generic ImageContent
912907 }
913908
914909
@@ -955,17 +950,17 @@ public static ClipboardContents FromClipboard() {
955950 return container ;
956951 }
957952
958- private static IEnumerable < string > MimeForImageExtension ( string extension ) {
959- switch ( BaseContent . NormalizeExtension ( extension ) ) {
960- case "jpg" : return new [ ] { "image/jpeg" } ;
961- case "bmp" : return new [ ] { "image/bmp" , "image/x-bmp" , "image/x-ms-bmp" } ;
962- case "tif" : return new [ ] { "image/tiff " , "image/tiff-fx " } ;
963- case "ico" : return new [ ] { "image/x-ico" , "image/vnd.microsoft.icon" } ;
964- case "emf" : return new [ ] { "image/emf" , "image/x-emf" } ;
965- case "wmf" : return new [ ] { "image/wmf " , "image/x-wmf " } ;
966- default : return new [ ] { "image/" + extension . ToLower ( ) } ;
967- }
968- }
953+ private static Dict < string , string [ ] > IMAGE_MIME_TYPES = new Dict < string , string [ ] > {
954+ { "bmp" , new [ ] { "image/bmp" , "image/x-bmp" , "image/x-ms-bmp" } } ,
955+ { "emf" , new [ ] { "image/emf" , "image/x-emf" } } ,
956+ { "gif" , new [ ] { "image/gif" } } ,
957+ { "ico" , new [ ] { "image/x-ico " , "image/vnd.microsoft.icon " } } ,
958+ { "jpg" , new [ ] { "image/jpeg" } } ,
959+ { "png" , new [ ] { "image/png" } } ,
960+ { "tif" , new [ ] { "image/tiff " , "image/tiff-fx " } } ,
961+ { "webp" , new [ ] { "image/webp" } } ,
962+ { "wmf" , new [ ] { "image/wmf" , "image/x-wmf" } } ,
963+ } ;
969964
970965 private static string ReadClipboardHtml ( ) {
971966 if ( Clipboard . ContainsData ( DataFormats . Html ) ) {
@@ -1046,16 +1041,9 @@ public static ClipboardContents FromFile(string path) {
10461041 // add the file itself
10471042 container . Contents . Add ( new FilesContent ( new StringCollection { path } ) ) ;
10481043
1049- // if it's an image (try&catch instead of maintaining a list of supported extensions)
1050- try {
1051- var img = Image . FromFile ( path ) ;
1052- img = RotateFlipImageFromExif ( img ) ;
1053- if ( img is Metafile mf ) {
1054- container . Contents . Add ( new VectorImageContent ( mf ) ) ;
1055- } else {
1056- container . Contents . Add ( new ImageContent ( img ) ) ;
1057- }
1058- } catch { /* it's not */ }
1044+ // if it's an image
1045+ if ( ImageContentFromBytes ( ext , File . ReadAllBytes ( path ) ) is BaseContent content )
1046+ container . Contents . Add ( content ) ;
10591047
10601048
10611049 // if it's text like (check for absence of zero byte)
@@ -1118,22 +1106,43 @@ private static bool LooksLikeBinaryFile(string filepath) {
11181106 /// </summary>
11191107 /// <param name="uri">The data URI, typically starting with data:image/</param>
11201108 /// <returns>The image or null if the uri is not an image or conversion failed</returns>
1121- private static Image ImageFromDataUri ( string uri ) {
1109+ private static ( string , byte [ ] ) BytesFromDataUri ( string uri ) {
11221110 try {
1123- var match = Regex . Match ( uri , @"^data:image/\w+(?<base64>;base64)?,(?<data>.+)$" ) ;
1111+ var match = Regex . Match ( uri , @"^data:image/(?<ext>; \w+) (?<base64>;base64)?,(?<data>.+)$" ) ;
11241112 if ( match . Success ) {
1113+ var ext = BaseContent . NormalizeExtension ( match . Groups [ "ext" ] . Value ) ;
1114+ byte [ ] bytes ;
11251115 if ( match . Groups [ "base64" ] . Success ) {
11261116 // Base64 encoded
1127- var bytes = Convert . FromBase64String ( match . Groups [ "data" ] . Value ) ;
1128- return Image . FromStream ( new MemoryStream ( bytes ) ) ;
1117+ bytes = Convert . FromBase64String ( match . Groups [ "data" ] . Value ) ;
11291118 } else {
11301119 // URL encoded
1131- var bytes = Encoding . Default . GetBytes ( match . Groups [ "data" ] . Value ) ;
1120+ bytes = Encoding . Default . GetBytes ( match . Groups [ "data" ] . Value ) ;
11321121 bytes = WebUtility . UrlDecodeToBytes ( bytes , 0 , bytes . Length ) ;
1133- return Image . FromStream ( new MemoryStream ( bytes ) ) ;
11341122 }
1123+ return ( ext , bytes ) ;
11351124 }
11361125 } catch { /* data uri malformed or not supported */ }
1126+ return ( null , null ) ;
1127+ }
1128+
1129+
1130+ private static ImageLikeContent ImageContentFromBytes ( string ext , byte [ ] bytes ) {
1131+ try {
1132+ var img = Image . FromStream ( new MemoryStream ( bytes ) ) ;
1133+ img = RotateFlipImageFromExif ( img ) ;
1134+ if ( img is Metafile mf )
1135+ return new VectorImageContent ( mf ) ;
1136+ try {
1137+ if ( img . GetFrameCount ( FrameDimension . Time ) > 1 )
1138+ return new AnimatedImageContent ( img ) ;
1139+ } catch { /* not an animated image */ }
1140+ if ( ( ( ImageFlags ) img . Flags ) . HasFlag ( ImageFlags . HasAlpha ) )
1141+ return new TransparentImageContent ( img ) ;
1142+ return new ImageContent ( img ) ;
1143+ } catch ( Exception e ) { /* not an image? */
1144+ Console . WriteLine ( e ) ;
1145+ }
11371146 return null ;
11381147 }
11391148
0 commit comments