Made image file format support extensible by providing a new interface IImageFormatReader and supplying some implementations.

This commit is contained in:
Robin Krom 2019-05-20 23:32:03 +02:00
commit 694e2f3f4d
11 changed files with 323 additions and 149 deletions

View file

@ -13,5 +13,6 @@
<add key="aspnet-entityframeworkcore" value="https://dotnetfeed.blob.core.windows.net/aspnet-entityframeworkcore/index.json" />
<add key="aspnet-extensions" value="https://dotnetfeed.blob.core.windows.net/aspnet-extensions/index.json" />
<add key="bdn-nightly" value="https://ci.appveyor.com/nuget/benchmarkdotnet" />
<add key="dotnet-core-myget" value="https://dotnet.myget.org/F/dotnet-core/api/v3/index.json" />
</packageSources>
</configuration>

View file

@ -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<ImageEditorForm> imageEditorFactory,
Func<ISurface> surfaceExportFactory)
Func<ImageEditorForm> imageEditorFactory)
{
_editorConfiguration = editorConfiguration;
_imageEditorFactory = imageEditorFactory;
// Factory for surface objects
ImageOutput.SurfaceFactory = surfaceExportFactory;
}
/// <summary>

View file

@ -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<GreenshotFormatReader>()
.As<IImageFormatReader>()
.SingleInstance()
.AutoActivate()
.OnActivated(args =>
{
BitmapHelper.StreamConverters["greenshot"] = args.Instance;
});
base.Load(builder);
}
}

View file

@ -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<ISurface> _surfaceFactory;
public GreenshotFormatReader(Func<ISurface> surfaceFactory)
{
_surfaceFactory = surfaceFactory;
ImageOutput.SurfaceFactory = surfaceFactory;
}
public IEnumerable<string> 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();
}
}
}

View file

@ -62,16 +62,6 @@ namespace Greenshot.Addons.Core
private static readonly int PROPERTY_TAG_SOFTWARE_USED = 0x0131;
private static readonly Cache<string, string> TmpFileCache = new Cache<string, string>(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();
};
}
/// <summary>
/// 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;

View file

@ -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();
/// <summary>
/// A function which usage image.fromstream
/// </summary>
public static readonly Func<Stream, string, IBitmapWithNativeSupport> 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);
};
}
/// <summary>
/// This defines all available bitmap reader functions, registered to an "extension" is called with a stream to a IBitmap.
/// </summary>
public static IDictionary<string, Func<Stream, string, IBitmapWithNativeSupport>> StreamConverters { get; } = new Dictionary<string, Func<Stream, string, IBitmapWithNativeSupport>>(StringComparer.OrdinalIgnoreCase);
public static IDictionary<string, IImageFormatReader> StreamConverters { get; } = new Dictionary<string, IImageFormatReader>(StringComparer.OrdinalIgnoreCase);
/// <summary>
/// Make sure the image is orientated correctly
@ -403,48 +323,6 @@ namespace Greenshot.Gfx
return fileBitmap;
}
/// <summary>
/// Based on: http://www.codeproject.com/KB/cs/IconExtractor.aspx
/// And a hint from: http://www.codeproject.com/KB/cs/IconLib.aspx
/// </summary>
/// <param name="iconStream">Stream with the icon information</param>
/// <returns>Bitmap with the Vista Icon (256x256)</returns>
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;
}
/// <summary>
/// Apply the effect to the bitmap
/// </summary>
@ -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;
}

View file

@ -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
{
/// <summary>
/// This implements a IImageFormatReader with the help of Gdi
/// </summary>
public class GenericGdiFormatReader : IImageFormatReader
{
private static readonly LogSource Log = new LogSource();
/// <inheritdoc/>
public IEnumerable<string> SupportedFormats { get; } = new []{ "","gif", "bmp", "jpg", "jpeg", "png", "wmf" };
/// <inheritdoc/>
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);
}
}
}
}

View file

@ -0,0 +1,23 @@
using System.Collections.Generic;
using System.IO;
namespace Greenshot.Gfx.Formats
{
/// <summary>
/// Implement this interface to add reading a format to Greenshot
/// </summary>
public interface IImageFormatReader
{
/// <summary>
/// This returns all the formats the reader supports
/// </summary>
IEnumerable<string> SupportedFormats { get; }
/// <summary>
/// This reads a IBitmapWithNativeSupport from a stream
/// </summary>
/// <param name="stream">Stream</param>
/// <param name="extension">string</param>
/// <returns>IBitmapWithNativeSupport</returns>
IBitmapWithNativeSupport Read(Stream stream, string extension = null);
}
}

View file

@ -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
{
/// <summary>
/// This implements a IImageFormatReader which reads .ico files
/// </summary>
public class IcoFormatReader : IImageFormatReader
{
private static readonly LogSource Log = new LogSource();
private readonly GenericGdiFormatReader _genericGdiFormatReader;
public IcoFormatReader(GenericGdiFormatReader genericGdiFormatReader)
{
_genericGdiFormatReader = genericGdiFormatReader;
}
/// <inheritdoc/>
public IEnumerable<string> SupportedFormats { get; } = new []{ "ico" };
/// <inheritdoc/>
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);
}
/// <summary>
/// Based on: http://www.codeproject.com/KB/cs/IconExtractor.aspx
/// And a hint from: http://www.codeproject.com/KB/cs/IconLib.aspx
/// </summary>
/// <param name="iconStream">Stream with the icon information</param>
/// <returns>Bitmap with the Vista Icon (256x256)</returns>
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;
}
}
}

View file

@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
using System.IO;
using Dapplo.Log;
namespace Greenshot.Gfx.Formats
{
/// <summary>
/// This implements a IImageFormatReader which reads svg
/// </summary>
public class SvgFormatReader : IImageFormatReader
{
private static readonly LogSource Log = new LogSource();
/// <inheritdoc/>
public IEnumerable<string> SupportedFormats { get; } = new []{ "svg" };
/// <inheritdoc/>
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;
}
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
using Autofac;
using Dapplo.Addons;
using Greenshot.Gfx;
using Greenshot.Gfx.Formats;
namespace Greenshot.Gfx
{
/// <inheritdoc />
public class GfxModule : AddonModule
{
private void Register(IImageFormatReader reader)
{
foreach(var extension in reader.SupportedFormats)
{
BitmapHelper.StreamConverters[extension] = reader;
}
}
/// <inheritdoc/>
protected override void Load(ContainerBuilder builder)
{
builder
.RegisterType<GenericGdiFormatReader>()
.As<IImageFormatReader>()
.AsSelf()
.SingleInstance()
.AutoActivate()
.OnActivated(args =>
{
Register(args.Instance);
});
builder
.RegisterType<IcoFormatReader>()
.As<IImageFormatReader>()
.SingleInstance()
.AutoActivate()
.OnActivated(args =>
{
Register(args.Instance);
});
builder
.RegisterType<SvgFormatReader>()
.As<IImageFormatReader>()
.SingleInstance()
.AutoActivate()
.OnActivated(args =>
{
Register(args.Instance);
});
base.Load(builder);
}
}
}