@@ -784,105 +784,98 @@ public static ClipboardContents FromClipboard() {
784
784
// Images
785
785
// ======
786
786
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 > ( ) ;
806
789
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
+ }
810
796
811
797
// 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
+ }
821
808
}
822
809
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
+ }
826
817
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 ) ) ;
832
826
}
833
827
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
+
834
837
// Since images can have features (transparency, animations) which are not supported by all file format,
835
838
// 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 ) ) ; ;
837
845
838
846
// 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 ) ;
845
850
}
846
851
847
852
// 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 ) ;
863
856
}
864
857
865
858
// 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 ) ;
869
862
} 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 ) ) ;
874
867
remainingExtensions . ExceptWith ( TransparentImageContent . EXTENSIONS ) ;
875
868
break ;
876
869
}
877
870
}
878
871
}
879
872
880
873
// 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 ) {
884
877
// 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
886
879
}
887
880
888
881
@@ -940,17 +933,17 @@ public static ClipboardContents FromClipboard() {
940
933
return container ;
941
934
}
942
935
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
+ } ;
954
947
955
948
private static string ReadClipboardHtml ( ) {
956
949
if ( Clipboard . ContainsData ( DataFormats . Html ) ) {
@@ -1073,23 +1066,24 @@ private static bool LooksLikeBinaryFile(string filepath) {
1073
1066
/// </summary>
1074
1067
/// <param name="uri">The data URI, typically starting with data:image/</param>
1075
1068
/// <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 ) {
1077
1070
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>.+)$" ) ;
1079
1072
if ( match . Success ) {
1073
+ var ext = BaseContent . NormalizeExtension ( match . Groups [ "ext" ] . Value ) ;
1074
+ byte [ ] bytes ;
1080
1075
if ( match . Groups [ "base64" ] . Success ) {
1081
1076
// 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 ) ;
1084
1078
} else {
1085
1079
// URL encoded
1086
- var bytes = Encoding . Default . GetBytes ( match . Groups [ "data" ] . Value ) ;
1080
+ bytes = Encoding . Default . GetBytes ( match . Groups [ "data" ] . Value ) ;
1087
1081
bytes = WebUtility . UrlDecodeToBytes ( bytes , 0 , bytes . Length ) ;
1088
- return Image . FromStream ( new MemoryStream ( bytes ) ) ;
1089
1082
}
1083
+ return ( ext , bytes ) ;
1090
1084
}
1091
1085
} catch { /* data uri malformed or not supported */ }
1092
- return null ;
1086
+ return ( null , null ) ;
1093
1087
}
1094
1088
1095
1089
private static ImageLikeContent ImageContentFromBytes ( string ext , byte [ ] bytes ) {
0 commit comments