From 78334dd86fb764309bce6bf9d8f8b36c25724b7c Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Wed, 22 Mar 2017 06:58:16 +0000 Subject: [PATCH] Add IImageDecoder overloads Adds overloads the the Image.Load(..) methods that take an IImageDecoder. --- src/ImageSharp/Image.Decode.cs | 40 +++--- src/ImageSharp/Image.FromBytes.cs | 72 +++++++++- src/ImageSharp/Image.FromFile.cs | 70 ++++++++- src/ImageSharp/Image.FromStream.cs | 108 +++++++++++--- .../ImageSharp.Tests/Image/ImageLoadTests.cs | 133 +++++++++++++++++- tests/ImageSharp.Tests/TestFileSystem.cs | 72 ++++++++++ 6 files changed, 449 insertions(+), 46 deletions(-) create mode 100644 tests/ImageSharp.Tests/TestFileSystem.cs diff --git a/src/ImageSharp/Image.Decode.cs b/src/ImageSharp/Image.Decode.cs index f31f2c0a55..c95b71b1e8 100644 --- a/src/ImageSharp/Image.Decode.cs +++ b/src/ImageSharp/Image.Decode.cs @@ -19,25 +19,12 @@ namespace ImageSharp /// public sealed partial class Image { - /// - /// Decodes the image stream to the current image. - /// - /// The pixel format. - /// The stream. - /// The options for the decoder. - /// the configuration. - /// The decoded image - /// - /// [true] if can successfull decode the image otherwise [false]. - /// - private static bool Decode(Stream stream, IDecoderOptions options, Configuration config, out Image img) - where TColor : struct, IPixel + private static IImageFormat DiscoverFormat(Stream stream, IDecoderOptions options, Configuration config) { - img = null; int maxHeaderSize = config.MaxHeaderSize; if (maxHeaderSize <= 0) { - return false; + return null; } IImageFormat format; @@ -54,14 +41,31 @@ namespace ImageSharp ArrayPool.Shared.Return(header); } + return format; + } + + /// + /// Decodes the image stream to the current image. + /// + /// The pixel format. + /// The stream. + /// The options for the decoder. + /// the configuration. + /// + /// The decoded image + /// + private static Image Decode(Stream stream, IDecoderOptions options, Configuration config) + where TColor : struct, IPixel + { + IImageFormat format = DiscoverFormat(stream, options, config); if (format == null) { - return false; + return null; } - img = format.Decoder.Decode(stream, options); + Image img = format.Decoder.Decode(stream, options); img.CurrentImageFormat = format; - return true; + return img; } } } diff --git a/src/ImageSharp/Image.FromBytes.cs b/src/ImageSharp/Image.FromBytes.cs index 3f1a3031c0..8e0ac04a5a 100644 --- a/src/ImageSharp/Image.FromBytes.cs +++ b/src/ImageSharp/Image.FromBytes.cs @@ -29,7 +29,7 @@ namespace ImageSharp /// The image public static Image Load(byte[] data) { - return Load(data, null, null); + return Load(data, null, (Configuration)null); } /// @@ -60,6 +60,20 @@ namespace ImageSharp return Load(data, null, config); } + /// + /// Loads the image from the given byte array. + /// + /// The byte array containing image data. + /// The decoder. + /// + /// Thrown if the stream is not readable nor seekable. + /// + /// The image + public static Image Load(byte[] data, IImageDecoder decoder) + { + return Load(data, decoder, null); + } + /// /// Loads the image from the given byte array. /// @@ -78,6 +92,24 @@ namespace ImageSharp } } + /// + /// Loads the image from the given byte array. + /// + /// The byte array containing image data. + /// The decoder. + /// The options for the decoder. + /// + /// Thrown if the stream is not readable nor seekable. + /// + /// The image + public static Image Load(byte[] data, IImageDecoder decoder, IDecoderOptions options) + { + using (MemoryStream ms = new MemoryStream(data)) + { + return Load(ms, decoder, options); + } + } + /// /// Loads the image from the given byte array. /// @@ -90,7 +122,7 @@ namespace ImageSharp public static Image Load(byte[] data) where TColor : struct, IPixel { - return Load(data, null, null); + return Load(data, null, (Configuration)null); } /// @@ -125,6 +157,22 @@ namespace ImageSharp return Load(data, null, config); } + /// + /// Loads the image from the given byte array. + /// + /// The pixel format. + /// The byte array containing image data. + /// The decoder. + /// + /// Thrown if the stream is not readable nor seekable. + /// + /// The image + public static Image Load(byte[] data, IImageDecoder decoder) + where TColor : struct, IPixel + { + return Load(data, decoder, null); + } + /// /// Loads the image from the given byte array. /// @@ -144,5 +192,25 @@ namespace ImageSharp return Load(ms, options, config); } } + + /// + /// Loads the image from the given byte array. + /// + /// The pixel format. + /// The byte array containing image data. + /// The decoder. + /// The options for the decoder. + /// + /// Thrown if the stream is not readable nor seekable. + /// + /// The image + public static Image Load(byte[] data, IImageDecoder decoder, IDecoderOptions options) + where TColor : struct, IPixel + { + using (MemoryStream ms = new MemoryStream(data)) + { + return Load(ms, decoder, options); + } + } } } diff --git a/src/ImageSharp/Image.FromFile.cs b/src/ImageSharp/Image.FromFile.cs index 9b8ba83ded..e9015eaa9d 100644 --- a/src/ImageSharp/Image.FromFile.cs +++ b/src/ImageSharp/Image.FromFile.cs @@ -30,7 +30,7 @@ namespace ImageSharp /// The image public static Image Load(string path) { - return Load(path, null, null); + return Load(path, null, (Configuration)null); } /// @@ -61,6 +61,35 @@ namespace ImageSharp return Load(path, null, config); } + /// + /// Loads the image from the given file. + /// + /// The file path to the image. + /// The decoder. + /// + /// Thrown if the stream is not readable nor seekable. + /// + /// The image + public static Image Load(string path, IImageDecoder decoder) + { + return Load(path, decoder, null); + } + + /// + /// Loads the image from the given file. + /// + /// The file path to the image. + /// The decoder. + /// The options for the decoder. + /// + /// Thrown if the stream is not readable nor seekable. + /// + /// The image + public static Image Load(string path, IImageDecoder decoder, IDecoderOptions options) + { + return new Image(Load(path, decoder, options)); + } + /// /// Loads the image from the given file. /// @@ -88,7 +117,7 @@ namespace ImageSharp public static Image Load(string path) where TColor : struct, IPixel { - return Load(path, null, null); + return Load(path, null, (Configuration)null); } /// @@ -123,6 +152,22 @@ namespace ImageSharp return Load(path, null, config); } + /// + /// Loads the image from the given file. + /// + /// The pixel format. + /// The file path to the image. + /// The decoder. + /// + /// Thrown if the stream is not readable nor seekable. + /// + /// The image + public static Image Load(string path, IImageDecoder decoder) + where TColor : struct, IPixel + { + return Load(path, decoder, null); + } + /// /// Loads the image from the given file. /// @@ -143,6 +188,27 @@ namespace ImageSharp return Load(s, options, config); } } + + /// + /// Loads the image from the given file. + /// + /// The pixel format. + /// The file path to the image. + /// The decoder. + /// The options for the decoder. + /// + /// Thrown if the stream is not readable nor seekable. + /// + /// The image + public static Image Load(string path, IImageDecoder decoder, IDecoderOptions options) + where TColor : struct, IPixel + { + Configuration config = Configuration.Default; + using (Stream s = config.FileSystem.OpenRead(path)) + { + return Load(s, decoder, options); + } + } } #endif } diff --git a/src/ImageSharp/Image.FromStream.cs b/src/ImageSharp/Image.FromStream.cs index 36309db5b5..b40aa62cd9 100644 --- a/src/ImageSharp/Image.FromStream.cs +++ b/src/ImageSharp/Image.FromStream.cs @@ -29,7 +29,7 @@ namespace ImageSharp /// The image public static Image Load(Stream stream) { - return Load(stream, null, null); + return Load(stream, null, (Configuration)null); } /// @@ -60,6 +60,20 @@ namespace ImageSharp return Load(stream, null, config); } + /// + /// Loads the image from the given stream. + /// + /// The stream containing image information. + /// The decoder. + /// + /// Thrown if the stream is not readable nor seekable. + /// + /// The image + public static Image Load(Stream stream, IImageDecoder decoder) + { + return Load(stream, decoder, null); + } + /// /// Loads the image from the given stream. /// @@ -75,6 +89,21 @@ namespace ImageSharp return new Image(Load(stream, options, config)); } + /// + /// Loads the image from the given stream. + /// + /// The stream containing image information. + /// The decoder. + /// The options for the decoder. + /// + /// Thrown if the stream is not readable nor seekable. + /// + /// The image + public static Image Load(Stream stream, IImageDecoder decoder, IDecoderOptions options) + { + return new Image(Load(stream, decoder, options)); + } + /// /// Loads the image from the given stream. /// @@ -87,7 +116,7 @@ namespace ImageSharp public static Image Load(Stream stream) where TColor : struct, IPixel { - return Load(stream, null, null); + return Load(stream, null, (Configuration)null); } /// @@ -122,6 +151,39 @@ namespace ImageSharp return Load(stream, null, config); } + /// + /// Loads the image from the given stream. + /// + /// The pixel format. + /// The stream containing image information. + /// The decoder. + /// + /// Thrown if the stream is not readable nor seekable. + /// + /// The image + public static Image Load(Stream stream, IImageDecoder decoder) + where TColor : struct, IPixel + { + return Load(stream, decoder, null); + } + + /// + /// Loads the image from the given stream. + /// + /// The pixel format. + /// The stream containing image information. + /// The decoder. + /// The options for the decoder. + /// + /// Thrown if the stream is not readable nor seekable. + /// + /// The image + public static Image Load(Stream stream, IImageDecoder decoder, IDecoderOptions options) + where TColor : struct, IPixel + { + return WithSeekableStream(stream, s => decoder.Decode(s, options)); + } + /// /// Loads the image from the given stream. /// @@ -138,11 +200,29 @@ namespace ImageSharp { config = config ?? Configuration.Default; - if (!config.ImageFormats.Any()) + Image img = WithSeekableStream(stream, s => + { + return Decode(stream, options, config); + }); + + if (img != null) + { + return img; + } + + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.AppendLine("Image cannot be loaded. Available formats:"); + + foreach (IImageFormat format in config.ImageFormats) { - throw new InvalidOperationException("No image formats have been configured."); + stringBuilder.AppendLine("-" + format); } + throw new NotSupportedException(stringBuilder.ToString()); + } + + private static T WithSeekableStream(Stream stream, Func action) + { if (!stream.CanRead) { throw new NotSupportedException("Cannot read from the stream."); @@ -150,10 +230,7 @@ namespace ImageSharp if (stream.CanSeek) { - if (Decode(stream, options, config, out Image img)) - { - return img; - } + return action(stream); } else { @@ -163,22 +240,9 @@ namespace ImageSharp stream.CopyTo(ms); ms.Position = 0; - if (Decode(ms, options, config, out Image img)) - { - return img; - } + return action(stream); } } - - StringBuilder stringBuilder = new StringBuilder(); - stringBuilder.AppendLine("Image cannot be loaded. Available formats:"); - - foreach (IImageFormat format in config.ImageFormats) - { - stringBuilder.AppendLine("-" + format); - } - - throw new NotSupportedException(stringBuilder.ToString()); } } } diff --git a/tests/ImageSharp.Tests/Image/ImageLoadTests.cs b/tests/ImageSharp.Tests/Image/ImageLoadTests.cs index 2c97ea06fd..da3d2fb069 100644 --- a/tests/ImageSharp.Tests/Image/ImageLoadTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageLoadTests.cs @@ -66,6 +66,9 @@ namespace ImageSharp.Tests this.FilePath = Guid.NewGuid().ToString(); this.fileSystem.Setup(x => x.OpenRead(this.FilePath)).Returns(this.DataStream); + + TestFileSystem.RegisterGloablTestFormat(); + TestFileSystem.Global.AddFile(this.FilePath, this.DataStream); } [Fact] @@ -157,6 +160,50 @@ namespace ImageSharp.Tests this.localDecoder.Verify(x => x.Decode(stream, this.decoderOptions)); } + + + [Fact] + public void LoadFromStreamWithDecoder() + { + Stream stream = new MemoryStream(); + Image img = Image.Load(stream, this.localDecoder.Object); + + Assert.NotNull(img); + this.localDecoder.Verify(x => x.Decode(stream, null)); + } + + [Fact] + public void LoadFromStreamWithTypeAndDecoder() + { + Stream stream = new MemoryStream(); + Image img = Image.Load(stream, this.localDecoder.Object); + + Assert.NotNull(img); + Assert.Equal(this.returnImage, img); + this.localDecoder.Verify(x => x.Decode(stream, null)); + } + + [Fact] + public void LoadFromStreamWithDecoderAndOptions() + { + Stream stream = new MemoryStream(); + Image img = Image.Load(stream, this.localDecoder.Object, this.decoderOptions); + + Assert.NotNull(img); + this.localDecoder.Verify(x => x.Decode(stream, this.decoderOptions)); + } + + [Fact] + public void LoadFromStreamWithTypeAndDecoderAndOptions() + { + Stream stream = new MemoryStream(); + Image img = Image.Load(stream, this.localDecoder.Object, this.decoderOptions); + + Assert.NotNull(img); + Assert.Equal(this.returnImage, img); + this.localDecoder.Verify(x => x.Decode(stream, this.decoderOptions)); + } + [Fact] public void LoadFromBytes() { @@ -246,7 +293,50 @@ namespace ImageSharp.Tests this.localDecoder.Verify(x => x.Decode(It.IsAny(), this.decoderOptions)); Assert.Equal(this.DataStream.ToArray(), this.DecodedData); } - + + + [Fact] + public void LoadFromBytesWithDecoder() + { + Image img = Image.Load(this.DataStream.ToArray(), this.localDecoder.Object); + + Assert.NotNull(img); + this.localDecoder.Verify(x => x.Decode(It.IsAny(), null)); + Assert.Equal(this.DataStream.ToArray(), this.DecodedData); + } + + [Fact] + public void LoadFromBytesWithTypeAndDecoder() + { + Image img = Image.Load(this.DataStream.ToArray(), this.localDecoder.Object); + + Assert.NotNull(img); + Assert.Equal(this.returnImage, img); + this.localDecoder.Verify(x => x.Decode(It.IsAny(), null)); + Assert.Equal(this.DataStream.ToArray(), this.DecodedData); + } + + [Fact] + public void LoadFromBytesWithDecoderAndOptions() + { + Image img = Image.Load(this.DataStream.ToArray(), this.localDecoder.Object, this.decoderOptions); + + Assert.NotNull(img); + this.localDecoder.Verify(x => x.Decode(It.IsAny(), this.decoderOptions)); + Assert.Equal(this.DataStream.ToArray(), this.DecodedData); + } + + [Fact] + public void LoadFromBytesWithTypeAndDecoderAndOptions() + { + Image img = Image.Load(this.DataStream.ToArray(), this.localDecoder.Object, this.decoderOptions); + + Assert.NotNull(img); + Assert.Equal(this.returnImage, img); + this.localDecoder.Verify(x => x.Decode(It.IsAny(), this.decoderOptions)); + Assert.Equal(this.DataStream.ToArray(), this.DecodedData); + } + [Fact] public void LoadFromFile() { @@ -324,7 +414,7 @@ namespace ImageSharp.Tests [Fact] public void LoadFromFileWithTypeAndConfigAndOptions() { - Image img = Image.Load(FilePath, this.decoderOptions, this.LocalConfiguration); + Image img = Image.Load(this.FilePath, this.decoderOptions, this.LocalConfiguration); Assert.NotNull(img); Assert.Equal(this.returnImage, img); @@ -332,6 +422,45 @@ namespace ImageSharp.Tests this.localDecoder.Verify(x => x.Decode(this.DataStream, this.decoderOptions)); } + + [Fact] + public void LoadFromFileWithDecoder() + { + Image img = Image.Load(this.FilePath, this.localDecoder.Object); + + Assert.NotNull(img); + this.localDecoder.Verify(x => x.Decode(this.DataStream, null)); + } + + [Fact] + public void LoadFromFileWithTypeAndDecoder() + { + Image img = Image.Load(this.FilePath, this.localDecoder.Object); + + Assert.NotNull(img); + Assert.Equal(this.returnImage, img); + this.localDecoder.Verify(x => x.Decode(this.DataStream, null)); + } + + [Fact] + public void LoadFromFileWithDecoderAndOptions() + { + Image img = Image.Load(this.FilePath, this.localDecoder.Object, this.decoderOptions); + + Assert.NotNull(img); + this.localDecoder.Verify(x => x.Decode(this.DataStream, this.decoderOptions)); + } + + [Fact] + public void LoadFromFileWithTypeAndDecoderAndOptions() + { + Image img = Image.Load(this.FilePath, this.localDecoder.Object, this.decoderOptions); + + Assert.NotNull(img); + Assert.Equal(this.returnImage, img); + this.localDecoder.Verify(x => x.Decode(this.DataStream, this.decoderOptions)); + } + public void Dispose() { // clean up the global object; diff --git a/tests/ImageSharp.Tests/TestFileSystem.cs b/tests/ImageSharp.Tests/TestFileSystem.cs new file mode 100644 index 0000000000..78499ac8ee --- /dev/null +++ b/tests/ImageSharp.Tests/TestFileSystem.cs @@ -0,0 +1,72 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System; + using System.Collections.Concurrent; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Reflection; + using ImageSharp.Formats; + using Xunit; + + /// + /// A test image file. + /// + public class TestFileSystem : ImageSharp.IO.IFileSystem + { + + public static TestFileSystem Global { get; } = new TestFileSystem(); + + public static void RegisterGloablTestFormat() + { + Configuration.Default.FileSystem = Global; + } + + Dictionary fileSystem = new Dictionary(StringComparer.OrdinalIgnoreCase); + + public void AddFile(string path, Stream data) + { + fileSystem.Add(path, data); + } + + public Stream Create(string path) + { + MemoryStream stream = new MemoryStream(); + lock (fileSystem) + { + if (fileSystem.ContainsKey(path)) + { + fileSystem[path] = stream; + } + else + { + fileSystem.Add(path, stream); + } + } + return stream; + } + + + public Stream OpenRead(string path) + { + lock (fileSystem) + { + if (fileSystem.ContainsKey(path)) + { + + Stream stream = fileSystem[path]; + stream.Position = 0; + return stream; + } + } + + return File.OpenRead(path); + } + } +} +