From 29976716982ec4e280acd69c5b411274b79f210f Mon Sep 17 00:00:00 2001 From: Robin Krom Date: Wed, 9 Feb 2022 23:37:38 +0100 Subject: [PATCH] Fixing drag and drop of files, the container was not added to the surface. Also fixed opening things from the clipboard. --- src/Greenshot.Base/Core/ClipboardHelper.cs | 199 +++++++++++++++++- .../FileFormatHandlerRegistry.cs | 23 ++ src/Greenshot.Base/Core/NetworkHelper.cs | 57 ++++- src/Greenshot.Editor/Drawing/Surface.cs | 48 ++--- .../SvgFileFormatHandler.cs | 8 +- 5 files changed, 295 insertions(+), 40 deletions(-) diff --git a/src/Greenshot.Base/Core/ClipboardHelper.cs b/src/Greenshot.Base/Core/ClipboardHelper.cs index 0ab70e5b2..a87ac9071 100644 --- a/src/Greenshot.Base/Core/ClipboardHelper.cs +++ b/src/Greenshot.Base/Core/ClipboardHelper.cs @@ -494,7 +494,7 @@ EndSelection:<<<<<<<4 { IDataObject clipboardData = GetDataObject(); // Return the first image - foreach (Image clipboardImage in GetImages(clipboardData)) + foreach (var clipboardImage in GetImages(clipboardData)) { return clipboardImage; } @@ -507,11 +507,89 @@ EndSelection:<<<<<<<4 /// Returned images must be disposed by the calling code! /// /// - /// IEnumerable of Image - public static IEnumerable GetImages(IDataObject dataObject) + /// IEnumerable of Bitmap + public static IEnumerable GetImages(IDataObject dataObject) { // Get single image, this takes the "best" match - IDrawableContainer singleImage = GetImage(dataObject); + Bitmap singleImage = GetImage(dataObject); + if (singleImage != null) + { + Log.InfoFormat($"Got {singleImage.GetType()} from clipboard with size {singleImage.Size}"); + yield return singleImage; + yield break; + } + + var supportedExtensions = FileFormatHandlerRegistry.ExtensionsFor(FileFormatHandlerActions.LoadDrawableFromStream).ToList(); + + foreach (var fileData in IterateClipboardContent(dataObject)) + { + var extension = Path.GetExtension(fileData.filename)?.ToLowerInvariant(); + if (!supportedExtensions.Contains(extension)) + { + continue; + } + + Bitmap bitmap = null; + + try + { + if (!FileFormatHandlerRegistry.TryLoadFromStream(fileData.stream, extension, out bitmap)) + { + continue; + } + + } + catch (Exception ex) + { + Log.Error("Couldn't read file contents", ex); + continue; + } + finally + { + fileData.stream?.Dispose(); + } + // If we get here, there is an image + yield return bitmap; + } + + // check if files are supplied + foreach (string imageFile in GetImageFilenames(dataObject)) + { + var extension = Path.GetExtension(imageFile)?.ToLowerInvariant(); + if (!supportedExtensions.Contains(extension)) + { + continue; + } + + Bitmap bitmap = null; + using FileStream fileStream = new FileStream(imageFile, FileMode.Open, FileAccess.Read, FileShare.Read); + try + { + if (!FileFormatHandlerRegistry.TryLoadFromStream(fileStream, extension, out bitmap)) + { + continue; + } + } + catch (Exception ex) + { + Log.Error("Couldn't read file contents", ex); + continue; + } + // If we get here, there is an image + yield return bitmap; + } + } + + /// + /// Get all images (multiple if file names are available) from the dataObject + /// Returned images must be disposed by the calling code! + /// + /// + /// IEnumerable of IDrawableContainer + public static IEnumerable GetDrawables(IDataObject dataObject) + { + // Get single image, this takes the "best" match + IDrawableContainer singleImage = GetDrawable(dataObject); if (singleImage != null) { Log.InfoFormat($"Got {singleImage.GetType()} from clipboard with size {singleImage.Size}"); @@ -585,11 +663,11 @@ EndSelection:<<<<<<<4 /// /// /// Image or null - private static IDrawableContainer GetImage(IDataObject dataObject) + private static Bitmap GetImage(IDataObject dataObject) { if (dataObject == null) return null; - IDrawableContainer returnImage = null; + Bitmap returnImage = null; IList formats = GetFormats(dataObject); string[] retrieveFormats; @@ -635,6 +713,113 @@ EndSelection:<<<<<<<4 return null; } + /// + /// Get an IDrawableContainer from the IDataObject, don't check for FileDrop + /// + /// + /// Image or null + private static IDrawableContainer GetDrawable(IDataObject dataObject) + { + if (dataObject == null) return null; + + IDrawableContainer returnImage = null; + IList formats = GetFormats(dataObject); + string[] retrieveFormats; + + // Found a weird bug, where PNG's from Outlook 2010 are clipped + // So I build some special logic to get the best format: + if (formats != null && formats.Contains(FORMAT_PNG_OFFICEART) && formats.Contains(DataFormats.Dib)) + { + // Outlook ?? + Log.Info("Most likely the current clipboard contents come from Outlook, as this has a problem with PNG and others we place the DIB format to the front..."); + retrieveFormats = new[] + { + DataFormats.Dib, FORMAT_BITMAP, FORMAT_FILECONTENTS, FORMAT_PNG_OFFICEART, FORMAT_PNG, FORMAT_JFIF_OFFICEART, FORMAT_JPG, FORMAT_JPEG, FORMAT_JFIF, + DataFormats.Tiff, FORMAT_GIF, FORMAT_HTML + }; + } + else + { + retrieveFormats = new[] + { + FORMAT_PNG_OFFICEART, FORMAT_PNG, FORMAT_17, FORMAT_JFIF_OFFICEART, FORMAT_JPG, FORMAT_JPEG, FORMAT_JFIF, DataFormats.Tiff, DataFormats.Dib, FORMAT_BITMAP, + FORMAT_FILECONTENTS, FORMAT_GIF, FORMAT_HTML + }; + } + + foreach (string currentFormat in retrieveFormats) + { + if (formats != null && formats.Contains(currentFormat)) + { + Log.InfoFormat("Found {0}, trying to retrieve.", currentFormat); + returnImage = GetDrawableForFormat(currentFormat, dataObject); + } + else + { + Log.DebugFormat("Couldn't find format {0}.", currentFormat); + } + + if (returnImage != null) + { + return returnImage; + } + } + + return null; + } + + /// + /// Helper method to try to get an Bitmap in the specified format from the dataObject + /// the DIB reader should solve some issues + /// It also supports Format17/DibV5, by using the following information: https://stackoverflow.com/a/14335591 + /// + /// string with the format + /// IDataObject + /// Bitmap or null + private static Bitmap GetImageForFormat(string format, IDataObject dataObject) + { + Bitmap bitmap = null; + + if (format == FORMAT_HTML) + { + var textObject = ContentAsString(dataObject, FORMAT_HTML, Encoding.UTF8); + if (textObject != null) + { + var doc = new HtmlDocument(); + doc.LoadHtml(textObject); + var imgNodes = doc.DocumentNode.SelectNodes("//img"); + if (imgNodes != null) + { + foreach (var imgNode in imgNodes) + { + var srcAttribute = imgNode.Attributes["src"]; + var imageUrl = srcAttribute.Value; + Log.Debug(imageUrl); + bitmap = NetworkHelper.DownloadImage(imageUrl); + if (bitmap != null) + { + return bitmap; + } + } + } + } + } + + object clipboardObject = GetFromDataObject(dataObject, format); + var imageStream = clipboardObject as MemoryStream; + if (!IsValidStream(imageStream)) + { + return clipboardObject as Bitmap; + } + + // From here, imageStream is a valid stream + if (!FileFormatHandlerRegistry.TryLoadFromStream(imageStream, format, out bitmap)) + { + return bitmap; + } + return null; + } + /// /// Helper method to try to get an IDrawableContainer in the specified format from the dataObject /// the DIB reader should solve some issues @@ -643,7 +828,7 @@ EndSelection:<<<<<<<4 /// string with the format /// IDataObject /// IDrawableContainer or null - private static IDrawableContainer GetImageForFormat(string format, IDataObject dataObject) + private static IDrawableContainer GetDrawableForFormat(string format, IDataObject dataObject) { IDrawableContainer drawableContainer = null; diff --git a/src/Greenshot.Base/Core/FileFormatHandlers/FileFormatHandlerRegistry.cs b/src/Greenshot.Base/Core/FileFormatHandlers/FileFormatHandlerRegistry.cs index 0dded0b55..47603fa2a 100644 --- a/src/Greenshot.Base/Core/FileFormatHandlers/FileFormatHandlerRegistry.cs +++ b/src/Greenshot.Base/Core/FileFormatHandlers/FileFormatHandlerRegistry.cs @@ -84,5 +84,28 @@ namespace Greenshot.Base.Core.FileFormatHandlers return fileFormatHandler.TryLoadDrawableFromStream(stream, extension, out drawableContainer, parentSurface); } + + /// + /// Try to load a Bitmap from the stream + /// + /// Stream + /// string + /// Bitmap out + /// bool true if it was successful + public static bool TryLoadFromStream(Stream stream, string extension, out Bitmap bitmap) + { + var fileFormatHandler = FileFormatHandlers + .Where(ffh => ffh.Supports(FileFormatHandlerActions.LoadFromStream, extension)) + .OrderBy(ffh => ffh.PriorityFor(FileFormatHandlerActions.LoadFromStream, extension)) + .FirstOrDefault(); + + if (fileFormatHandler == null) + { + bitmap = null; + return false; + } + + return fileFormatHandler.TryLoadFromStream(stream, extension, out bitmap); + } } } diff --git a/src/Greenshot.Base/Core/NetworkHelper.cs b/src/Greenshot.Base/Core/NetworkHelper.cs index 23427ce0d..02797ed8d 100644 --- a/src/Greenshot.Base/Core/NetworkHelper.cs +++ b/src/Greenshot.Base/Core/NetworkHelper.cs @@ -24,7 +24,6 @@ using System.Collections.Generic; using System.Drawing; using System.Globalization; using System.IO; -using System.Linq; using System.Net; using System.Text; using System.Text.RegularExpressions; @@ -145,6 +144,62 @@ namespace Greenshot.Base.Core return null; } + /// + /// Download the uri to create a Bitmap + /// + /// Of an image + /// Bitmap + public static Bitmap DownloadImage(string url) + { + var extensions = string.Join("|", FileFormatHandlerRegistry.ExtensionsFor(FileFormatHandlerActions.LoadFromStream)); + + var imageUrlRegex = new Regex($@"(http|https)://.*(?{extensions})"); + var match = imageUrlRegex.Match(url); + try + { + using var memoryStream = GetAsMemoryStream(url); + try + { + if (FileFormatHandlerRegistry.TryLoadFromStream(memoryStream, match.Success ? match.Groups["extension"]?.Value : null, out var bitmap)) + { + return bitmap; + } + } + catch (Exception) + { + // If we arrive here, the image loading didn't work, try to see if the response has a http(s) URL to an image and just take this instead. + string content; + using (var streamReader = new StreamReader(memoryStream, Encoding.UTF8, true)) + { + content = streamReader.ReadLine(); + } + + if (string.IsNullOrEmpty(content)) + { + throw; + } + + match = imageUrlRegex.Match(content); + if (!match.Success) + { + throw; + } + + using var memoryStream2 = GetAsMemoryStream(match.Value); + if (FileFormatHandlerRegistry.TryLoadFromStream(memoryStream2, match.Success ? match.Groups["extension"]?.Value : null, out var bitmap)) + { + return bitmap; + } + } + } + catch (Exception e) + { + Log.Error("Problem downloading the image from: " + url, e); + } + + return null; + } + /// /// Helper method to create a web request with a lot of default settings /// diff --git a/src/Greenshot.Editor/Drawing/Surface.cs b/src/Greenshot.Editor/Drawing/Surface.cs index d39276992..049748cd1 100644 --- a/src/Greenshot.Editor/Drawing/Surface.cs +++ b/src/Greenshot.Editor/Drawing/Surface.cs @@ -935,7 +935,7 @@ namespace Greenshot.Editor.Drawing } } - foreach (var drawableContainer in ClipboardHelper.GetImages(e.Data)) + foreach (var drawableContainer in ClipboardHelper.GetDrawables(e.Data)) { drawableContainer.Left = mouse.X; drawableContainer.Top = mouse.Y; @@ -987,13 +987,11 @@ namespace Greenshot.Editor.Drawing { //create a blank bitmap the same size as original Bitmap newBitmap = ImageHelper.CreateEmptyLike(Image, Color.Empty); - if (newBitmap != null) - { - // Make undoable - MakeUndoable(new SurfaceBackgroundChangeMemento(this, null), false); - SetImage(newBitmap, false); - Invalidate(); - } + if (newBitmap == null) return; + // Make undoable + MakeUndoable(new SurfaceBackgroundChangeMemento(this, null), false); + SetImage(newBitmap, false); + Invalidate(); } /// @@ -2057,12 +2055,13 @@ namespace Greenshot.Editor.Drawing { Point pasteLocation = GetPasteLocation(0.1f, 0.1f); - foreach (var drawableContainer in ClipboardHelper.GetImages(clipboard)) + foreach (var drawableContainer in ClipboardHelper.GetDrawables(clipboard)) { if (drawableContainer == null) continue; DeselectAllElements(); drawableContainer.Left = pasteLocation.X; drawableContainer.Top = pasteLocation.Y; + AddElement(drawableContainer); SelectElement(drawableContainer); pasteLocation.X += 10; pasteLocation.Y += 10; @@ -2206,24 +2205,23 @@ namespace Greenshot.Editor.Drawing /// false to skip event generation public void SelectElement(IDrawableContainer container, bool invalidate = true, bool generateEvents = true) { - if (!selectedElements.Contains(container)) - { - selectedElements.Add(container); - container.Selected = true; - FieldAggregator.BindElement(container); - if (generateEvents && _movingElementChanged != null) - { - SurfaceElementEventArgs eventArgs = new SurfaceElementEventArgs - { - Elements = selectedElements - }; - _movingElementChanged(this, eventArgs); - } + if (selectedElements.Contains(container)) return; - if (invalidate) + selectedElements.Add(container); + container.Selected = true; + FieldAggregator.BindElement(container); + if (generateEvents && _movingElementChanged != null) + { + SurfaceElementEventArgs eventArgs = new SurfaceElementEventArgs { - container.Invalidate(); - } + Elements = selectedElements + }; + _movingElementChanged(this, eventArgs); + } + + if (invalidate) + { + container.Invalidate(); } } diff --git a/src/Greenshot.Editor/FileFormatHandlers/SvgFileFormatHandler.cs b/src/Greenshot.Editor/FileFormatHandlers/SvgFileFormatHandler.cs index 17c3eb542..3cc04d681 100644 --- a/src/Greenshot.Editor/FileFormatHandlers/SvgFileFormatHandler.cs +++ b/src/Greenshot.Editor/FileFormatHandlers/SvgFileFormatHandler.cs @@ -22,10 +22,8 @@ using System; using System.Collections.Generic; using System.Drawing; -using System.Drawing.Imaging; using System.IO; using System.Linq; -using Greenshot.Base.Core; using Greenshot.Base.Core.FileFormatHandlers; using Greenshot.Base.Interfaces; using Greenshot.Base.Interfaces.Drawing; @@ -73,20 +71,16 @@ namespace Greenshot.Editor.FileFormatHandlers public bool TryLoadFromStream(Stream stream, string extension, out Bitmap bitmap) { var svgDocument = SvgDocument.Open(stream); - int width = (int)svgDocument.ViewBox.Width; - int height = (int)svgDocument.ViewBox.Height; try { - bitmap = ImageHelper.CreateEmpty(width, height, PixelFormat.Format32bppArgb, Color.Transparent); - svgDocument.Draw(bitmap); + bitmap = svgDocument.Draw(); return true; } catch (Exception ex) { Log.Error("Can't load SVG", ex); } - bitmap = null; return false; }