Skip to content

Commit 5448267

Browse files
committed
Unify image content creation from file/filedroplist/mime/uri
1 parent 72d9b76 commit 5448267

File tree

1 file changed

+84
-90
lines changed

1 file changed

+84
-90
lines changed

PasteIntoFile/ClipboardContents.cs

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

Comments
 (0)