Skip to content

Commit 86f3753

Browse files
committed
Unify image content creation from file/filedroplist/mime/uri
1 parent 1de12cf commit 86f3753

1 file changed

Lines changed: 107 additions & 98 deletions

File tree

PasteIntoFile/ClipboardContents.cs

Lines changed: 107 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)