From 694e2f3f4d6116dbf0ecb7d4b5f572ad0229cec4 Mon Sep 17 00:00:00 2001 From: Robin Krom Date: Mon, 20 May 2019 23:32:03 +0200 Subject: [PATCH] Made image file format support extensible by providing a new interface IImageFormatReader and supplying some implementations. --- NuGet.Config | 1 + .../EditorFactory.cs | 6 +- src/Greenshot.Addons/AddonsModule.cs | 12 ++ .../Core/GreenshotFormatReader.cs | 30 ++++ src/Greenshot.Addons/Core/ImageOutput.cs | 12 +- src/Greenshot.Gfx/BitmapHelper.cs | 137 +----------------- .../Formats/GenericGdiFormatReader.cs | 34 +++++ .../Formats/IImageFormatReader.cs | 23 +++ src/Greenshot.Gfx/Formats/IcoFormatReader.cs | 109 ++++++++++++++ src/Greenshot.Gfx/Formats/SvgFormatReader.cs | 33 +++++ src/Greenshot.Gfx/GfxModule.cs | 75 ++++++++++ 11 files changed, 323 insertions(+), 149 deletions(-) create mode 100644 src/Greenshot.Addons/Core/GreenshotFormatReader.cs create mode 100644 src/Greenshot.Gfx/Formats/GenericGdiFormatReader.cs create mode 100644 src/Greenshot.Gfx/Formats/IImageFormatReader.cs create mode 100644 src/Greenshot.Gfx/Formats/IcoFormatReader.cs create mode 100644 src/Greenshot.Gfx/Formats/SvgFormatReader.cs create mode 100644 src/Greenshot.Gfx/GfxModule.cs diff --git a/NuGet.Config b/NuGet.Config index 28aaeb53d..3eef61ce6 100644 --- a/NuGet.Config +++ b/NuGet.Config @@ -13,5 +13,6 @@ + \ No newline at end of file diff --git a/src/Greenshot.Addon.LegacyEditor/EditorFactory.cs b/src/Greenshot.Addon.LegacyEditor/EditorFactory.cs index 0b5670aa9..79b6afc03 100644 --- a/src/Greenshot.Addon.LegacyEditor/EditorFactory.cs +++ b/src/Greenshot.Addon.LegacyEditor/EditorFactory.cs @@ -21,7 +21,6 @@ using System; using System.Collections.Generic; using System.Linq; using Greenshot.Addon.LegacyEditor.Forms; -using Greenshot.Addons.Core; using Greenshot.Addons.Interfaces; using Greenshot.Addons.Interfaces.Forms; @@ -38,13 +37,10 @@ namespace Greenshot.Addon.LegacyEditor public EditorFactory( IEditorConfiguration editorConfiguration, - Func imageEditorFactory, - Func surfaceExportFactory) + Func imageEditorFactory) { _editorConfiguration = editorConfiguration; _imageEditorFactory = imageEditorFactory; - // Factory for surface objects - ImageOutput.SurfaceFactory = surfaceExportFactory; } /// diff --git a/src/Greenshot.Addons/AddonsModule.cs b/src/Greenshot.Addons/AddonsModule.cs index d0249db4f..f6d88b465 100644 --- a/src/Greenshot.Addons/AddonsModule.cs +++ b/src/Greenshot.Addons/AddonsModule.cs @@ -28,6 +28,8 @@ using Greenshot.Addons.Controls; using Greenshot.Addons.Core; using Greenshot.Addons.Resources; using Greenshot.Addons.ViewModels; +using Greenshot.Gfx; +using Greenshot.Gfx.Formats; namespace Greenshot.Addons { @@ -96,6 +98,16 @@ namespace Greenshot.Addons .SingleInstance() .AutoActivate(); + builder + .RegisterType() + .As() + .SingleInstance() + .AutoActivate() + .OnActivated(args => + { + BitmapHelper.StreamConverters["greenshot"] = args.Instance; + }); + base.Load(builder); } } diff --git a/src/Greenshot.Addons/Core/GreenshotFormatReader.cs b/src/Greenshot.Addons/Core/GreenshotFormatReader.cs new file mode 100644 index 000000000..988e066d3 --- /dev/null +++ b/src/Greenshot.Addons/Core/GreenshotFormatReader.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.IO; +using Greenshot.Addons.Interfaces; +using Greenshot.Gfx; +using Greenshot.Gfx.Formats; + +namespace Greenshot.Addons.Core +{ + public class GreenshotFormatReader : IImageFormatReader + { + private readonly Func _surfaceFactory; + + public GreenshotFormatReader(Func surfaceFactory) + { + _surfaceFactory = surfaceFactory; + ImageOutput.SurfaceFactory = surfaceFactory; + } + + public IEnumerable SupportedFormats { get; } = new[] { "greenshot" }; + + public IBitmapWithNativeSupport Read(Stream stream, string extension = null) + { + // TODO: Create surface from stream + var surface = _surfaceFactory(); + surface.LoadElementsFromStream(stream); + return surface.GetBitmapForExport(); + } + } +} diff --git a/src/Greenshot.Addons/Core/ImageOutput.cs b/src/Greenshot.Addons/Core/ImageOutput.cs index 67763651b..8dbe78d3a 100644 --- a/src/Greenshot.Addons/Core/ImageOutput.cs +++ b/src/Greenshot.Addons/Core/ImageOutput.cs @@ -62,16 +62,6 @@ namespace Greenshot.Addons.Core private static readonly int PROPERTY_TAG_SOFTWARE_USED = 0x0131; private static readonly Cache TmpFileCache = new Cache(10 * 60 * 60, RemoveExpiredTmpFile); - static ImageOutput() - { - BitmapHelper.StreamConverters["greenshot"] = (stream, s) => - { - // TODO: Create surface from stream - var surface = SurfaceFactory(); - surface.LoadElementsFromStream(stream); - return surface.GetBitmapForExport(); - }; - } /// /// This is a factory method to create a surface, set from the Greenshot main project @@ -390,7 +380,7 @@ namespace Greenshot.Addons.Core // We create a copy of the bitmap, so everything else can be disposed surfaceFileStream.Position = 0; - var fileImage = BitmapHelper.FromStreamReader(surfaceFileStream, ".greenshot"); + var fileImage = BitmapHelper.FromStream(surfaceFileStream, ".greenshot"); // Start at -14 read "GreenshotXX.YY" (XX=Major, YY=Minor) const int markerSize = 14; diff --git a/src/Greenshot.Gfx/BitmapHelper.cs b/src/Greenshot.Gfx/BitmapHelper.cs index 0ec362e6f..079b7e42f 100644 --- a/src/Greenshot.Gfx/BitmapHelper.cs +++ b/src/Greenshot.Gfx/BitmapHelper.cs @@ -32,6 +32,7 @@ using Dapplo.Windows.Dpi; using Greenshot.Gfx.Effects; using Greenshot.Gfx.Extensions; using Greenshot.Gfx.FastBitmap; +using Greenshot.Gfx.Formats; using Greenshot.Gfx.Structs; namespace Greenshot.Gfx @@ -44,91 +45,10 @@ namespace Greenshot.Gfx private const int ExifOrientationId = 0x0112; private static readonly LogSource Log = new LogSource(); - /// - /// A function which usage image.fromstream - /// - public static readonly Func FromStreamReader = (stream, s) => - { - using (var tmpImage = Image.FromStream(stream, true, true)) - { - if (!(tmpImage is Bitmap bitmap)) - { - return null; - } - Log.Debug().WriteLine("Loaded bitmap with Size {0}x{1} and PixelFormat {2}", bitmap.Width, bitmap.Height, bitmap.PixelFormat); - return bitmap.CloneBitmap(PixelFormat.Format32bppArgb); - } - }; - - static BitmapHelper() - { - // Fallback - StreamConverters[""] = FromStreamReader; - - StreamConverters["gif"] = FromStreamReader; - StreamConverters["bmp"] = FromStreamReader; - StreamConverters["jpg"] = FromStreamReader; - StreamConverters["jpeg"] = FromStreamReader; - StreamConverters["png"] = FromStreamReader; - StreamConverters["wmf"] = FromStreamReader; - StreamConverters["svg"] = (stream, s) => - { - stream.Position = 0; - try - { - return SvgBitmap.FromStream(stream); - } - catch (Exception ex) - { - Log.Error().WriteLine(ex, "Can't load SVG"); - } - return null; - }; - - StreamConverters["ico"] = (stream, extension) => - { - // Icon logic, try to get the Vista icon, else the biggest possible - try - { - using (var tmpBitmap = stream.ExtractVistaIcon()) - { - if (tmpBitmap != null) - { - return tmpBitmap.CloneBitmap(PixelFormat.Format32bppArgb); - } - } - } - catch (Exception vistaIconException) - { - Log.Warn().WriteLine(vistaIconException, "Can't read icon"); - } - try - { - // No vista icon, try normal icon - stream.Position = 0; - // We create a copy of the bitmap, so everything else can be disposed - using (var tmpIcon = new Icon(stream, new Size(1024, 1024))) - { - using (var tmpImage = tmpIcon.ToBitmap()) - { - return tmpImage.CloneBitmap(PixelFormat.Format32bppArgb); - } - } - } - catch (Exception iconException) - { - Log.Warn().WriteLine(iconException, "Can't read icon"); - } - - stream.Position = 0; - return FromStreamReader(stream, extension); - }; - } - /// /// This defines all available bitmap reader functions, registered to an "extension" is called with a stream to a IBitmap. /// - public static IDictionary> StreamConverters { get; } = new Dictionary>(StringComparer.OrdinalIgnoreCase); + public static IDictionary StreamConverters { get; } = new Dictionary(StringComparer.OrdinalIgnoreCase); /// /// Make sure the image is orientated correctly @@ -403,48 +323,6 @@ namespace Greenshot.Gfx return fileBitmap; } - /// - /// Based on: http://www.codeproject.com/KB/cs/IconExtractor.aspx - /// And a hint from: http://www.codeproject.com/KB/cs/IconLib.aspx - /// - /// Stream with the icon information - /// Bitmap with the Vista Icon (256x256) - private static IBitmapWithNativeSupport ExtractVistaIcon(this Stream iconStream) - { - const int sizeIconDir = 6; - const int sizeIconDirEntry = 16; - IBitmapWithNativeSupport bmpPngExtracted = null; - try - { - var srcBuf = new byte[iconStream.Length]; - iconStream.Read(srcBuf, 0, (int) iconStream.Length); - int iCount = BitConverter.ToInt16(srcBuf, 4); - for (var iIndex = 0; iIndex < iCount; iIndex++) - { - int iWidth = srcBuf[sizeIconDir + sizeIconDirEntry * iIndex]; - int iHeight = srcBuf[sizeIconDir + sizeIconDirEntry * iIndex + 1]; - if (iWidth != 0 || iHeight != 0) - { - continue; - } - var iImageSize = BitConverter.ToInt32(srcBuf, sizeIconDir + sizeIconDirEntry * iIndex + 8); - var iImageOffset = BitConverter.ToInt32(srcBuf, sizeIconDir + sizeIconDirEntry * iIndex + 12); - using (var destStream = new MemoryStream()) - { - destStream.Write(srcBuf, iImageOffset, iImageSize); - destStream.Seek(0, SeekOrigin.Begin); - bmpPngExtracted = BitmapWrapper.FromBitmap(new Bitmap(destStream)); // This is PNG! :) - } - break; - } - } - catch - { - return null; - } - return bmpPngExtracted; - } - /// /// Apply the effect to the bitmap /// @@ -893,17 +771,10 @@ namespace Greenshot.Gfx } IBitmapWithNativeSupport returnBitmap = null; - if (StreamConverters.TryGetValue(extension ?? "", out var converter)) + if (StreamConverters.TryGetValue(extension ?? "", out var reader)) { - returnBitmap = converter(stream, extension); + returnBitmap = reader.Read(stream, extension); } - if (returnBitmap != null || converter == FromStreamReader) - { - return returnBitmap; - } - // Fallback to default converter - stream.Position = 0; - returnBitmap = FromStreamReader(stream, extension); return returnBitmap; } diff --git a/src/Greenshot.Gfx/Formats/GenericGdiFormatReader.cs b/src/Greenshot.Gfx/Formats/GenericGdiFormatReader.cs new file mode 100644 index 000000000..b6e0cf84c --- /dev/null +++ b/src/Greenshot.Gfx/Formats/GenericGdiFormatReader.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; +using Dapplo.Log; + +namespace Greenshot.Gfx.Formats +{ + /// + /// This implements a IImageFormatReader with the help of Gdi + /// + public class GenericGdiFormatReader : IImageFormatReader + { + private static readonly LogSource Log = new LogSource(); + + /// + public IEnumerable SupportedFormats { get; } = new []{ "","gif", "bmp", "jpg", "jpeg", "png", "wmf" }; + + /// + public IBitmapWithNativeSupport Read(Stream stream, string extension = null) + { + using (var tmpImage = Image.FromStream(stream, true, true)) + { + if (!(tmpImage is Bitmap bitmap)) + { + return null; + } + Log.Debug().WriteLine("Loaded bitmap with Size {0}x{1} and PixelFormat {2}", bitmap.Width, bitmap.Height, bitmap.PixelFormat); + return bitmap.CloneBitmap(PixelFormat.Format32bppArgb); + } + } + } +} diff --git a/src/Greenshot.Gfx/Formats/IImageFormatReader.cs b/src/Greenshot.Gfx/Formats/IImageFormatReader.cs new file mode 100644 index 000000000..61cf48193 --- /dev/null +++ b/src/Greenshot.Gfx/Formats/IImageFormatReader.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; +using System.IO; + +namespace Greenshot.Gfx.Formats +{ + /// + /// Implement this interface to add reading a format to Greenshot + /// + public interface IImageFormatReader + { + /// + /// This returns all the formats the reader supports + /// + IEnumerable SupportedFormats { get; } + /// + /// This reads a IBitmapWithNativeSupport from a stream + /// + /// Stream + /// string + /// IBitmapWithNativeSupport + IBitmapWithNativeSupport Read(Stream stream, string extension = null); + } +} diff --git a/src/Greenshot.Gfx/Formats/IcoFormatReader.cs b/src/Greenshot.Gfx/Formats/IcoFormatReader.cs new file mode 100644 index 000000000..e927e738a --- /dev/null +++ b/src/Greenshot.Gfx/Formats/IcoFormatReader.cs @@ -0,0 +1,109 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; +using Dapplo.Log; + +namespace Greenshot.Gfx.Formats +{ + /// + /// This implements a IImageFormatReader which reads .ico files + /// + public class IcoFormatReader : IImageFormatReader + { + private static readonly LogSource Log = new LogSource(); + private readonly GenericGdiFormatReader _genericGdiFormatReader; + + public IcoFormatReader(GenericGdiFormatReader genericGdiFormatReader) + { + _genericGdiFormatReader = genericGdiFormatReader; + } + /// + public IEnumerable SupportedFormats { get; } = new []{ "ico" }; + + /// + public IBitmapWithNativeSupport Read(Stream stream, string extension = null) + { + // Icon logic, try to get the Vista icon, else the biggest possible + try + { + using (var tmpBitmap = ExtractVistaIcon(stream)) + { + if (tmpBitmap != null) + { + return tmpBitmap.CloneBitmap(PixelFormat.Format32bppArgb); + } + } + } + catch (Exception vistaIconException) + { + Log.Warn().WriteLine(vistaIconException, "Can't read icon"); + } + try + { + // No vista icon, try normal icon + stream.Position = 0; + // We create a copy of the bitmap, so everything else can be disposed + using (var tmpIcon = new Icon(stream, new Size(1024, 1024))) + { + using (var tmpImage = tmpIcon.ToBitmap()) + { + return tmpImage.CloneBitmap(PixelFormat.Format32bppArgb); + } + } + } + catch (Exception iconException) + { + Log.Warn().WriteLine(iconException, "Can't read icon"); + } + + stream.Position = 0; + return _genericGdiFormatReader.Read(stream, extension); + } + + + /// + /// Based on: http://www.codeproject.com/KB/cs/IconExtractor.aspx + /// And a hint from: http://www.codeproject.com/KB/cs/IconLib.aspx + /// + /// Stream with the icon information + /// Bitmap with the Vista Icon (256x256) + private static IBitmapWithNativeSupport ExtractVistaIcon(Stream iconStream) + { + const int sizeIconDir = 6; + const int sizeIconDirEntry = 16; + IBitmapWithNativeSupport bmpPngExtracted = null; + try + { + var srcBuf = new byte[iconStream.Length]; + iconStream.Read(srcBuf, 0, (int)iconStream.Length); + int iCount = BitConverter.ToInt16(srcBuf, 4); + for (var iIndex = 0; iIndex < iCount; iIndex++) + { + int iWidth = srcBuf[sizeIconDir + sizeIconDirEntry * iIndex]; + int iHeight = srcBuf[sizeIconDir + sizeIconDirEntry * iIndex + 1]; + if (iWidth != 0 || iHeight != 0) + { + continue; + } + var iImageSize = BitConverter.ToInt32(srcBuf, sizeIconDir + sizeIconDirEntry * iIndex + 8); + var iImageOffset = BitConverter.ToInt32(srcBuf, sizeIconDir + sizeIconDirEntry * iIndex + 12); + using (var destStream = new MemoryStream()) + { + destStream.Write(srcBuf, iImageOffset, iImageSize); + destStream.Seek(0, SeekOrigin.Begin); + bmpPngExtracted = BitmapWrapper.FromBitmap(new Bitmap(destStream)); // This is PNG! :) + } + break; + } + } + catch + { + return null; + } + return bmpPngExtracted; + } + + } +} diff --git a/src/Greenshot.Gfx/Formats/SvgFormatReader.cs b/src/Greenshot.Gfx/Formats/SvgFormatReader.cs new file mode 100644 index 000000000..f2f83874b --- /dev/null +++ b/src/Greenshot.Gfx/Formats/SvgFormatReader.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.IO; +using Dapplo.Log; + +namespace Greenshot.Gfx.Formats +{ + /// + /// This implements a IImageFormatReader which reads svg + /// + public class SvgFormatReader : IImageFormatReader + { + private static readonly LogSource Log = new LogSource(); + + /// + public IEnumerable SupportedFormats { get; } = new []{ "svg" }; + + /// + public IBitmapWithNativeSupport Read(Stream stream, string extension = null) + { + stream.Position = 0; + try + { + return SvgBitmap.FromStream(stream); + } + catch (Exception ex) + { + Log.Error().WriteLine(ex, "Can't load SVG"); + } + return null; + } + } +} diff --git a/src/Greenshot.Gfx/GfxModule.cs b/src/Greenshot.Gfx/GfxModule.cs new file mode 100644 index 000000000..6db89f62d --- /dev/null +++ b/src/Greenshot.Gfx/GfxModule.cs @@ -0,0 +1,75 @@ +// Greenshot - a free and open source screenshot tool +// Copyright (C) 2007-2019 Thomas Braun, Jens Klingen, Robin Krom +// +// For more information see: http://getgreenshot.org/ +// The Greenshot project is hosted on GitHub https://github.com/greenshot/greenshot +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 1 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +using Autofac; +using Dapplo.Addons; +using Greenshot.Gfx; +using Greenshot.Gfx.Formats; + +namespace Greenshot.Gfx +{ + /// + public class GfxModule : AddonModule + { + private void Register(IImageFormatReader reader) + { + foreach(var extension in reader.SupportedFormats) + { + BitmapHelper.StreamConverters[extension] = reader; + } + } + + /// + protected override void Load(ContainerBuilder builder) + { + builder + .RegisterType() + .As() + .AsSelf() + .SingleInstance() + .AutoActivate() + .OnActivated(args => + { + Register(args.Instance); + }); + + builder + .RegisterType() + .As() + .SingleInstance() + .AutoActivate() + .OnActivated(args => + { + Register(args.Instance); + }); + + builder + .RegisterType() + .As() + .SingleInstance() + .AutoActivate() + .OnActivated(args => + { + Register(args.Instance); + }); + + base.Load(builder); + } + } +}