@@ -784,105 +784,98 @@ public static ClipboardContents FromClipboard() {
784784 // Images
785785 // ======
786786
787- var images = new Dict < string , Image > ( ) ;
788- var extensions = new HashSet < string > ( new [ ] {
789- ImageContent . EXTENSIONS ,
790- TransparentImageContent . EXTENSIONS ,
791- AnimatedImageContent . EXTENSIONS ,
792- VectorImageContent . EXTENSIONS ,
793- } . SelectMany ( i => i ) ) ;
794-
795- // Native clipboard bitmap image
796- if ( Clipboard . GetData ( DataFormats . Dib ) is Image dib ) // device independent bitmap
797- images . Add ( "bmp" , dib ) ;
798- else if ( Clipboard . GetData ( DataFormats . Bitmap ) is Image bmp ) // device specific bitmap
799- images . Add ( "bmp" , bmp ) ;
800- else if ( Clipboard . GetImage ( ) is Image converted ) // anything converted to device specific bitmap
801- images . Add ( "bmp" , converted ) ;
802-
803- // Native clipboard tiff image
804- if ( Clipboard . GetData ( DataFormats . Tiff ) is Image tif )
805- images . Add ( "tif" , tif ) ;
787+ // Collect images (preferred first)
788+ var images = new Dict < string , ImageLikeContent > ( ) ;
806789
807- // Native clipboard metafile (emf or wmf)
808- if ( ReadClipboardMetafile ( ) is Metafile emf )
809- images . Add ( "emf" , emf ) ;
790+ // Generic image from file
791+ if ( Clipboard . ContainsFileDropList ( ) && Clipboard . GetFileDropList ( ) is { Count : 1 } files ) {
792+ var ext = BaseContent . NormalizeExtension ( Path . GetExtension ( files [ 0 ] ) . Trim ( '.' ) ) ;
793+ if ( ImageContentFromBytes ( ext , File . ReadAllBytes ( files [ 0 ] ) ) is { } imageContent )
794+ images . Add ( ext , imageContent ) ;
795+ }
810796
811797 // Mime and file extension formats
812- var formats = extensions . SelectMany ( ext => MimeForImageExtension ( ext ) . Concat ( new [ ] { ext } ) ) ;
813- foreach ( var format in formats ) { // case insensitive
814- if ( Clipboard . ContainsData ( format ) && Clipboard . GetData ( format ) is MemoryStream stream )
815- try {
816- if ( Image . FromStream ( stream ) is Image img )
817- images . Add ( format , img ) ;
818- } catch ( Exception e ) {
819- Console . WriteLine ( e ) ;
820- }
798+ foreach ( var types in IMAGE_MIME_TYPES ) {
799+ var ext = BaseContent . NormalizeExtension ( types . Key ) ;
800+ if ( images . ContainsKey ( ext ) )
801+ continue ;
802+ foreach ( var format in types . Value . Concat ( [ ext ] ) ) {
803+ if ( ! Clipboard . ContainsData ( format ) || Clipboard . GetData ( format ) is not MemoryStream stream )
804+ continue ;
805+ if ( ImageContentFromBytes ( ext , stream . ToArray ( ) ) is { } imageContent )
806+ images . Add ( ext , imageContent ) ;
807+ }
821808 }
822809
823- // Generic image from encoded data uri
824- if ( Clipboard . ContainsText ( ) && ImageFromDataUri ( Clipboard . GetText ( ) ) is Image uriImage )
825- images . Add ( uriImage . RawFormat . ToString ( ) . ToLower ( ) , uriImage ) ;
810+ // Image from encoded data uri
811+ if ( Clipboard . ContainsText ( ) ) {
812+ var ( mime_ext , bytes ) = BytesFromDataUri ( Clipboard . GetText ( ) ) ;
813+ if ( bytes != null && ! images . ContainsKey ( mime_ext ) )
814+ if ( ImageContentFromBytes ( mime_ext , bytes ) is { } imageContent )
815+ images . Add ( mime_ext , imageContent ) ;
816+ }
826817
827- // Generic image from file
828- if ( Clipboard . ContainsFileDropList ( ) && Clipboard . GetFileDropList ( ) is StringCollection files && files . Count == 1 ) {
829- try {
830- images . Add ( Path . GetExtension ( files [ 0 ] ) . Trim ( '.' ) . ToLower ( ) , Image . FromFile ( files [ 0 ] ) ) ;
831- } catch { /* format not supported */ }
818+ // Native clipboard bitmap image
819+ if ( ! images . ContainsKey ( "bmp" ) ) {
820+ if ( Clipboard . GetData ( DataFormats . Dib ) is Image dib ) // device independent bitmap
821+ images . Add ( "bmp" , new ImageContent ( dib ) ) ;
822+ else if ( Clipboard . GetData ( DataFormats . Bitmap ) is Image bmp ) // device specific bitmap
823+ images . Add ( "bmp" , new ImageContent ( bmp ) ) ;
824+ else if ( Clipboard . GetImage ( ) is Image converted ) // anything converted to device specific bitmap
825+ images . Add ( "bmp" , new ImageContent ( converted ) ) ;
832826 }
833827
828+ // Native clipboard tiff image
829+ if ( ! images . ContainsKey ( "tif" ) && Clipboard . GetData ( DataFormats . Tiff ) is Image tif )
830+ images . Add ( "tif" , new ImageContent ( tif ) ) ;
831+
832+ // Native clipboard metafile (emf or wmf)
833+ if ( ! images . ContainsKey ( "emf" ) && ReadClipboardMetafile ( ) is Metafile emf )
834+ images . Add ( "emf" , new VectorImageContent ( emf ) ) ;
835+
836+
834837 // Since images can have features (transparency, animations) which are not supported by all file format,
835838 // we handel images with such features separately (in order of priority):
836- var remainingExtensions = new HashSet < string > ( extensions ) ;
839+ var remainingExtensions = new HashSet < string > ( new [ ] {
840+ ImageContent . EXTENSIONS ,
841+ TransparentImageContent . EXTENSIONS ,
842+ AnimatedImageContent . EXTENSIONS ,
843+ VectorImageContent . EXTENSIONS ,
844+ } . SelectMany ( i => i ) ) ; ;
837845
838846 // 0. Vector image (if any)
839- foreach ( var ( ext , img ) in images . Items ) {
840- if ( img is Metafile mf ) {
841- container . Contents . Add ( new VectorImageContent ( mf ) ) ;
842- remainingExtensions . ExceptWith ( VectorImageContent . EXTENSIONS ) ;
843- break ;
844- }
847+ if ( images . Values . FirstOrDefault ( content => content is VectorImageContent ) is { } vectorContent ) {
848+ container . Contents . Add ( vectorContent ) ;
849+ remainingExtensions . ExceptWith ( vectorContent . Extensions ) ;
845850 }
846851
847852 // 1. Animated image (if any)
848- if ( images . GetAll ( AnimatedImageContent . EXTENSIONS ) . FirstOrDefault ( ) is Image animated ) {
849- container . Contents . Add ( new AnimatedImageContent ( animated ) ) ;
850- remainingExtensions . ExceptWith ( AnimatedImageContent . EXTENSIONS ) ;
851- } else {
852- // no direct match, search for anything that looks like it's animated
853- foreach ( var ( ext , img ) in images . Items ) {
854- try {
855- if ( img . GetFrameCount ( FrameDimension . Time ) > 1 ) {
856- container . Contents . Add ( new AnimatedImageContent ( img ) ) ;
857- remainingExtensions . ExceptWith ( AnimatedImageContent . EXTENSIONS ) ;
858- break ;
859- }
860- } catch { /* format does not support frames */
861- }
862- }
853+ if ( images . Values . FirstOrDefault ( content => content is AnimatedImageContent ) is { } animatedContent ) {
854+ container . Contents . Add ( animatedContent ) ;
855+ remainingExtensions . ExceptWith ( animatedContent . Extensions ) ;
863856 }
864857
865858 // 2. Transparent image (if any)
866- if ( images . GetAll ( TransparentImageContent . EXTENSIONS ) . FirstOrDefault ( ) is Image transparent ) {
867- container . Contents . Add ( new TransparentImageContent ( transparent ) ) ;
868- remainingExtensions . ExceptWith ( TransparentImageContent . EXTENSIONS ) ;
859+ if ( images . Values . FirstOrDefault ( content => content is TransparentImageContent ) is { } transparentContent ) {
860+ container . Contents . Add ( transparentContent ) ;
861+ remainingExtensions . ExceptWith ( transparentContent . Extensions ) ;
869862 } else {
870- // no direct match, search for anything that looks like it's transparent
871- foreach ( var ( ext , img ) in images . Items ) {
872- if ( ( ( ImageFlags ) img . Flags ) . HasFlag ( ImageFlags . HasAlpha ) ) {
873- container . Contents . Add ( new TransparentImageContent ( img ) ) ;
863+ // no direct match, search for anything that looks like it's transparent (e.g. transparent animated or vector image)
864+ foreach ( var cnt in images . Values ) {
865+ if ( cnt is ImageContent imgCnt && ( ( ImageFlags ) imgCnt . Image . Flags ) . HasFlag ( ImageFlags . HasAlpha ) ) {
866+ container . Contents . Add ( new TransparentImageContent ( imgCnt . Image ) ) ;
874867 remainingExtensions . ExceptWith ( TransparentImageContent . EXTENSIONS ) ;
875868 break ;
876869 }
877870 }
878871 }
879872
880873 // 3. Remaining image with no special features (if any)
881- if ( images . GetAll ( remainingExtensions ) . FirstOrDefault ( ) is Image image ) {
882- container . Contents . Add ( new ImageContent ( image ) ) ;
883- } else if ( images . Values . FirstOrDefault ( ) is Image anything ) {
874+ if ( images . GetAll ( remainingExtensions ) . FirstOrDefault ( ) is ImageContent imgContent ) {
875+ container . Contents . Add ( new ImageContent ( imgContent . Image ) ) ; // as generic ImageContent
876+ } else if ( images . Values . FirstOrDefault ( ) is ImageContent anything ) {
884877 // no unique match, so accept anything (even if already used as special format)
885- container . Contents . Add ( new ImageContent ( anything ) ) ;
878+ container . Contents . Add ( new ImageContent ( anything . Image ) ) ; // as generic ImageContent
886879 }
887880
888881
@@ -940,17 +933,17 @@ public static ClipboardContents FromClipboard() {
940933 return container ;
941934 }
942935
943- private static IEnumerable < string > MimeForImageExtension ( string extension ) {
944- switch ( BaseContent . NormalizeExtension ( extension ) ) {
945- case "jpg" : return new [ ] { "image/jpeg" } ;
946- case "bmp" : return new [ ] { "image/bmp" , "image/x-bmp" , "image/x-ms-bmp" } ;
947- case "tif" : return new [ ] { "image/tiff " , "image/tiff-fx " } ;
948- case "ico" : return new [ ] { "image/x-ico" , "image/vnd.microsoft.icon" } ;
949- case "emf" : return new [ ] { "image/emf" , "image/x-emf" } ;
950- case "wmf" : return new [ ] { "image/wmf " , "image/x-wmf " } ;
951- default : return new [ ] { "image/" + extension . ToLower ( ) } ;
952- }
953- }
936+ private static Dict < string , string [ ] > IMAGE_MIME_TYPES = new ( ) {
937+ { "bmp" , new [ ] { "image/bmp" , "image/x-bmp" , "image/x-ms-bmp" } } ,
938+ { "emf" , new [ ] { "image/emf" , "image/x-emf" } } ,
939+ { "gif" , new [ ] { "image/gif" } } ,
940+ { "ico" , new [ ] { "image/x-ico " , "image/vnd.microsoft.icon " } } ,
941+ { "jpg" , new [ ] { "image/jpeg" } } ,
942+ { "png" , new [ ] { "image/png" } } ,
943+ { "tif" , new [ ] { "image/tiff " , "image/tiff-fx " } } ,
944+ { "webp" , new [ ] { "image/webp" } } ,
945+ { "wmf" , new [ ] { "image/wmf" , "image/x-wmf" } } ,
946+ } ;
954947
955948 private static string ReadClipboardHtml ( ) {
956949 if ( Clipboard . ContainsData ( DataFormats . Html ) ) {
@@ -1073,23 +1066,24 @@ private static bool LooksLikeBinaryFile(string filepath) {
10731066 /// </summary>
10741067 /// <param name="uri">The data URI, typically starting with data:image/</param>
10751068 /// <returns>The image or null if the uri is not an image or conversion failed</returns>
1076- private static Image ImageFromDataUri ( string uri ) {
1069+ private static ( string , byte [ ] ) BytesFromDataUri ( string uri ) {
10771070 try {
1078- var match = Regex . Match ( uri , @"^data:image/\w+(?<base64>;base64)?,(?<data>.+)$" ) ;
1071+ var match = Regex . Match ( uri , @"^data:image/(?<ext>; \w+) (?<base64>;base64)?,(?<data>.+)$" ) ;
10791072 if ( match . Success ) {
1073+ var ext = BaseContent . NormalizeExtension ( match . Groups [ "ext" ] . Value ) ;
1074+ byte [ ] bytes ;
10801075 if ( match . Groups [ "base64" ] . Success ) {
10811076 // Base64 encoded
1082- var bytes = Convert . FromBase64String ( match . Groups [ "data" ] . Value ) ;
1083- return Image . FromStream ( new MemoryStream ( bytes ) ) ;
1077+ bytes = Convert . FromBase64String ( match . Groups [ "data" ] . Value ) ;
10841078 } else {
10851079 // URL encoded
1086- var bytes = Encoding . Default . GetBytes ( match . Groups [ "data" ] . Value ) ;
1080+ bytes = Encoding . Default . GetBytes ( match . Groups [ "data" ] . Value ) ;
10871081 bytes = WebUtility . UrlDecodeToBytes ( bytes , 0 , bytes . Length ) ;
1088- return Image . FromStream ( new MemoryStream ( bytes ) ) ;
10891082 }
1083+ return ( ext , bytes ) ;
10901084 }
10911085 } catch { /* data uri malformed or not supported */ }
1092- return null ;
1086+ return ( null , null ) ;
10931087 }
10941088
10951089 private static ImageLikeContent ImageContentFromBytes ( string ext , byte [ ] bytes ) {
0 commit comments