From ea44e6ce158069d089bf119ac1ed353e981987ca Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Tue, 20 Jun 2017 22:28:11 +0100 Subject: [PATCH 01/36] refactor encoders/decoders refactor encoders to remove IImageFormat and split the logic across Encoders and Decoders. remove IXXXOptions and moved setting onto encoders/decoders directly. add out param on Image.Load APIs to output mime type of loaded image. --- src/ImageSharp/Configuration.cs | 153 +++------ src/ImageSharp/Formats/Bmp/BmpConstants.cs | 25 ++ src/ImageSharp/Formats/Bmp/BmpDecoder.cs | 20 +- src/ImageSharp/Formats/Bmp/BmpEncoder.cs | 28 +- src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs | 20 +- .../Formats/Bmp/BmpEncoderOptions.cs | 45 --- src/ImageSharp/Formats/Bmp/BmpFormat.cs | 41 --- .../Formats/Bmp/IBmpEncoderOptions.cs | 18 -- src/ImageSharp/Formats/DecoderOptions.cs | 37 --- src/ImageSharp/Formats/EncoderOptions.cs | 37 --- src/ImageSharp/Formats/Gif/GifConstants.cs | 13 +- src/ImageSharp/Formats/Gif/GifDecoder.cs | 47 ++- src/ImageSharp/Formats/Gif/GifDecoderCore.cs | 27 +- .../Formats/Gif/GifDecoderOptions.cs | 47 --- src/ImageSharp/Formats/Gif/GifEncoder.cs | 51 ++- src/ImageSharp/Formats/Gif/GifEncoderCore.cs | 41 ++- .../Formats/Gif/GifEncoderOptions.cs | 65 ---- src/ImageSharp/Formats/Gif/GifFormat.cs | 45 --- .../Formats/Gif/IGifDecoderOptions.cs | 20 -- .../Formats/Gif/IGifEncoderOptions.cs | 38 --- src/ImageSharp/Formats/Gif/ImageExtensions.cs | 8 +- src/ImageSharp/Formats/IDecoderOptions.cs | 18 -- src/ImageSharp/Formats/IEncoderOptions.cs | 18 -- src/ImageSharp/Formats/IImageDecoder.cs | 30 +- src/ImageSharp/Formats/IImageEncoder.cs | 14 +- src/ImageSharp/Formats/IImageFormat.cs | 60 ---- .../Formats/Jpeg/IJpegEncoderOptions.cs | 26 -- .../Formats/Jpeg/ImageExtensions.cs | 8 +- src/ImageSharp/Formats/Jpeg/JpegConstants.cs | 12 + src/ImageSharp/Formats/Jpeg/JpegDecoder.cs | 76 ++++- .../Formats/Jpeg/JpegDecoderCore.cs | 18 +- src/ImageSharp/Formats/Jpeg/JpegEncoder.cs | 50 ++- .../Formats/Jpeg/JpegEncoderCore.cs | 51 ++- .../Formats/Jpeg/JpegEncoderOptions.cs | 56 ---- src/ImageSharp/Formats/Jpeg/JpegFormat.cs | 89 ----- .../Formats/Png/IPngDecoderOptions.cs | 20 -- .../Formats/Png/IPngEncoderOptions.cs | 54 ---- src/ImageSharp/Formats/Png/ImageExtensions.cs | 8 +- src/ImageSharp/Formats/Png/PngConstants.cs | 30 ++ src/ImageSharp/Formats/Png/PngDecoder.cs | 43 ++- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 42 +-- .../Formats/Png/PngDecoderOptions.cs | 49 --- src/ImageSharp/Formats/Png/PngEncoder.cs | 71 +++- src/ImageSharp/Formats/Png/PngEncoderCore.cs | 84 +++-- .../Formats/Png/PngEncoderOptions.cs | 82 ----- src/ImageSharp/Formats/Png/PngFormat.cs | 47 --- src/ImageSharp/Image/IImage.cs | 5 - src/ImageSharp/Image/Image.Decode.cs | 22 +- src/ImageSharp/Image/Image.FromBytes.cs | 66 ++-- src/ImageSharp/Image/Image.FromFile.cs | 66 ++-- src/ImageSharp/Image/Image.FromStream.cs | 60 ++-- src/ImageSharp/Image/Image{TPixel}.cs | 156 ++------- src/ImageSharp/ImageSharp.csproj | 1 + src/ImageSharp/MetaData/ImageMetaData.cs | 6 - tests/ImageSharp.Benchmarks/BenchmarkBase.cs | 4 - .../Image/EncodeIndexedPng.cs | 12 +- .../ImageSharp.Benchmarks/Image/EncodePng.cs | 2 +- tests/ImageSharp.Tests/ConfigurationTests.cs | 204 +----------- .../ImageSharp.Tests/Drawing/BeziersTests.cs | 28 +- .../ImageSharp.Tests/Drawing/DrawPathTests.cs | 29 +- .../Drawing/FillPatternTests.cs | 10 +- .../Drawing/FillSolidBrushTests.cs | 34 +- .../Drawing/LineComplexPolygonTests.cs | 55 ++-- tests/ImageSharp.Tests/Drawing/LineTests.cs | 119 +++---- .../ImageSharp.Tests/Drawing/PolygonTests.cs | 15 +- .../Drawing/RecolorImageTest.cs | 16 +- .../Drawing/SolidBezierTests.cs | 10 +- .../Drawing/SolidComplexPolygonTests.cs | 35 +- .../Drawing/SolidPolygonTests.cs | 96 ++---- .../Formats/Bmp/BmpEncoderTests.cs | 2 +- .../Formats/GeneralFormatTests.cs | 22 +- .../Formats/Gif/GifDecoderTests.cs | 6 +- .../Formats/Gif/GifEncoderTests.cs | 8 +- .../Formats/Jpg/JpegDecoderTests.cs | 13 +- .../Formats/Jpg/JpegEncoderTests.cs | 16 +- .../Formats/Jpg/JpegProfilingBenchmarks.cs | 5 +- .../Formats/Png/PngDecoderTests.cs | 6 +- .../Formats/Png/PngEncoderTests.cs | 4 +- .../Formats/Png/PngSmokeTests.cs | 3 +- .../ImageSharp.Tests/Image/ImageLoadTests.cs | 303 ++---------------- .../ImageSharp.Tests/Image/ImageSaveTests.cs | 121 +------ tests/ImageSharp.Tests/Image/ImageTests.cs | 30 +- .../MetaData/ImageMetaDataTests.cs | 2 - tests/ImageSharp.Tests/TestFile.cs | 7 +- tests/ImageSharp.Tests/TestFormat.cs | 34 +- .../TestUtilities/ImagingTestCaseUtility.cs | 12 +- 86 files changed, 1094 insertions(+), 2433 deletions(-) create mode 100644 src/ImageSharp/Formats/Bmp/BmpConstants.cs delete mode 100644 src/ImageSharp/Formats/Bmp/BmpEncoderOptions.cs delete mode 100644 src/ImageSharp/Formats/Bmp/BmpFormat.cs delete mode 100644 src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs delete mode 100644 src/ImageSharp/Formats/DecoderOptions.cs delete mode 100644 src/ImageSharp/Formats/EncoderOptions.cs delete mode 100644 src/ImageSharp/Formats/Gif/GifDecoderOptions.cs delete mode 100644 src/ImageSharp/Formats/Gif/GifEncoderOptions.cs delete mode 100644 src/ImageSharp/Formats/Gif/GifFormat.cs delete mode 100644 src/ImageSharp/Formats/Gif/IGifDecoderOptions.cs delete mode 100644 src/ImageSharp/Formats/Gif/IGifEncoderOptions.cs delete mode 100644 src/ImageSharp/Formats/IDecoderOptions.cs delete mode 100644 src/ImageSharp/Formats/IEncoderOptions.cs delete mode 100644 src/ImageSharp/Formats/IImageFormat.cs delete mode 100644 src/ImageSharp/Formats/Jpeg/IJpegEncoderOptions.cs delete mode 100644 src/ImageSharp/Formats/Jpeg/JpegEncoderOptions.cs delete mode 100644 src/ImageSharp/Formats/Jpeg/JpegFormat.cs delete mode 100644 src/ImageSharp/Formats/Png/IPngDecoderOptions.cs delete mode 100644 src/ImageSharp/Formats/Png/IPngEncoderOptions.cs create mode 100644 src/ImageSharp/Formats/Png/PngConstants.cs delete mode 100644 src/ImageSharp/Formats/Png/PngDecoderOptions.cs delete mode 100644 src/ImageSharp/Formats/Png/PngEncoderOptions.cs delete mode 100644 src/ImageSharp/Formats/Png/PngFormat.cs diff --git a/src/ImageSharp/Configuration.cs b/src/ImageSharp/Configuration.cs index fa983d3557..5734d70fb8 100644 --- a/src/ImageSharp/Configuration.cs +++ b/src/ImageSharp/Configuration.cs @@ -30,27 +30,20 @@ namespace ImageSharp private readonly object syncRoot = new object(); /// - /// The list of supported . + /// The list of supported . /// - private readonly List imageFormatsList = new List(); + private readonly List encoders = new List(); /// - /// Initializes a new instance of the class. + /// The list of supported . /// - public Configuration() - { - } + private readonly List decoders = new List(); /// /// Initializes a new instance of the class. /// - /// The inital set of image formats. - public Configuration(params IImageFormat[] providers) + public Configuration() { - foreach (IImageFormat p in providers) - { - this.AddImageFormat(p); - } } /// @@ -59,9 +52,14 @@ namespace ImageSharp public static Configuration Default { get; } = Lazy.Value; /// - /// Gets the collection of supported + /// Gets the collection of supported /// - public IReadOnlyCollection ImageFormats => new ReadOnlyCollection(this.imageFormatsList); + public IReadOnlyCollection ImageEncoders => new ReadOnlyCollection(this.encoders); + + /// + /// Gets the collection of supported + /// + public IReadOnlyCollection ImageDecoders => new ReadOnlyCollection(this.decoders); /// /// Gets the global parallel options for processing tasks in parallel. @@ -81,118 +79,57 @@ namespace ImageSharp #endif /// - /// Adds a new to the collection of supported image formats. - /// - /// The new format to add. - public void AddImageFormat(IImageFormat format) - { - Guard.NotNull(format, nameof(format)); - Guard.NotNull(format.Encoder, nameof(format), "The encoder should not be null."); - Guard.NotNull(format.Decoder, nameof(format), "The decoder should not be null."); - Guard.NotNullOrEmpty(format.MimeType, nameof(format), "The mime type should not be null or empty."); - Guard.NotNullOrEmpty(format.Extension, nameof(format), "The extension should not be null or empty."); - Guard.NotNullOrEmpty(format.SupportedExtensions, nameof(format), "The supported extensions not be null or empty."); - - this.AddImageFormatLocked(format); - } - - /// - /// Creates the default instance, with Png, Jpeg, Gif and Bmp preregisterd (if they have been referenced) + /// Adds a new to the collection of supported image formats. /// - /// The default configuration of - internal static Configuration CreateDefaultInstance() + /// The new format to add. + public void AddImageFormat(IImageDecoder decoder) { - Configuration config = new Configuration(); - - // lets try auto loading the known image formats - config.AddImageFormat(new Formats.PngFormat()); - config.AddImageFormat(new Formats.JpegFormat()); - config.AddImageFormat(new Formats.GifFormat()); - config.AddImageFormat(new Formats.BmpFormat()); - return config; - } + Guard.NotNull(decoder, nameof(decoder)); + Guard.NotNullOrEmpty(decoder.FileExtensions, nameof(decoder.FileExtensions)); + Guard.NotNullOrEmpty(decoder.MimeTypes, nameof(decoder.MimeTypes)); - /// - /// Tries the add image format. - /// - /// Name of the type. - /// True if type discoverd and is a valid - internal bool TryAddImageFormat(string typeName) - { - Type type = Type.GetType(typeName, false); - if (type != null) + lock (this.syncRoot) { - IImageFormat format = Activator.CreateInstance(type) as IImageFormat; - if (format != null - && format.Encoder != null - && format.Decoder != null - && !string.IsNullOrEmpty(format.MimeType) - && format.SupportedExtensions?.Any() == true) - { - // we can use the locked version as we have already validated in the if. - this.AddImageFormatLocked(format); - return true; - } - } + this.decoders.Add(decoder); - return false; + this.SetMaxHeaderSize(); + } } /// - /// Adds image format. The class is locked to make it thread safe. + /// Adds a new to the collection of supported image formats. /// - /// The image format. - private void AddImageFormatLocked(IImageFormat format) + /// The new format to add. + public void AddImageFormat(IImageEncoder encoder) { + Guard.NotNull(encoder, nameof(encoder)); + Guard.NotNullOrEmpty(encoder.FileExtensions, nameof(encoder.FileExtensions)); + Guard.NotNullOrEmpty(encoder.MimeTypes, nameof(encoder.MimeTypes)); lock (this.syncRoot) { - if (this.GuardDuplicate(format)) - { - this.imageFormatsList.Add(format); - - this.SetMaxHeaderSize(); - } + this.encoders.Add(encoder); } } /// - /// Checks to ensure duplicate image formats are not added. + /// Creates the default instance, with Png, Jpeg, Gif and Bmp preregisterd (if they have been referenced) /// - /// The image format. - /// Thrown if a duplicate is added. - /// - /// The . - /// - private bool GuardDuplicate(IImageFormat format) + /// The default configuration of + internal static Configuration CreateDefaultInstance() { - if (!format.SupportedExtensions.Contains(format.Extension, StringComparer.OrdinalIgnoreCase)) - { - throw new ArgumentException("The supported extensions should contain the default extension.", nameof(format)); - } - - // ReSharper disable once ConvertClosureToMethodGroup - // Prevents method group allocation - if (format.SupportedExtensions.Any(e => string.IsNullOrWhiteSpace(e))) - { - throw new ArgumentException("The supported extensions should not contain empty values.", nameof(format)); - } - - // If there is already a format with the same extension or a format that supports that - // extension return false. - foreach (IImageFormat imageFormat in this.imageFormatsList) - { - if (imageFormat.Extension.Equals(format.Extension, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - - if (imageFormat.SupportedExtensions.Intersect(format.SupportedExtensions, StringComparer.OrdinalIgnoreCase).Any()) - { - return false; - } - } + Configuration config = new Configuration(); - return true; + // lets try auto loading the known image formats + config.AddImageFormat(new Formats.PngEncoder()); + config.AddImageFormat(new Formats.JpegEncoder()); + config.AddImageFormat(new Formats.GifEncoder()); + config.AddImageFormat(new Formats.BmpEncoder()); + + config.AddImageFormat(new Formats.PngDecoder()); + config.AddImageFormat(new Formats.JpegDecoder()); + config.AddImageFormat(new Formats.GifDecoder()); + config.AddImageFormat(new Formats.BmpDecoder()); + return config; } /// @@ -200,7 +137,7 @@ namespace ImageSharp /// private void SetMaxHeaderSize() { - this.MaxHeaderSize = this.imageFormatsList.Max(x => x.HeaderSize); + this.MaxHeaderSize = this.decoders.Max(x => x.HeaderSize); } } } diff --git a/src/ImageSharp/Formats/Bmp/BmpConstants.cs b/src/ImageSharp/Formats/Bmp/BmpConstants.cs new file mode 100644 index 0000000000..f2454f24dd --- /dev/null +++ b/src/ImageSharp/Formats/Bmp/BmpConstants.cs @@ -0,0 +1,25 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using System.Collections.Generic; + + /// + /// Defines constants relating to BMPs + /// + internal static class BmpConstants + { + /// + /// The list of mimetypes that equate to a bmp + /// + public static readonly IEnumerable MimeTypes = new[] { "image/bmp", "image/x-windows-bmp" }; + + /// + /// The list of mimetypes that equate to a bmp + /// + public static readonly IEnumerable FileExtensions = new[] { "bm", "bmp", "dip" }; + } +} diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoder.cs b/src/ImageSharp/Formats/Bmp/BmpDecoder.cs index 9090e9a8cd..9ff331490b 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoder.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoder.cs @@ -6,6 +6,7 @@ namespace ImageSharp.Formats { using System; + using System.Collections.Generic; using System.IO; using ImageSharp.PixelFormats; @@ -28,7 +29,24 @@ namespace ImageSharp.Formats public class BmpDecoder : IImageDecoder { /// - public Image Decode(Configuration configuration, Stream stream, IDecoderOptions options) + public IEnumerable MimeTypes => BmpConstants.MimeTypes; + + /// + public IEnumerable FileExtensions => BmpConstants.FileExtensions; + + /// + public int HeaderSize => 2; + + /// + public bool IsSupportedFileFormat(Span header) + { + return header.Length >= this.HeaderSize && + header[0] == 0x42 && // B + header[1] == 0x4D; // M + } + + /// + public Image Decode(Configuration configuration, Stream stream) where TPixel : struct, IPixel { diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoder.cs b/src/ImageSharp/Formats/Bmp/BmpEncoder.cs index dc2bc0e972..25db0eda05 100644 --- a/src/ImageSharp/Formats/Bmp/BmpEncoder.cs +++ b/src/ImageSharp/Formats/Bmp/BmpEncoder.cs @@ -6,6 +6,7 @@ namespace ImageSharp.Formats { using System; + using System.Collections.Generic; using System.IO; using ImageSharp.PixelFormats; @@ -16,26 +17,23 @@ namespace ImageSharp.Formats /// The encoder can currently only write 24-bit rgb images to streams. public class BmpEncoder : IImageEncoder { + /// + /// Gets or sets the number of bits per pixel. + /// + public BmpBitsPerPixel BitsPerPixel { get; set; } = BmpBitsPerPixel.Pixel24; + /// - public void Encode(Image image, Stream stream, IEncoderOptions options) - where TPixel : struct, IPixel - { - IBmpEncoderOptions bmpOptions = BmpEncoderOptions.Create(options); + public IEnumerable MimeTypes => BmpConstants.MimeTypes; - this.Encode(image, stream, bmpOptions); - } + /// + public IEnumerable FileExtensions => BmpConstants.FileExtensions; - /// - /// Encodes the image to the specified stream from the . - /// - /// The pixel format. - /// The to encode from. - /// The to encode the image data to. - /// The options for the encoder. - public void Encode(Image image, Stream stream, IBmpEncoderOptions options) + /// + public void Encode(Image image, Stream stream) where TPixel : struct, IPixel { - BmpEncoderCore encoder = new BmpEncoderCore(options); + BmpEncoderCore encoder = new BmpEncoderCore(); + encoder.BitsPerPixel = this.BitsPerPixel; encoder.Encode(image, stream); } } diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs index 617edde8eb..615ff23ee2 100644 --- a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs @@ -17,11 +17,6 @@ namespace ImageSharp.Formats /// internal sealed class BmpEncoderCore { - /// - /// The options for the encoder. - /// - private readonly IBmpEncoderOptions options; - /// /// The amount to pad each row by. /// @@ -30,12 +25,15 @@ namespace ImageSharp.Formats /// /// Initializes a new instance of the class. /// - /// The options for the encoder. - public BmpEncoderCore(IBmpEncoderOptions options) + public BmpEncoderCore() { - this.options = options ?? new BmpEncoderOptions(); } + /// + /// Gets or sets the BitsPerPixel + /// + public BmpBitsPerPixel BitsPerPixel { get; internal set; } = BmpBitsPerPixel.Pixel24; + /// /// Encodes the image to the specified stream from the . /// @@ -49,9 +47,9 @@ namespace ImageSharp.Formats Guard.NotNull(stream, nameof(stream)); // Cast to int will get the bytes per pixel - short bpp = (short)(8 * (int)this.options.BitsPerPixel); + short bpp = (short)(8 * (int)this.BitsPerPixel); int bytesPerLine = 4 * (((image.Width * bpp) + 31) / 32); - this.padding = bytesPerLine - (image.Width * (int)this.options.BitsPerPixel); + this.padding = bytesPerLine - (image.Width * (int)this.BitsPerPixel); // Do not use IDisposable pattern here as we want to preserve the stream. EndianBinaryWriter writer = new EndianBinaryWriter(Endianness.LittleEndian, stream); @@ -136,7 +134,7 @@ namespace ImageSharp.Formats { using (PixelAccessor pixels = image.Lock()) { - switch (this.options.BitsPerPixel) + switch (this.BitsPerPixel) { case BmpBitsPerPixel.Pixel32: this.Write32Bit(writer, pixels); diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoderOptions.cs b/src/ImageSharp/Formats/Bmp/BmpEncoderOptions.cs deleted file mode 100644 index a0f9ff8e05..0000000000 --- a/src/ImageSharp/Formats/Bmp/BmpEncoderOptions.cs +++ /dev/null @@ -1,45 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Formats -{ - /// - /// Encapsulates the options for the . - /// - public sealed class BmpEncoderOptions : EncoderOptions, IBmpEncoderOptions - { - /// - /// Initializes a new instance of the class. - /// - public BmpEncoderOptions() - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The options for the encoder. - private BmpEncoderOptions(IEncoderOptions options) - : base(options) - { - } - - /// - /// Gets or sets the number of bits per pixel. - /// - public BmpBitsPerPixel BitsPerPixel { get; set; } = BmpBitsPerPixel.Pixel24; - - /// - /// Converts the options to a instance with a cast - /// or by creating a new instance with the specfied options. - /// - /// The options for the encoder. - /// The options for the . - internal static IBmpEncoderOptions Create(IEncoderOptions options) - { - return options as IBmpEncoderOptions ?? new BmpEncoderOptions(options); - } - } -} diff --git a/src/ImageSharp/Formats/Bmp/BmpFormat.cs b/src/ImageSharp/Formats/Bmp/BmpFormat.cs deleted file mode 100644 index bf73d31621..0000000000 --- a/src/ImageSharp/Formats/Bmp/BmpFormat.cs +++ /dev/null @@ -1,41 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Formats -{ - using System.Collections.Generic; - - /// - /// Encapsulates the means to encode and decode bitmap images. - /// - public class BmpFormat : IImageFormat - { - /// - public string MimeType => "image/bmp"; - - /// - public string Extension => "bmp"; - - /// - public IEnumerable SupportedExtensions => new string[] { "bmp", "dip" }; - - /// - public IImageDecoder Decoder => new BmpDecoder(); - - /// - public IImageEncoder Encoder => new BmpEncoder(); - - /// - public int HeaderSize => 2; - - /// - public bool IsSupportedFileFormat(byte[] header) - { - return header.Length >= this.HeaderSize && - header[0] == 0x42 && // B - header[1] == 0x4D; // M - } - } -} diff --git a/src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs b/src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs deleted file mode 100644 index 6cf37cbae3..0000000000 --- a/src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs +++ /dev/null @@ -1,18 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Formats -{ - /// - /// Encapsulates the options for the . - /// - public interface IBmpEncoderOptions : IEncoderOptions - { - /// - /// Gets the number of bits per pixel. - /// - BmpBitsPerPixel BitsPerPixel { get; } - } -} diff --git a/src/ImageSharp/Formats/DecoderOptions.cs b/src/ImageSharp/Formats/DecoderOptions.cs deleted file mode 100644 index 5257b07b39..0000000000 --- a/src/ImageSharp/Formats/DecoderOptions.cs +++ /dev/null @@ -1,37 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - /// - /// Encapsulates the shared decoder options. - /// - public class DecoderOptions : IDecoderOptions - { - /// - /// Initializes a new instance of the class. - /// - public DecoderOptions() - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The decoder options - protected DecoderOptions(IDecoderOptions options) - { - if (options != null) - { - this.IgnoreMetadata = options.IgnoreMetadata; - } - } - - /// - /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded. - /// - public bool IgnoreMetadata { get; set; } = false; - } -} diff --git a/src/ImageSharp/Formats/EncoderOptions.cs b/src/ImageSharp/Formats/EncoderOptions.cs deleted file mode 100644 index 27a7e9781d..0000000000 --- a/src/ImageSharp/Formats/EncoderOptions.cs +++ /dev/null @@ -1,37 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - /// - /// Encapsulates the shared encoder options. - /// - public class EncoderOptions : IEncoderOptions - { - /// - /// Initializes a new instance of the class. - /// - public EncoderOptions() - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The encoder options - protected EncoderOptions(IEncoderOptions options) - { - if (options != null) - { - this.IgnoreMetadata = options.IgnoreMetadata; - } - } - - /// - /// Gets or sets a value indicating whether the metadata should be ignored when the image is being encoded. - /// - public bool IgnoreMetadata { get; set; } = false; - } -} diff --git a/src/ImageSharp/Formats/Gif/GifConstants.cs b/src/ImageSharp/Formats/Gif/GifConstants.cs index 4af291c2ba..5c4d806d70 100644 --- a/src/ImageSharp/Formats/Gif/GifConstants.cs +++ b/src/ImageSharp/Formats/Gif/GifConstants.cs @@ -5,6 +5,7 @@ namespace ImageSharp.Formats { + using System.Collections.Generic; using System.Text; /// @@ -90,6 +91,16 @@ namespace ImageSharp.Formats /// /// Gets the default encoding to use when reading comments. /// - public static Encoding DefaultEncoding { get; } = Encoding.GetEncoding("ASCII"); + public static readonly Encoding DefaultEncoding = Encoding.GetEncoding("ASCII"); + + /// + /// The list of mimetypes that equate to a bmp + /// + public static readonly IEnumerable MimeTypes = new[] { "image/gif" }; + + /// + /// The list of mimetypes that equate to a bmp + /// + public static readonly IEnumerable FileExtensions = new[] { "gif" }; } } diff --git a/src/ImageSharp/Formats/Gif/GifDecoder.cs b/src/ImageSharp/Formats/Gif/GifDecoder.cs index 88aaccf6a4..506b37dc8e 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoder.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoder.cs @@ -6,8 +6,9 @@ namespace ImageSharp.Formats { using System; + using System.Collections.Generic; using System.IO; - + using System.Text; using ImageSharp.PixelFormats; /// @@ -16,27 +17,43 @@ namespace ImageSharp.Formats public class GifDecoder : IImageDecoder { /// - public Image Decode(Configuration configuration, Stream stream, IDecoderOptions options) + public IEnumerable MimeTypes => GifConstants.MimeTypes; - where TPixel : struct, IPixel - { - IGifDecoderOptions gifOptions = GifDecoderOptions.Create(options); + /// + public IEnumerable FileExtensions => GifConstants.FileExtensions; - return this.Decode(configuration, stream, gifOptions); - } + /// + /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded. + /// + public bool IgnoreMetadata { get; set; } = false; /// - /// Decodes the image from the specified stream to the . + /// Gets or sets the encoding that should be used when reading comments. /// - /// The pixel format. - /// The configuration. - /// The containing image data. - /// The options for the decoder. - /// The image thats been decoded. - public Image Decode(Configuration configuration, Stream stream, IGifDecoderOptions options) + public Encoding TextEncoding { get; set; } = GifConstants.DefaultEncoding; + + /// + public int HeaderSize => 6; + + /// + public bool IsSupportedFileFormat(Span header) + { + return header.Length >= this.HeaderSize && + header[0] == 0x47 && // G + header[1] == 0x49 && // I + header[2] == 0x46 && // F + header[3] == 0x38 && // 8 + (header[4] == 0x39 || header[4] == 0x37) && // 9 or 7 + header[5] == 0x61; // a + } + + /// + public Image Decode(Configuration configuration, Stream stream) where TPixel : struct, IPixel { - return new GifDecoderCore(options, configuration).Decode(stream); + var decoder = new GifDecoderCore(this.TextEncoding, configuration); + decoder.IgnoreMetadata = this.IgnoreMetadata; + return decoder.Decode(stream); } } } diff --git a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs index 5b56c4c02f..0bd64b0577 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs @@ -25,11 +25,6 @@ namespace ImageSharp.Formats /// private readonly byte[] buffer = new byte[16]; - /// - /// The decoder options. - /// - private readonly IGifDecoderOptions options; - /// /// The global configuration. /// @@ -83,14 +78,24 @@ namespace ImageSharp.Formats /// /// Initializes a new instance of the class. /// - /// The decoder options. + /// The decoder encoding. /// The configuration. - public GifDecoderCore(IGifDecoderOptions options, Configuration configuration) + public GifDecoderCore(Encoding encoding, Configuration configuration) { - this.options = options ?? new GifDecoderOptions(); + this.TextEncoding = encoding ?? GifConstants.DefaultEncoding; this.configuration = configuration ?? Configuration.Default; } + /// + /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded. + /// + public bool IgnoreMetadata { get; internal set; } + + /// + /// Gets the text encoding + /// + public Encoding TextEncoding { get; private set; } + /// /// Decodes the stream to the image. /// @@ -268,7 +273,7 @@ namespace ImageSharp.Formats throw new ImageFormatException($"Gif comment length '{length}' exceeds max '{GifConstants.MaxCommentLength}'"); } - if (this.options.IgnoreMetadata) + if (this.IgnoreMetadata) { this.currentStream.Seek(length, SeekOrigin.Current); continue; @@ -279,7 +284,7 @@ namespace ImageSharp.Formats try { this.currentStream.Read(commentsBuffer, 0, length); - string comments = this.options.TextEncoding.GetString(commentsBuffer, 0, length); + string comments = this.TextEncoding.GetString(commentsBuffer, 0, length); this.metaData.Properties.Add(new ImageProperty(GifConstants.Comments, comments)); } finally @@ -363,8 +368,6 @@ namespace ImageSharp.Formats if (this.previousFrame == null) { - this.metaData.Quality = colorTableLength / 3; - // This initializes the image to become fully transparent because the alpha channel is zero. this.image = new Image(this.configuration, imageWidth, imageHeight, this.metaData); diff --git a/src/ImageSharp/Formats/Gif/GifDecoderOptions.cs b/src/ImageSharp/Formats/Gif/GifDecoderOptions.cs deleted file mode 100644 index bc7709f759..0000000000 --- a/src/ImageSharp/Formats/Gif/GifDecoderOptions.cs +++ /dev/null @@ -1,47 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Formats -{ - using System.Text; - - /// - /// Encapsulates the options for the . - /// - public sealed class GifDecoderOptions : DecoderOptions, IGifDecoderOptions - { - /// - /// Initializes a new instance of the class. - /// - public GifDecoderOptions() - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The options for the decoder. - private GifDecoderOptions(IDecoderOptions options) - : base(options) - { - } - - /// - /// Gets or sets the encoding that should be used when reading comments. - /// - public Encoding TextEncoding { get; set; } = GifConstants.DefaultEncoding; - - /// - /// Converts the options to a instance with a cast - /// or by creating a new instance with the specfied options. - /// - /// The options for the decoder. - /// The options for the . - internal static IGifDecoderOptions Create(IDecoderOptions options) - { - return options as IGifDecoderOptions ?? new GifDecoderOptions(options); - } - } -} diff --git a/src/ImageSharp/Formats/Gif/GifEncoder.cs b/src/ImageSharp/Formats/Gif/GifEncoder.cs index b5cadd834e..f7dba9266a 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoder.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoder.cs @@ -6,9 +6,11 @@ namespace ImageSharp.Formats { using System; + using System.Collections.Generic; using System.IO; - + using System.Text; using ImageSharp.PixelFormats; + using ImageSharp.Quantizers; /// /// Image encoder for writing image data to a stream in gif format. @@ -16,25 +18,46 @@ namespace ImageSharp.Formats public class GifEncoder : IImageEncoder { /// - public void Encode(Image image, Stream stream, IEncoderOptions options) - where TPixel : struct, IPixel - { - IGifEncoderOptions gifOptions = GifEncoderOptions.Create(options); + public IEnumerable MimeTypes => GifConstants.MimeTypes; - this.Encode(image, stream, gifOptions); - } + /// + public IEnumerable FileExtensions => GifConstants.FileExtensions; /// - /// Encodes the image to the specified stream from the . + /// Gets or sets a value indicating whether the metadata should be ignored when the image is being encoded. /// - /// The pixel format. - /// The to encode from. - /// The to encode the image data to. - /// The options for the encoder. - public void Encode(Image image, Stream stream, IGifEncoderOptions options) + public bool IgnoreMetadata { get; set; } = false; + + /// + /// Gets or sets the encoding that should be used when writing comments. + /// + public Encoding TextEncoding { get; set; } = GifConstants.DefaultEncoding; + + /// + /// Gets or sets the quality of output for images. + /// + /// For gifs the value ranges from 1 to 256. + public int Quality { get; set; } + + /// + /// Gets or sets the transparency threshold. + /// + public byte Threshold { get; set; } = 128; + + /// + /// Gets or sets the quantizer for reducing the color count. + /// + public IQuantizer Quantizer { get; set; } + + /// + public void Encode(Image image, Stream stream) where TPixel : struct, IPixel { - GifEncoderCore encoder = new GifEncoderCore(options); + GifEncoderCore encoder = new GifEncoderCore(this.TextEncoding); + encoder.Quantizer = this.Quantizer; + encoder.Threshold = this.Threshold; + encoder.Quality = this.Quality; + encoder.IgnoreMetadata = this.IgnoreMetadata; encoder.Encode(image, stream); } } diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs index 5ef7ca1658..bc7014f195 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs @@ -9,7 +9,7 @@ namespace ImageSharp.Formats using System.Buffers; using System.IO; using System.Linq; - + using System.Text; using ImageSharp.PixelFormats; using IO; @@ -25,11 +25,6 @@ namespace ImageSharp.Formats /// private readonly byte[] buffer = new byte[16]; - /// - /// The options for the encoder. - /// - private readonly IGifEncoderOptions options; - /// /// The number of bits requires to store the image palette. /// @@ -43,17 +38,37 @@ namespace ImageSharp.Formats /// /// Initializes a new instance of the class. /// - /// The options for the encoder. - public GifEncoderCore(IGifEncoderOptions options) + /// The encoding for the encoder. + public GifEncoderCore(Encoding encoding) { - this.options = options ?? new GifEncoderOptions(); + this.TextEncoding = encoding ?? GifConstants.DefaultEncoding; } + /// + /// Gets the TextEncoding + /// + public Encoding TextEncoding { get; private set; } + /// /// Gets or sets the quantizer for reducing the color count. /// public IQuantizer Quantizer { get; set; } + /// + /// Gets or sets the threshold. + /// + public byte Threshold { get; internal set; } + + /// + /// Gets or sets the quality of output for images. + /// + public int Quality { get; internal set; } + + /// + /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded. + /// + public bool IgnoreMetadata { get; internal set; } + /// /// Encodes the image to the specified stream from the . /// @@ -66,13 +81,13 @@ namespace ImageSharp.Formats Guard.NotNull(image, nameof(image)); Guard.NotNull(stream, nameof(stream)); - this.Quantizer = this.options.Quantizer ?? new OctreeQuantizer(); + this.Quantizer = this.Quantizer ?? new OctreeQuantizer(); // Do not use IDisposable pattern here as we want to preserve the stream. var writer = new EndianBinaryWriter(Endianness.LittleEndian, stream); // Ensure that quality can be set but has a fallback. - int quality = this.options.Quality > 0 ? this.options.Quality : image.MetaData.Quality; + int quality = this.Quality; quality = quality > 0 ? quality.Clamp(1, 256) : 256; // Get the number of bits. @@ -240,7 +255,7 @@ namespace ImageSharp.Formats private void WriteComments(Image image, EndianBinaryWriter writer) where TPixel : struct, IPixel { - if (this.options.IgnoreMetadata) + if (this.IgnoreMetadata) { return; } @@ -251,7 +266,7 @@ namespace ImageSharp.Formats return; } - byte[] comments = this.options.TextEncoding.GetBytes(property.Value); + byte[] comments = this.TextEncoding.GetBytes(property.Value); int count = Math.Min(comments.Length, 255); diff --git a/src/ImageSharp/Formats/Gif/GifEncoderOptions.cs b/src/ImageSharp/Formats/Gif/GifEncoderOptions.cs deleted file mode 100644 index 5d7c6e40b6..0000000000 --- a/src/ImageSharp/Formats/Gif/GifEncoderOptions.cs +++ /dev/null @@ -1,65 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Formats -{ - using System.Text; - - using Quantizers; - - /// - /// Encapsulates the options for the . - /// - public sealed class GifEncoderOptions : EncoderOptions, IGifEncoderOptions - { - /// - /// Initializes a new instance of the class. - /// - public GifEncoderOptions() - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The options for the encoder. - private GifEncoderOptions(IEncoderOptions options) - : base(options) - { - } - - /// - /// Gets or sets the encoding that should be used when writing comments. - /// - public Encoding TextEncoding { get; set; } = GifConstants.DefaultEncoding; - - /// - /// Gets or sets the quality of output for images. - /// - /// For gifs the value ranges from 1 to 256. - public int Quality { get; set; } - - /// - /// Gets or sets the transparency threshold. - /// - public byte Threshold { get; set; } = 128; - - /// - /// Gets or sets the quantizer for reducing the color count. - /// - public IQuantizer Quantizer { get; set; } - - /// - /// Converts the options to a instance with a - /// cast or by creating a new instance with the specfied options. - /// - /// The options for the encoder. - /// The options for the . - internal static IGifEncoderOptions Create(IEncoderOptions options) - { - return options as IGifEncoderOptions ?? new GifEncoderOptions(options); - } - } -} diff --git a/src/ImageSharp/Formats/Gif/GifFormat.cs b/src/ImageSharp/Formats/Gif/GifFormat.cs deleted file mode 100644 index 2851b0b6b2..0000000000 --- a/src/ImageSharp/Formats/Gif/GifFormat.cs +++ /dev/null @@ -1,45 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Formats -{ - using System.Collections.Generic; - - /// - /// Encapsulates the means to encode and decode gif images. - /// - public class GifFormat : IImageFormat - { - /// - public string Extension => "gif"; - - /// - public string MimeType => "image/gif"; - - /// - public IEnumerable SupportedExtensions => new string[] { "gif" }; - - /// - public IImageDecoder Decoder => new GifDecoder(); - - /// - public IImageEncoder Encoder => new GifEncoder(); - - /// - public int HeaderSize => 6; - - /// - public bool IsSupportedFileFormat(byte[] header) - { - return header.Length >= this.HeaderSize && - header[0] == 0x47 && // G - header[1] == 0x49 && // I - header[2] == 0x46 && // F - header[3] == 0x38 && // 8 - (header[4] == 0x39 || header[4] == 0x37) && // 9 or 7 - header[5] == 0x61; // a - } - } -} diff --git a/src/ImageSharp/Formats/Gif/IGifDecoderOptions.cs b/src/ImageSharp/Formats/Gif/IGifDecoderOptions.cs deleted file mode 100644 index 729bf1d111..0000000000 --- a/src/ImageSharp/Formats/Gif/IGifDecoderOptions.cs +++ /dev/null @@ -1,20 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Formats -{ - using System.Text; - - /// - /// Encapsulates the options for the . - /// - public interface IGifDecoderOptions : IDecoderOptions - { - /// - /// Gets the encoding that should be used when reading comments. - /// - Encoding TextEncoding { get; } - } -} diff --git a/src/ImageSharp/Formats/Gif/IGifEncoderOptions.cs b/src/ImageSharp/Formats/Gif/IGifEncoderOptions.cs deleted file mode 100644 index c1d6b7ad86..0000000000 --- a/src/ImageSharp/Formats/Gif/IGifEncoderOptions.cs +++ /dev/null @@ -1,38 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Formats -{ - using System.Text; - - using Quantizers; - - /// - /// Encapsulates the options for the . - /// - public interface IGifEncoderOptions : IEncoderOptions - { - /// - /// Gets the encoding that should be used when writing comments. - /// - Encoding TextEncoding { get; } - - /// - /// Gets the quality of output for images. - /// - /// For gifs the value ranges from 1 to 256. - int Quality { get; } - - /// - /// Gets the transparency threshold. - /// - byte Threshold { get; } - - /// - /// Gets the quantizer for reducing the color count. - /// - IQuantizer Quantizer { get; } - } -} diff --git a/src/ImageSharp/Formats/Gif/ImageExtensions.cs b/src/ImageSharp/Formats/Gif/ImageExtensions.cs index d64203f6ce..ea9c9b5047 100644 --- a/src/ImageSharp/Formats/Gif/ImageExtensions.cs +++ b/src/ImageSharp/Formats/Gif/ImageExtensions.cs @@ -39,16 +39,16 @@ namespace ImageSharp /// The pixel format. /// The image this method extends. /// The stream to save the image to. - /// The options for the encoder. + /// The options for the encoder. /// Thrown if the stream is null. /// /// The . /// - public static Image SaveAsGif(this Image source, Stream stream, IGifEncoderOptions options) + public static Image SaveAsGif(this Image source, Stream stream, GifEncoder encoder) where TPixel : struct, IPixel { - GifEncoder encoder = new GifEncoder(); - encoder.Encode(source, stream, options); + encoder = encoder ?? new GifEncoder(); + encoder.Encode(source, stream); return source; } diff --git a/src/ImageSharp/Formats/IDecoderOptions.cs b/src/ImageSharp/Formats/IDecoderOptions.cs deleted file mode 100644 index cdfd90d5e2..0000000000 --- a/src/ImageSharp/Formats/IDecoderOptions.cs +++ /dev/null @@ -1,18 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - /// - /// Encapsulates the shared decoder options. - /// - public interface IDecoderOptions - { - /// - /// Gets a value indicating whether the metadata should be ignored when the image is being decoded. - /// - bool IgnoreMetadata { get; } - } -} diff --git a/src/ImageSharp/Formats/IEncoderOptions.cs b/src/ImageSharp/Formats/IEncoderOptions.cs deleted file mode 100644 index 0fd3d1c438..0000000000 --- a/src/ImageSharp/Formats/IEncoderOptions.cs +++ /dev/null @@ -1,18 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - /// - /// Encapsulates the shared encoder options. - /// - public interface IEncoderOptions - { - /// - /// Gets a value indicating whether the metadata should be ignored when the image is being encoded. - /// - bool IgnoreMetadata { get; } - } -} diff --git a/src/ImageSharp/Formats/IImageDecoder.cs b/src/ImageSharp/Formats/IImageDecoder.cs index 4fd25df13e..ff655d718d 100644 --- a/src/ImageSharp/Formats/IImageDecoder.cs +++ b/src/ImageSharp/Formats/IImageDecoder.cs @@ -6,6 +6,7 @@ namespace ImageSharp.Formats { using System; + using System.Collections.Generic; using System.IO; using ImageSharp.PixelFormats; @@ -15,15 +16,40 @@ namespace ImageSharp.Formats /// public interface IImageDecoder { + /// + /// Gets the collection of mime types that this decoder supports decoding on. + /// + IEnumerable MimeTypes { get; } + + /// + /// Gets the collection of file extensionsthis decoder supports decoding. + /// + IEnumerable FileExtensions { get; } + + /// + /// Gets the size of the header for this image type. + /// + /// The size of the header. + int HeaderSize { get; } + + /// + /// Returns a value indicating whether the supports the specified + /// file header. + /// + /// The containing the file header. + /// + /// True if the decoder supports the file header; otherwise, false. + /// + bool IsSupportedFileFormat(Span header); + /// /// Decodes the image from the specified stream to the . /// /// The pixel format. /// The configuration for the image. /// The containing image data. - /// The options for the decoder. /// The decoded image - Image Decode(Configuration configuration, Stream stream, IDecoderOptions options) + Image Decode(Configuration configuration, Stream stream) where TPixel : struct, IPixel; } } diff --git a/src/ImageSharp/Formats/IImageEncoder.cs b/src/ImageSharp/Formats/IImageEncoder.cs index a28511c173..465f8eec57 100644 --- a/src/ImageSharp/Formats/IImageEncoder.cs +++ b/src/ImageSharp/Formats/IImageEncoder.cs @@ -6,6 +6,7 @@ namespace ImageSharp.Formats { using System; + using System.Collections.Generic; using System.IO; using ImageSharp.PixelFormats; @@ -15,14 +16,23 @@ namespace ImageSharp.Formats /// public interface IImageEncoder { + /// + /// Gets the collection of mime types that this decoder supports encoding for. + /// + IEnumerable MimeTypes { get; } + + /// + /// Gets the collection of file extensionsthis decoder supports encoding for. + /// + IEnumerable FileExtensions { get; } + /// /// Encodes the image to the specified stream from the . /// /// The pixel format. /// The to encode from. /// The to encode the image data to. - /// The options for the encoder. - void Encode(Image image, Stream stream, IEncoderOptions options) + void Encode(Image image, Stream stream) where TPixel : struct, IPixel; } } diff --git a/src/ImageSharp/Formats/IImageFormat.cs b/src/ImageSharp/Formats/IImageFormat.cs deleted file mode 100644 index de2e400fa0..0000000000 --- a/src/ImageSharp/Formats/IImageFormat.cs +++ /dev/null @@ -1,60 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Formats -{ - using System.Collections.Generic; - - /// - /// Encapsulates a supported image format, providing means to encode and decode an image. - /// Individual formats implements in this interface must be registered in the - /// - public interface IImageFormat - { - /// - /// Gets the standard identifier used on the Internet to indicate the type of data that a file contains. - /// - string MimeType { get; } - - /// - /// Gets the default file extension for this format. - /// - string Extension { get; } - - /// - /// Gets the supported file extensions for this format. - /// - /// - /// The supported file extension. - /// - IEnumerable SupportedExtensions { get; } - - /// - /// Gets the image encoder for encoding an image from a stream. - /// - IImageEncoder Encoder { get; } - - /// - /// Gets the image decoder for decoding an image from a stream. - /// - IImageDecoder Decoder { get; } - - /// - /// Gets the size of the header for this image type. - /// - /// The size of the header. - int HeaderSize { get; } - - /// - /// Returns a value indicating whether the supports the specified - /// file header. - /// - /// The containing the file header. - /// - /// True if the decoder supports the file header; otherwise, false. - /// - bool IsSupportedFileFormat(byte[] header); - } -} diff --git a/src/ImageSharp/Formats/Jpeg/IJpegEncoderOptions.cs b/src/ImageSharp/Formats/Jpeg/IJpegEncoderOptions.cs deleted file mode 100644 index a545179653..0000000000 --- a/src/ImageSharp/Formats/Jpeg/IJpegEncoderOptions.cs +++ /dev/null @@ -1,26 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Formats -{ - /// - /// Encapsulates the options for the . - /// - public interface IJpegEncoderOptions : IEncoderOptions - { - /// - /// Gets the quality, that will be used to encode the image. Quality - /// index must be between 0 and 100 (compression from max to min). - /// - /// The quality of the jpg image from 0 to 100. - int Quality { get; } - - /// - /// Gets the subsample ration, that will be used to encode the image. - /// - /// The subsample ratio of the jpg image. - JpegSubsample? Subsample { get; } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/ImageExtensions.cs b/src/ImageSharp/Formats/Jpeg/ImageExtensions.cs index 420af6b742..8fbf9e5a74 100644 --- a/src/ImageSharp/Formats/Jpeg/ImageExtensions.cs +++ b/src/ImageSharp/Formats/Jpeg/ImageExtensions.cs @@ -39,16 +39,16 @@ namespace ImageSharp /// The pixel format. /// The image this method extends. /// The stream to save the image to. - /// The options for the encoder. + /// The options for the encoder. /// Thrown if the stream is null. /// /// The . /// - public static Image SaveAsJpeg(this Image source, Stream stream, IJpegEncoderOptions options) + public static Image SaveAsJpeg(this Image source, Stream stream, JpegEncoder encoder) where TPixel : struct, IPixel { - JpegEncoder encoder = new JpegEncoder(); - encoder.Encode(source, stream, options); + encoder = encoder ?? new JpegEncoder(); + encoder.Encode(source, stream); return source; } diff --git a/src/ImageSharp/Formats/Jpeg/JpegConstants.cs b/src/ImageSharp/Formats/Jpeg/JpegConstants.cs index dcda39842e..9598136119 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegConstants.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegConstants.cs @@ -5,6 +5,8 @@ namespace ImageSharp.Formats { + using System.Collections.Generic; + /// /// Defines jpeg constants defined in the specification. /// @@ -15,6 +17,16 @@ namespace ImageSharp.Formats /// public const ushort MaxLength = 65535; + /// + /// The list of mimetypes that equate to a jpeg + /// + public static readonly IEnumerable MimeTypes = new[] { "image/jpeg", "image/pjpeg" }; + + /// + /// The list of mimetypes that equate to a jpeg + /// + public static readonly IEnumerable FileExtensions = new[] { "jpg", "jpeg", "jfif" }; + /// /// Represents high detail chroma horizontal subsampling. /// diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs index 56d025504d..09575a12e4 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs @@ -6,6 +6,7 @@ namespace ImageSharp.Formats { using System; + using System.Collections.Generic; using System.IO; using ImageSharp.PixelFormats; @@ -15,16 +16,87 @@ namespace ImageSharp.Formats /// public class JpegDecoder : IImageDecoder { + /// + /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded. + /// + public bool IgnoreMetadata { get; set; } + + /// + public IEnumerable MimeTypes => JpegConstants.MimeTypes; + + /// + public IEnumerable FileExtensions => JpegConstants.FileExtensions; + + /// + public int HeaderSize => 11; + + /// + public bool IsSupportedFileFormat(Span header) + { + return header.Length >= this.HeaderSize && + (IsJfif(header) || IsExif(header) || IsJpeg(header)); + } + /// - public Image Decode(Configuration configuration, Stream stream, IDecoderOptions options) + public Image Decode(Configuration configuration, Stream stream) where TPixel : struct, IPixel { Guard.NotNull(stream, "stream"); - using (JpegDecoderCore decoder = new JpegDecoderCore(options, configuration)) + using (JpegDecoderCore decoder = new JpegDecoderCore(configuration)) { + decoder.IgnoreMetadata = this.IgnoreMetadata; return decoder.Decode(stream); } } + + /// + /// Returns a value indicating whether the given bytes identify Jfif data. + /// + /// The bytes representing the file header. + /// The + private static bool IsJfif(Span header) + { + bool isJfif = + header[6] == 0x4A && // J + header[7] == 0x46 && // F + header[8] == 0x49 && // I + header[9] == 0x46 && // F + header[10] == 0x00; + + return isJfif; + } + + /// + /// Returns a value indicating whether the given bytes identify EXIF data. + /// + /// The bytes representing the file header. + /// The + private static bool IsExif(Span header) + { + bool isExif = + header[6] == 0x45 && // E + header[7] == 0x78 && // X + header[8] == 0x69 && // I + header[9] == 0x66 && // F + header[10] == 0x00; + + return isExif; + } + + /// + /// Returns a value indicating whether the given bytes identify Jpeg data. + /// This is a last chance resort for jpegs that contain ICC information. + /// + /// The bytes representing the file header. + /// The + private static bool IsJpeg(Span header) + { + bool isJpg = + header[0] == 0xFF && // 255 + header[1] == 0xD8; // 216 + + return isJpg; + } } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 9716843719..f6456620ef 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -45,11 +45,6 @@ namespace ImageSharp.Formats /// private static YCbCrToRgbTables yCbCrToRgbTables = YCbCrToRgbTables.Create(); - /// - /// The decoder options. - /// - private readonly IDecoderOptions options; - /// /// The global configuration /// @@ -103,12 +98,10 @@ namespace ImageSharp.Formats /// /// Initializes a new instance of the class. /// - /// The decoder options. /// The configuration. - public JpegDecoderCore(IDecoderOptions options, Configuration configuration) + public JpegDecoderCore(Configuration configuration) { this.configuration = configuration ?? Configuration.Default; - this.options = options ?? new DecoderOptions(); this.HuffmanTrees = HuffmanTree.CreateHuffmanTrees(); this.QuantizationTables = new Block8x8F[MaxTq + 1]; this.Temp = new byte[2 * Block8x8F.ScalarCount]; @@ -190,6 +183,11 @@ namespace ImageSharp.Formats /// public int TotalMCUCount => this.MCUCountX * this.MCUCountY; + /// + /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded. + /// + public bool IgnoreMetadata { get; internal set; } + /// /// Decodes the image from the specified and sets /// the data to image. @@ -938,7 +936,7 @@ namespace ImageSharp.Formats /// The image. private void ProcessApp1Marker(int remaining, ImageMetaData metadata) { - if (remaining < 6 || this.options.IgnoreMetadata) + if (remaining < 6 || this.IgnoreMetadata) { this.InputProcessor.Skip(remaining); return; @@ -968,7 +966,7 @@ namespace ImageSharp.Formats { // Length is 14 though we only need to check 12. const int Icclength = 14; - if (remaining < Icclength || this.options.IgnoreMetadata) + if (remaining < Icclength || this.IgnoreMetadata) { this.InputProcessor.Skip(remaining); return; diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs index 152fd2c64c..f76df0585c 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs @@ -5,6 +5,8 @@ namespace ImageSharp.Formats { + using System; + using System.Collections.Generic; using System.IO; using ImageSharp.PixelFormats; @@ -14,14 +16,29 @@ namespace ImageSharp.Formats /// public class JpegEncoder : IImageEncoder { + /// + /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded. + /// + public bool IgnoreMetadata { get; set; } + + /// + /// Gets or sets the quality, that will be used to encode the image. Quality + /// index must be between 0 and 100 (compression from max to min). + /// + /// The quality of the jpg image from 0 to 100. + public int Quality { get; set; } + + /// + /// Gets or sets the subsample ration, that will be used to encode the image. + /// + /// The subsample ratio of the jpg image. + public JpegSubsample? Subsample { get; set; } + /// - public void Encode(Image image, Stream stream, IEncoderOptions options) - where TPixel : struct, IPixel - { - IJpegEncoderOptions gifOptions = JpegEncoderOptions.Create(options); + public IEnumerable MimeTypes => JpegConstants.MimeTypes; - this.Encode(image, stream, gifOptions); - } + /// + public IEnumerable FileExtensions => JpegConstants.FileExtensions; /// /// Encodes the image to the specified stream from the . @@ -29,12 +46,23 @@ namespace ImageSharp.Formats /// The pixel format. /// The to encode from. /// The to encode the image data to. - /// The options for the encoder. - public void Encode(Image image, Stream stream, IJpegEncoderOptions options) - where TPixel : struct, IPixel + public void Encode(Image image, Stream stream) + where TPixel : struct, IPixel { - JpegEncoderCore encode = new JpegEncoderCore(options); - encode.Encode(image, stream); + JpegEncoderCore encoder = new JpegEncoderCore(); + + var quality = this.Quality; + if (quality == 0) + { + quality = 75; + } + + encoder.Quality = quality; + encoder.Subsample = this.Subsample ?? (quality >= 91 ? JpegSubsample.Ratio444 : JpegSubsample.Ratio420); + + encoder.IgnoreMetadata = this.IgnoreMetadata; + + encoder.Encode(image, stream); } } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs index b65a56e73d..168e473111 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs @@ -124,11 +124,6 @@ namespace ImageSharp.Formats /// private readonly byte[] huffmanBuffer = new byte[179]; - /// - /// The options for the encoder. - /// - private readonly IJpegEncoderOptions options; - /// /// The accumulated bits to write to the stream. /// @@ -154,20 +149,30 @@ namespace ImageSharp.Formats /// private Stream outputStream; - /// - /// The subsampling method to use. - /// - private JpegSubsample subsample; - /// /// Initializes a new instance of the class. /// - /// The options for the encoder. - public JpegEncoderCore(IJpegEncoderOptions options) + public JpegEncoderCore() { - this.options = options ?? new JpegEncoderOptions(); } + /// + /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded. + /// + public bool IgnoreMetadata { get; internal set; } = false; + + /// + /// Gets or sets the quality, that will be used to encode the image. Quality + /// index must be between 0 and 100 (compression from max to min). + /// + /// The quality of the jpg image from 0 to 100. + public int Quality { get; internal set; } + + /// + /// Gets or sets the subsampling method to use. + /// + public JpegSubsample? Subsample { get; internal set; } + /// /// Encode writes the image to the jpeg baseline format with the given options. /// @@ -186,21 +191,13 @@ namespace ImageSharp.Formats throw new ImageFormatException($"Image is too large to encode at {image.Width}x{image.Height}."); } - // Ensure that quality can be set but has a fallback. - int quality = this.options.Quality > 0 ? this.options.Quality : image.MetaData.Quality; - if (quality == 0) - { - quality = 75; - } - - quality = quality.Clamp(1, 100); - this.outputStream = stream; - this.subsample = this.options.Subsample ?? (quality >= 91 ? JpegSubsample.Ratio444 : JpegSubsample.Ratio420); + + int quality = this.Quality.Clamp(1, 100); // Convert from a quality rating to a scaling factor. int scale; - if (quality < 50) + if (this.Quality < 50) { scale = 5000 / quality; } @@ -788,7 +785,7 @@ namespace ImageSharp.Formats private void WriteProfiles(Image image) where TPixel : struct, IPixel { - if (this.options.IgnoreMetadata) + if (this.IgnoreMetadata) { return; } @@ -810,7 +807,7 @@ namespace ImageSharp.Formats byte[] subsamples = { 0x22, 0x11, 0x11 }; byte[] chroma = { 0x00, 0x01, 0x01 }; - switch (this.subsample) + switch (this.Subsample) { case JpegSubsample.Ratio444: subsamples = new byte[] { 0x11, 0x11, 0x11 }; @@ -866,7 +863,7 @@ namespace ImageSharp.Formats // TODO: We should allow grayscale writing. this.outputStream.Write(SosHeaderYCbCr, 0, SosHeaderYCbCr.Length); - switch (this.subsample) + switch (this.Subsample) { case JpegSubsample.Ratio444: this.Encode444(pixels); diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderOptions.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderOptions.cs deleted file mode 100644 index 73e483164c..0000000000 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderOptions.cs +++ /dev/null @@ -1,56 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Formats -{ - /// - /// Encapsulates the options for the . - /// - public sealed class JpegEncoderOptions : EncoderOptions, IJpegEncoderOptions - { - /// - /// Initializes a new instance of the class. - /// - public JpegEncoderOptions() - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The options for the encoder. - private JpegEncoderOptions(IEncoderOptions options) - : base(options) - { - } - - /// - /// Gets or sets the quality, that will be used to encode the image. Quality - /// index must be between 0 and 100 (compression from max to min). - /// - /// - /// If the quality is less than or equal to 90, the subsampling ratio will switch to - /// - /// The quality of the jpg image from 0 to 100. - public int Quality { get; set; } - - /// - /// Gets or sets the subsample ration, that will be used to encode the image. - /// - /// The subsample ratio of the jpg image. - public JpegSubsample? Subsample { get; set; } - - /// - /// Converts the options to a instance with a - /// cast or by creating a new instance with the specfied options. - /// - /// The options for the encoder. - /// The options for the . - internal static IJpegEncoderOptions Create(IEncoderOptions options) - { - return options as IJpegEncoderOptions ?? new JpegEncoderOptions(options); - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/JpegFormat.cs b/src/ImageSharp/Formats/Jpeg/JpegFormat.cs deleted file mode 100644 index b93c6ae690..0000000000 --- a/src/ImageSharp/Formats/Jpeg/JpegFormat.cs +++ /dev/null @@ -1,89 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Formats -{ - using System.Collections.Generic; - - /// - /// Encapsulates the means to encode and decode jpeg images. - /// - public class JpegFormat : IImageFormat - { - /// - public string MimeType => "image/jpeg"; - - /// - public string Extension => "jpg"; - - /// - public IEnumerable SupportedExtensions => new string[] { "jpg", "jpeg", "jfif" }; - - /// - public IImageDecoder Decoder => new JpegDecoder(); - - /// - public IImageEncoder Encoder => new JpegEncoder(); - - /// - public int HeaderSize => 11; - - /// - public bool IsSupportedFileFormat(byte[] header) - { - return header.Length >= this.HeaderSize && - (IsJfif(header) || IsExif(header) || IsJpeg(header)); - } - - /// - /// Returns a value indicating whether the given bytes identify Jfif data. - /// - /// The bytes representing the file header. - /// The - private static bool IsJfif(byte[] header) - { - bool isJfif = - header[6] == 0x4A && // J - header[7] == 0x46 && // F - header[8] == 0x49 && // I - header[9] == 0x46 && // F - header[10] == 0x00; - - return isJfif; - } - - /// - /// Returns a value indicating whether the given bytes identify EXIF data. - /// - /// The bytes representing the file header. - /// The - private static bool IsExif(byte[] header) - { - bool isExif = - header[6] == 0x45 && // E - header[7] == 0x78 && // X - header[8] == 0x69 && // I - header[9] == 0x66 && // F - header[10] == 0x00; - - return isExif; - } - - /// - /// Returns a value indicating whether the given bytes identify Jpeg data. - /// This is a last chance resort for jpegs that contain ICC information. - /// - /// The bytes representing the file header. - /// The - private static bool IsJpeg(byte[] header) - { - bool isJpg = - header[0] == 0xFF && // 255 - header[1] == 0xD8; // 216 - - return isJpg; - } - } -} diff --git a/src/ImageSharp/Formats/Png/IPngDecoderOptions.cs b/src/ImageSharp/Formats/Png/IPngDecoderOptions.cs deleted file mode 100644 index cc6d194bfc..0000000000 --- a/src/ImageSharp/Formats/Png/IPngDecoderOptions.cs +++ /dev/null @@ -1,20 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Formats -{ - using System.Text; - - /// - /// Encapsulates the options for the . - /// - public interface IPngDecoderOptions : IDecoderOptions - { - /// - /// Gets the encoding that should be used when reading text chunks. - /// - Encoding TextEncoding { get; } - } -} diff --git a/src/ImageSharp/Formats/Png/IPngEncoderOptions.cs b/src/ImageSharp/Formats/Png/IPngEncoderOptions.cs deleted file mode 100644 index 0008080d3f..0000000000 --- a/src/ImageSharp/Formats/Png/IPngEncoderOptions.cs +++ /dev/null @@ -1,54 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Formats -{ - using Quantizers; - - /// - /// Encapsulates the options for the . - /// - public interface IPngEncoderOptions : IEncoderOptions - { - /// - /// Gets the quality of output for images. - /// - int Quality { get; } - - /// - /// Gets the png color type - /// - PngColorType PngColorType { get; } - - /// - /// Gets the compression level 1-9. - /// - int CompressionLevel { get; } - - /// - /// Gets the gamma value, that will be written - /// the the stream, when the property - /// is set to true. - /// - /// The gamma value of the image. - float Gamma { get; } - - /// - /// Gets quantizer for reducing the color count. - /// - IQuantizer Quantizer { get; } - - /// - /// Gets the transparency threshold. - /// - byte Threshold { get; } - - /// - /// Gets a value indicating whether this instance should write - /// gamma information to the stream. - /// - bool WriteGamma { get; } - } -} diff --git a/src/ImageSharp/Formats/Png/ImageExtensions.cs b/src/ImageSharp/Formats/Png/ImageExtensions.cs index 44f242b3f8..c817385760 100644 --- a/src/ImageSharp/Formats/Png/ImageExtensions.cs +++ b/src/ImageSharp/Formats/Png/ImageExtensions.cs @@ -38,16 +38,16 @@ namespace ImageSharp /// The pixel format. /// The image this method extends. /// The stream to save the image to. - /// The options for the encoder. + /// The options for the encoder. /// Thrown if the stream is null. /// /// The . /// - public static Image SaveAsPng(this Image source, Stream stream, IPngEncoderOptions options) + public static Image SaveAsPng(this Image source, Stream stream, PngEncoder encoder) where TPixel : struct, IPixel { - PngEncoder encoder = new PngEncoder(); - encoder.Encode(source, stream, options); + encoder = encoder ?? new PngEncoder(); + encoder.Encode(source, stream); return source; } diff --git a/src/ImageSharp/Formats/Png/PngConstants.cs b/src/ImageSharp/Formats/Png/PngConstants.cs new file mode 100644 index 0000000000..b44ab76638 --- /dev/null +++ b/src/ImageSharp/Formats/Png/PngConstants.cs @@ -0,0 +1,30 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// +namespace ImageSharp.Formats +{ + using System.Collections.Generic; + using System.Text; + + /// + /// Defines png constants defined in the specification. + /// + internal static class PngConstants + { + /// + /// The default encoding for text metadata + /// + public static readonly Encoding DefaultEncoding = Encoding.GetEncoding("ASCII"); + + /// + /// The list of mimetypes that equate to a jpeg + /// + public static readonly IEnumerable MimeTypes = new[] { "image/png" }; + + /// + /// The list of mimetypes that equate to a jpeg + /// + public static readonly IEnumerable FileExtensions = new[] { "png" }; + } +} diff --git a/src/ImageSharp/Formats/Png/PngDecoder.cs b/src/ImageSharp/Formats/Png/PngDecoder.cs index 3a34147e2c..da00ff9069 100644 --- a/src/ImageSharp/Formats/Png/PngDecoder.cs +++ b/src/ImageSharp/Formats/Png/PngDecoder.cs @@ -6,8 +6,9 @@ namespace ImageSharp.Formats { using System; + using System.Collections.Generic; using System.IO; - + using System.Text; using ImageSharp.PixelFormats; /// @@ -32,14 +33,37 @@ namespace ImageSharp.Formats /// public class PngDecoder : IImageDecoder { + /// + /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded. + /// + public bool IgnoreMetadata { get; set; } + /// - public Image Decode(Configuration configuration, Stream stream, IDecoderOptions options) + public IEnumerable MimeTypes => PngConstants.MimeTypes; - where TPixel : struct, IPixel - { - IPngDecoderOptions pngOptions = PngDecoderOptions.Create(options); + /// + public IEnumerable FileExtensions => PngConstants.FileExtensions; - return this.Decode(configuration, stream, pngOptions); + /// + public int HeaderSize => 8; + + /// + /// Gets or sets the encoding that should be used when reading text chunks. + /// + public Encoding TextEncoding { get; set; } = PngConstants.DefaultEncoding; + + /// + public bool IsSupportedFileFormat(Span header) + { + return header.Length >= this.HeaderSize && + header[0] == 0x89 && + header[1] == 0x50 && // P + header[2] == 0x4E && // N + header[3] == 0x47 && // G + header[4] == 0x0D && // CR + header[5] == 0x0A && // LF + header[6] == 0x1A && // EOF + header[7] == 0x0A; // LF } /// @@ -48,12 +72,13 @@ namespace ImageSharp.Formats /// The pixel format. /// The configuration for the image. /// The containing image data. - /// The options for the decoder. /// The decoded image. - public Image Decode(Configuration configuration, Stream stream, IPngDecoderOptions options) + public Image Decode(Configuration configuration, Stream stream) where TPixel : struct, IPixel { - return new PngDecoderCore(options, configuration).Decode(stream); + var decoder = new PngDecoderCore(configuration, this.TextEncoding); + decoder.IgnoreMetadata = this.IgnoreMetadata; + return decoder.Decode(stream); } } } diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index 2ff6a43085..b1b98eca57 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -11,7 +11,7 @@ namespace ImageSharp.Formats using System.IO; using System.Linq; using System.Runtime.CompilerServices; - + using System.Text; using ImageSharp.Memory; using ImageSharp.PixelFormats; @@ -74,11 +74,6 @@ namespace ImageSharp.Formats /// private readonly char[] chars = new char[4]; - /// - /// The decoder options. - /// - private readonly IPngDecoderOptions options; - /// /// Reusable crc for validating chunks. /// @@ -154,21 +149,31 @@ namespace ImageSharp.Formats /// private int currentRowBytesRead; + /// + /// Gets or sets the png color type + /// + private PngColorType pngColorType; + /// /// Initializes a new instance of the class. /// - /// The decoder options. /// The configuration. - public PngDecoderCore(IPngDecoderOptions options, Configuration configuration) + /// The text encoding. + public PngDecoderCore(Configuration configuration, Encoding encoding) { this.configuration = configuration ?? Configuration.Default; - this.options = options ?? new PngDecoderOptions(); + this.TextEncoding = encoding ?? PngConstants.DefaultEncoding; } /// - /// Gets or sets the png color type + /// Gets the encoding to use + /// + public Encoding TextEncoding { get; private set; } + + /// + /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded. /// - public PngColorType PngColorType { get; set; } + public bool IgnoreMetadata { get; internal set; } /// /// Decodes the stream to the image. @@ -221,7 +226,6 @@ namespace ImageSharp.Formats byte[] pal = new byte[currentChunk.Length]; Buffer.BlockCopy(currentChunk.Data, 0, pal, 0, currentChunk.Length); this.palette = pal; - metadata.Quality = pal.Length / 3; break; case PngChunkTypes.PaletteAlpha: byte[] alpha = new byte[currentChunk.Length]; @@ -344,7 +348,7 @@ namespace ImageSharp.Formats /// The private int CalculateBytesPerPixel() { - switch (this.PngColorType) + switch (this.pngColorType) { case PngColorType.Grayscale: return 1; @@ -572,7 +576,7 @@ namespace ImageSharp.Formats Span rowSpan = pixels.GetRowSpan(this.currentRow); var scanlineBuffer = new Span(defilteredScanline, 1); - switch (this.PngColorType) + switch (this.pngColorType) { case PngColorType.Grayscale: int factor = 255 / ((int)Math.Pow(2, this.header.BitDepth) - 1); @@ -731,7 +735,7 @@ namespace ImageSharp.Formats { var color = default(TPixel); - switch (this.PngColorType) + switch (this.pngColorType) { case PngColorType.Grayscale: int factor = 255 / ((int)Math.Pow(2, this.header.BitDepth) - 1); @@ -896,7 +900,7 @@ namespace ImageSharp.Formats /// The maximum length to read. private void ReadTextChunk(ImageMetaData metadata, byte[] data, int length) { - if (this.options.IgnoreMetadata) + if (this.IgnoreMetadata) { return; } @@ -912,8 +916,8 @@ namespace ImageSharp.Formats } } - string name = this.options.TextEncoding.GetString(data, 0, zeroIndex); - string value = this.options.TextEncoding.GetString(data, zeroIndex + 1, length - zeroIndex - 1); + string name = this.TextEncoding.GetString(data, 0, zeroIndex); + string value = this.TextEncoding.GetString(data, zeroIndex + 1, length - zeroIndex - 1); metadata.Properties.Add(new ImageProperty(name, value)); } @@ -967,7 +971,7 @@ namespace ImageSharp.Formats throw new NotSupportedException("The png specification only defines 'None' and 'Adam7' as interlaced methods."); } - this.PngColorType = this.header.ColorType; + this.pngColorType = this.header.ColorType; } /// diff --git a/src/ImageSharp/Formats/Png/PngDecoderOptions.cs b/src/ImageSharp/Formats/Png/PngDecoderOptions.cs deleted file mode 100644 index e8990ec456..0000000000 --- a/src/ImageSharp/Formats/Png/PngDecoderOptions.cs +++ /dev/null @@ -1,49 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Formats -{ - using System.Text; - - /// - /// Encapsulates the options for the . - /// - public sealed class PngDecoderOptions : DecoderOptions, IPngDecoderOptions - { - private static readonly Encoding DefaultEncoding = Encoding.GetEncoding("ASCII"); - - /// - /// Initializes a new instance of the class. - /// - public PngDecoderOptions() - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The options for the decoder. - private PngDecoderOptions(IDecoderOptions options) - : base(options) - { - } - - /// - /// Gets or sets the encoding that should be used when reading text chunks. - /// - public Encoding TextEncoding { get; set; } = DefaultEncoding; - - /// - /// Converts the options to a instance with a cast - /// or by creating a new instance with the specfied options. - /// - /// The options for the decoder. - /// The options for the . - internal static IPngDecoderOptions Create(IDecoderOptions options) - { - return options as IPngDecoderOptions ?? new PngDecoderOptions(options); - } - } -} diff --git a/src/ImageSharp/Formats/Png/PngEncoder.cs b/src/ImageSharp/Formats/Png/PngEncoder.cs index f89b624f78..023e465e90 100644 --- a/src/ImageSharp/Formats/Png/PngEncoder.cs +++ b/src/ImageSharp/Formats/Png/PngEncoder.cs @@ -5,9 +5,11 @@ namespace ImageSharp.Formats { + using System.Collections.Generic; using System.IO; using ImageSharp.PixelFormats; + using ImageSharp.Quantizers; /// /// Image encoder for writing image data to a stream in png format. @@ -15,13 +17,55 @@ namespace ImageSharp.Formats public class PngEncoder : IImageEncoder { /// - public void Encode(Image image, Stream stream, IEncoderOptions options) - where TPixel : struct, IPixel - { - IPngEncoderOptions pngOptions = PngEncoderOptions.Create(options); + public IEnumerable MimeTypes => PngConstants.MimeTypes; - this.Encode(image, stream, pngOptions); - } + /// + public IEnumerable FileExtensions => PngConstants.FileExtensions; + + /// + /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded. + /// + public bool IgnoreMetadata { get; set; } + + /// + /// Gets or sets the quality of output for images. + /// + public int Quality { get; set; } + + /// + /// Gets or sets the png color type + /// + public PngColorType PngColorType { get; set; } = PngColorType.RgbWithAlpha; + + /// + /// Gets or sets the compression level 1-9. + /// Defaults to 6. + /// + public int CompressionLevel { get; set; } = 6; + + /// + /// Gets or sets the gamma value, that will be written + /// the the stream, when the property + /// is set to true. The default value is 2.2F. + /// + /// The gamma value of the image. + public float Gamma { get; set; } = 2.2F; + + /// + /// Gets or sets quantizer for reducing the color count. + /// + public IQuantizer Quantizer { get; set; } + + /// + /// Gets or sets the transparency threshold. + /// + public byte Threshold { get; set; } = 255; + + /// + /// Gets or sets a value indicating whether this instance should write + /// gamma information to the stream. The default value is false. + /// + public bool WriteGamma { get; set; } /// /// Encodes the image to the specified stream from the . @@ -29,12 +73,21 @@ namespace ImageSharp.Formats /// The pixel format. /// The to encode from. /// The to encode the image data to. - /// The options for the encoder. - public void Encode(Image image, Stream stream, IPngEncoderOptions options) + public void Encode(Image image, Stream stream) where TPixel : struct, IPixel { - using (var encode = new PngEncoderCore(options)) + using (var encode = new PngEncoderCore()) { + encode.IgnoreMetadata = this.IgnoreMetadata; + + encode.Quality = this.Quality > 0 ? this.Quality.Clamp(1, int.MaxValue) : int.MaxValue; + encode.PngColorType = this.PngColorType; + encode.CompressionLevel = this.CompressionLevel; + encode.Gamma = this.Gamma; + encode.Quantizer = this.Quantizer; + encode.Threshold = this.Threshold; + encode.WriteGamma = this.WriteGamma; + encode.Encode(image, stream); } } diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index 645df05481..c96d60dbd2 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -43,11 +43,6 @@ namespace ImageSharp.Formats /// private readonly Crc32 crc = new Crc32(); - /// - /// The options for the encoder. - /// - private readonly IPngEncoderOptions options; - /// /// Contains the raw pixel data from an indexed image. /// @@ -113,11 +108,6 @@ namespace ImageSharp.Formats /// private Buffer paeth; - /// - /// The quality of output for images. - /// - private int quality; - /// /// The png color type. /// @@ -131,12 +121,50 @@ namespace ImageSharp.Formats /// /// Initializes a new instance of the class. /// - /// The options for the encoder. - public PngEncoderCore(IPngEncoderOptions options) + public PngEncoderCore() { - this.options = options ?? new PngEncoderOptions(); } + /// + /// Gets or sets a value indicating whether to ignore metadata + /// + public bool IgnoreMetadata { get; internal set; } + + /// + /// Gets or sets the Quality value + /// + public int Quality { get; internal set; } + + /// + /// Gets or sets the Quality value + /// + public PngColorType PngColorType { get; internal set; } + + /// + /// Gets or sets the CompressionLevel value + /// + public int CompressionLevel { get; internal set; } + + /// + /// Gets or sets the Gamma value + /// + public float Gamma { get; internal set; } + + /// + /// Gets or sets the Quantizer value + /// + public IQuantizer Quantizer { get; internal set; } + + /// + /// Gets or sets the Threshold value + /// + public byte Threshold { get; internal set; } + + /// + /// Gets or sets a value indicating whether to Write Gamma + /// + public bool WriteGamma { get; internal set; } + /// /// Encodes the image to the specified stream from the . /// @@ -164,27 +192,23 @@ namespace ImageSharp.Formats stream.Write(this.chunkDataBuffer, 0, 8); - // Ensure that quality can be set but has a fallback. - this.quality = this.options.Quality > 0 ? this.options.Quality : image.MetaData.Quality; - this.quality = this.quality > 0 ? this.quality.Clamp(1, int.MaxValue) : int.MaxValue; - - this.pngColorType = this.options.PngColorType; - this.quantizer = this.options.Quantizer; + this.pngColorType = this.PngColorType; + this.quantizer = this.Quantizer; // Set correct color type if the color count is 256 or less. - if (this.quality <= 256) + if (this.Quality <= 256) { this.pngColorType = PngColorType.Palette; } - if (this.pngColorType == PngColorType.Palette && this.quality > 256) + if (this.pngColorType == PngColorType.Palette && this.Quality > 256) { - this.quality = 256; + this.Quality = 256; } // Set correct bit depth. - this.bitDepth = this.quality <= 256 - ? (byte)ImageMaths.GetBitsNeededForColorDepth(this.quality).Clamp(1, 8) + this.bitDepth = this.Quality <= 256 + ? (byte)ImageMaths.GetBitsNeededForColorDepth(this.Quality).Clamp(1, 8) : (byte)8; // Png only supports in four pixel depths: 1, 2, 4, and 8 bits when using the PLTE chunk @@ -514,7 +538,7 @@ namespace ImageSharp.Formats private QuantizedImage WritePaletteChunk(Stream stream, PngHeader header, ImageBase image) where TPixel : struct, IPixel { - if (this.quality > 256) + if (this.Quality > 256) { return null; } @@ -525,7 +549,7 @@ namespace ImageSharp.Formats } // Quantize the image returning a palette. This boxing is icky. - QuantizedImage quantized = ((IQuantizer)this.quantizer).Quantize(image, this.quality); + QuantizedImage quantized = ((IQuantizer)this.quantizer).Quantize(image, this.Quality); // Grab the palette and write it to the stream. TPixel[] palette = quantized.Palette; @@ -552,7 +576,7 @@ namespace ImageSharp.Formats colorTable[offset + 1] = bytes[1]; colorTable[offset + 2] = bytes[2]; - if (alpha > this.options.Threshold) + if (alpha > this.Threshold) { alpha = 255; } @@ -610,9 +634,9 @@ namespace ImageSharp.Formats /// The containing image data. private void WriteGammaChunk(Stream stream) { - if (this.options.WriteGamma) + if (this.WriteGamma) { - int gammaValue = (int)(this.options.Gamma * 100000F); + int gammaValue = (int)(this.Gamma * 100000F); byte[] size = BitConverter.GetBytes(gammaValue); @@ -655,7 +679,7 @@ namespace ImageSharp.Formats try { memoryStream = new MemoryStream(); - using (var deflateStream = new ZlibDeflateStream(memoryStream, this.options.CompressionLevel)) + using (var deflateStream = new ZlibDeflateStream(memoryStream, this.CompressionLevel)) { for (int y = 0; y < this.height; y++) { diff --git a/src/ImageSharp/Formats/Png/PngEncoderOptions.cs b/src/ImageSharp/Formats/Png/PngEncoderOptions.cs deleted file mode 100644 index 90175c6d65..0000000000 --- a/src/ImageSharp/Formats/Png/PngEncoderOptions.cs +++ /dev/null @@ -1,82 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Formats -{ - using Quantizers; - - /// - /// Encapsulates the options for the . - /// - public sealed class PngEncoderOptions : EncoderOptions, IPngEncoderOptions - { - /// - /// Initializes a new instance of the class. - /// - public PngEncoderOptions() - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The options for the encoder. - private PngEncoderOptions(IEncoderOptions options) - : base(options) - { - } - - /// - /// Gets or sets the quality of output for images. - /// - public int Quality { get; set; } - - /// - /// Gets or sets the png color type - /// - public PngColorType PngColorType { get; set; } = PngColorType.RgbWithAlpha; - - /// - /// Gets or sets the compression level 1-9. - /// Defaults to 6. - /// - public int CompressionLevel { get; set; } = 6; - - /// - /// Gets or sets the gamma value, that will be written - /// the the stream, when the property - /// is set to true. The default value is 2.2F. - /// - /// The gamma value of the image. - public float Gamma { get; set; } = 2.2F; - - /// - /// Gets or sets quantizer for reducing the color count. - /// - public IQuantizer Quantizer { get; set; } - - /// - /// Gets or sets the transparency threshold. - /// - public byte Threshold { get; set; } = 255; - - /// - /// Gets or sets a value indicating whether this instance should write - /// gamma information to the stream. The default value is false. - /// - public bool WriteGamma { get; set; } - - /// - /// Converts the options to a instance with a - /// cast or by creating a new instance with the specfied options. - /// - /// The options for the encoder. - /// The options for the . - internal static IPngEncoderOptions Create(IEncoderOptions options) - { - return options as IPngEncoderOptions ?? new PngEncoderOptions(options); - } - } -} diff --git a/src/ImageSharp/Formats/Png/PngFormat.cs b/src/ImageSharp/Formats/Png/PngFormat.cs deleted file mode 100644 index 0f5a74da04..0000000000 --- a/src/ImageSharp/Formats/Png/PngFormat.cs +++ /dev/null @@ -1,47 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Formats -{ - using System.Collections.Generic; - - /// - /// Encapsulates the means to encode and decode png images. - /// - public class PngFormat : IImageFormat - { - /// - public string MimeType => "image/png"; - - /// - public string Extension => "png"; - - /// - public IEnumerable SupportedExtensions => new string[] { "png" }; - - /// - public IImageDecoder Decoder => new PngDecoder(); - - /// - public IImageEncoder Encoder => new PngEncoder(); - - /// - public int HeaderSize => 8; - - /// - public bool IsSupportedFileFormat(byte[] header) - { - return header.Length >= this.HeaderSize && - header[0] == 0x89 && - header[1] == 0x50 && // P - header[2] == 0x4E && // N - header[3] == 0x47 && // G - header[4] == 0x0D && // CR - header[5] == 0x0A && // LF - header[6] == 0x1A && // EOF - header[7] == 0x0A; // LF - } - } -} diff --git a/src/ImageSharp/Image/IImage.cs b/src/ImageSharp/Image/IImage.cs index 55abdb244f..b9e8e392a3 100644 --- a/src/ImageSharp/Image/IImage.cs +++ b/src/ImageSharp/Image/IImage.cs @@ -12,11 +12,6 @@ namespace ImageSharp /// internal interface IImage : IImageBase { - /// - /// Gets the currently loaded image format. - /// - IImageFormat CurrentImageFormat { get; } - /// /// Gets the meta data of the image. /// diff --git a/src/ImageSharp/Image/Image.Decode.cs b/src/ImageSharp/Image/Image.Decode.cs index c162f17726..2730da8f28 100644 --- a/src/ImageSharp/Image/Image.Decode.cs +++ b/src/ImageSharp/Image/Image.Decode.cs @@ -23,7 +23,7 @@ namespace ImageSharp /// The image stream to read the header from. /// The configuration. /// The image format or null if none found. - private static IImageFormat DiscoverFormat(Stream stream, Configuration config) + private static IImageDecoder DiscoverDecoder(Stream stream, Configuration config) { // This is probably a candidate for making into a public API in the future! int maxHeaderSize = config.MaxHeaderSize; @@ -32,14 +32,14 @@ namespace ImageSharp return null; } - IImageFormat format; + IImageDecoder format; byte[] header = ArrayPool.Shared.Rent(maxHeaderSize); try { long startPosition = stream.Position; stream.Read(header, 0, maxHeaderSize); stream.Position = startPosition; - format = config.ImageFormats.FirstOrDefault(x => x.IsSupportedFileFormat(header)); + format = config.ImageDecoders.LastOrDefault(x => x.IsSupportedFileFormat(header)); // we should use last in case user has registerd a new one with their own settings } finally { @@ -49,28 +49,28 @@ namespace ImageSharp return format; } +#pragma warning disable SA1008 // Opening parenthesis must be spaced correctly /// /// Decodes the image stream to the current image. /// /// The stream. - /// The options for the decoder. /// the configuration. /// The pixel format. /// /// A new . /// - private static Image Decode(Stream stream, IDecoderOptions options, Configuration config) + private static (Image img, IImageDecoder decoder) Decode(Stream stream, Configuration config) +#pragma warning restore SA1008 // Opening parenthesis must be spaced correctly where TPixel : struct, IPixel { - IImageFormat format = DiscoverFormat(stream, config); - if (format == null) + IImageDecoder decoder = DiscoverDecoder(stream, config); + if (decoder == null) { - return null; + return (null, null); } - Image img = format.Decoder.Decode(config, stream, options); - img.CurrentImageFormat = format; - return img; + Image img = decoder.Decode(config, stream); + return (img, decoder); } } } \ No newline at end of file diff --git a/src/ImageSharp/Image/Image.FromBytes.cs b/src/ImageSharp/Image/Image.FromBytes.cs index c7309c4b14..8cc0e8f376 100644 --- a/src/ImageSharp/Image/Image.FromBytes.cs +++ b/src/ImageSharp/Image/Image.FromBytes.cs @@ -20,15 +20,15 @@ namespace ImageSharp /// /// The byte array containing image data. /// A new . - public static Image Load(byte[] data) => Load(null, data, null); + public static Image Load(byte[] data) => Load(null, data); /// /// Create a new instance of the class from the given byte array. /// /// The byte array containing image data. - /// The options for the decoder. + /// the mime type of the decoded image. /// A new . - public static Image Load(byte[] data, IDecoderOptions options) => Load(null, data, options); + public static Image Load(byte[] data, out string mimeType) => Load(null, data, out mimeType); /// /// Create a new instance of the class from the given byte array. @@ -36,33 +36,33 @@ namespace ImageSharp /// The config for the decoder. /// The byte array containing image data. /// A new . - public static Image Load(Configuration config, byte[] data) => Load(config, data, null); + public static Image Load(Configuration config, byte[] data) => Load(config, data); /// - /// Create a new instance of the class from the given byte array. + /// Create a new instance of the class from the given byte array. /// + /// The config for the decoder. /// The byte array containing image data. - /// The decoder. + /// the mime type of the decoded image. /// A new . - public static Image Load(byte[] data, IImageDecoder decoder) => Load(data, decoder, null); + public static Image Load(Configuration config, byte[] data, out string mimeType) => Load(config, data, out mimeType); /// - /// Create a new instance of the class from the given byte array. + /// Create a new instance of the class from the given byte array. /// - /// The configuration options. /// The byte array containing image data. - /// The options for the decoder. + /// The decoder. /// A new . - public static Image Load(Configuration config, byte[] data, IDecoderOptions options) => Load(config, data, options); + public static Image Load(byte[] data, IImageDecoder decoder) => Load(data, decoder); /// - /// Create a new instance of the class from the given byte array. + /// Create a new instance of the class from the given byte array. /// + /// The config for the decoder. /// The byte array containing image data. /// The decoder. - /// The options for the decoder. /// A new . - public static Image Load(byte[] data, IImageDecoder decoder, IDecoderOptions options) => Load(data, decoder, options); + public static Image Load(Configuration config, byte[] data, IImageDecoder decoder) => Load(config, data, decoder); /// /// Create a new instance of the class from the given byte array. @@ -73,79 +73,85 @@ namespace ImageSharp public static Image Load(byte[] data) where TPixel : struct, IPixel { - return Load(null, data, null); + return Load(null, data); } /// /// Create a new instance of the class from the given byte array. /// /// The byte array containing image data. - /// The options for the decoder. + /// the mime type of the decoded image. /// The pixel format. /// A new . - public static Image Load(byte[] data, IDecoderOptions options) + public static Image Load(byte[] data, out string mimeType) where TPixel : struct, IPixel { - return Load(null, data, options); + return Load(null, data, out mimeType); } /// /// Create a new instance of the class from the given byte array. /// - /// The config for the decoder. + /// The configuration options. /// The byte array containing image data. /// The pixel format. /// A new . public static Image Load(Configuration config, byte[] data) where TPixel : struct, IPixel { - return Load(config, data, null); + using (MemoryStream ms = new MemoryStream(data)) + { + return Load(config, ms); + } } /// /// Create a new instance of the class from the given byte array. /// + /// The configuration options. /// The byte array containing image data. - /// The decoder. + /// the mime type of the decoded image. /// The pixel format. /// A new . - public static Image Load(byte[] data, IImageDecoder decoder) + public static Image Load(Configuration config, byte[] data, out string mimeType) where TPixel : struct, IPixel { - return Load(data, decoder, null); + using (MemoryStream ms = new MemoryStream(data)) + { + return Load(config, ms, out mimeType); + } } /// /// Create a new instance of the class from the given byte array. /// - /// The configuration options. /// The byte array containing image data. - /// The options for the decoder. + /// The decoder. /// The pixel format. /// A new . - public static Image Load(Configuration config, byte[] data, IDecoderOptions options) + public static Image Load(byte[] data, IImageDecoder decoder) where TPixel : struct, IPixel { using (MemoryStream ms = new MemoryStream(data)) { - return Load(config, ms, options); + return Load(ms, decoder); } } /// /// Create a new instance of the class from the given byte array. /// + /// The Configuration. /// The byte array containing image data. /// The decoder. - /// The options for the decoder. /// The pixel format. /// A new . - public static Image Load(byte[] data, IImageDecoder decoder, IDecoderOptions options) + public static Image Load(Configuration config, byte[] data, IImageDecoder decoder) where TPixel : struct, IPixel { using (MemoryStream ms = new MemoryStream(data)) { - return Load(ms, decoder, options); + return Load(config, ms, decoder); } } } diff --git a/src/ImageSharp/Image/Image.FromFile.cs b/src/ImageSharp/Image/Image.FromFile.cs index a135c43f5e..9912949217 100644 --- a/src/ImageSharp/Image/Image.FromFile.cs +++ b/src/ImageSharp/Image/Image.FromFile.cs @@ -30,12 +30,12 @@ namespace ImageSharp /// Create a new instance of the class from the given file. /// /// The file path to the image. - /// The options for the decoder. + /// the mime type of the decoded image. /// /// Thrown if the stream is not readable nor seekable. /// - /// A new . - public static Image Load(string path, IDecoderOptions options) => Load(path, options); + /// A new . + public static Image Load(string path, out string mimeType) => Load(path, out mimeType); /// /// Create a new instance of the class from the given file. @@ -51,37 +51,37 @@ namespace ImageSharp /// /// Create a new instance of the class from the given file. /// + /// The config for the decoder. /// The file path to the image. - /// The decoder. + /// the mime type of the decoded image. /// /// Thrown if the stream is not readable nor seekable. /// /// A new . - public static Image Load(string path, IImageDecoder decoder) => Load(path, decoder); + public static Image Load(Configuration config, string path, out string mimeType) => Load(config, path, out mimeType); /// /// Create a new instance of the class from the given file. /// - /// The configuration options. + /// The Configuration. /// The file path to the image. - /// The options for the decoder. + /// The decoder. /// /// Thrown if the stream is not readable nor seekable. /// /// A new . - public static Image Load(Configuration config, string path, IDecoderOptions options) => Load(config, path, options); + public static Image Load(Configuration config, string path, IImageDecoder decoder) => Load(config, path, decoder); /// /// Create a new instance of the class 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. /// /// A new . - public static Image Load(string path, IImageDecoder decoder, IDecoderOptions options) => Load(path, decoder, options); + public static Image Load(string path, IImageDecoder decoder) => Load(path, decoder); /// /// Create a new instance of the class from the given file. @@ -95,29 +95,29 @@ namespace ImageSharp public static Image Load(string path) where TPixel : struct, IPixel { - return Load(null, path, null); + return Load(null, path); } /// /// Create a new instance of the class from the given file. /// /// The file path to the image. - /// The options for the decoder. + /// the mime type of the decoded image. /// /// Thrown if the stream is not readable nor seekable. /// /// The pixel format. /// A new . - public static Image Load(string path, IDecoderOptions options) + public static Image Load(string path, out string mimeType) where TPixel : struct, IPixel { - return Load(null, path, options); + return Load(null, path, out mimeType); } /// /// Create a new instance of the class from the given file. /// - /// The config for the decoder. + /// The configuration options. /// The file path to the image. /// /// Thrown if the stream is not readable nor seekable. @@ -127,64 +127,68 @@ namespace ImageSharp public static Image Load(Configuration config, string path) where TPixel : struct, IPixel { - return Load(config, path, null); + config = config ?? Configuration.Default; + using (Stream s = config.FileSystem.OpenRead(path)) + { + return Load(config, s); + } } /// /// Create a new instance of the class from the given file. /// + /// The configuration options. /// The file path to the image. - /// The decoder. + /// the mime type of the decoded image. /// /// Thrown if the stream is not readable nor seekable. /// /// The pixel format. /// A new . - public static Image Load(string path, IImageDecoder decoder) + public static Image Load(Configuration config, string path, out string mimeType) where TPixel : struct, IPixel { - return Load(path, decoder, null); + config = config ?? Configuration.Default; + using (Stream s = config.FileSystem.OpenRead(path)) + { + return Load(config, s, out mimeType); + } } /// /// Create a new instance of the class from the given file. /// - /// The configuration options. /// The file path to the image. - /// The options for the decoder. + /// The decoder. /// /// Thrown if the stream is not readable nor seekable. /// /// The pixel format. /// A new . - public static Image Load(Configuration config, string path, IDecoderOptions options) + public static Image Load(string path, IImageDecoder decoder) where TPixel : struct, IPixel { - config = config ?? Configuration.Default; - using (Stream s = config.FileSystem.OpenRead(path)) - { - return Load(config, s, options); - } + return Load(null, path, decoder); } /// /// Create a new instance of the class from the given file. /// + /// The Configuration. /// The file path to the image. /// The decoder. - /// The options for the decoder. /// /// Thrown if the stream is not readable nor seekable. /// /// The pixel format. /// A new . - public static Image Load(string path, IImageDecoder decoder, IDecoderOptions options) + public static Image Load(Configuration config, string path, IImageDecoder decoder) where TPixel : struct, IPixel { - Configuration config = Configuration.Default; + config = config ?? Configuration.Default; using (Stream s = config.FileSystem.OpenRead(path)) { - return Load(s, decoder, options); + return Load(config, s, decoder); } } } diff --git a/src/ImageSharp/Image/Image.FromStream.cs b/src/ImageSharp/Image/Image.FromStream.cs index 1bcb5adc9a..79c66af906 100644 --- a/src/ImageSharp/Image/Image.FromStream.cs +++ b/src/ImageSharp/Image/Image.FromStream.cs @@ -7,6 +7,7 @@ namespace ImageSharp { using System; using System.IO; + using System.Linq; using System.Text; using Formats; @@ -21,22 +22,22 @@ namespace ImageSharp /// Create a new instance of the class from the given stream. /// /// The stream containing image information. + /// the mime type of the decoded image. /// /// Thrown if the stream is not readable nor seekable. /// /// A new .> - public static Image Load(Stream stream) => Load(stream); + public static Image Load(Stream stream, out string mimeType) => Load(stream, out mimeType); /// /// Create a new instance of the class from the given stream. /// /// The stream containing image information. - /// The options for the decoder. /// /// Thrown if the stream is not readable nor seekable. /// /// A new .> - public static Image Load(Stream stream, IDecoderOptions options) => Load(stream, options); + public static Image Load(Stream stream) => Load(stream); /// /// Create a new instance of the class from the given stream. @@ -63,14 +64,14 @@ namespace ImageSharp /// /// Create a new instance of the class from the given stream. /// + /// The config for the decoder. /// The stream containing image information. - /// The decoder. - /// The options for the decoder. + /// the mime type of the decoded image. /// /// Thrown if the stream is not readable nor seekable. /// - /// A new .> - public static Image Load(Stream stream, IImageDecoder decoder, IDecoderOptions options) => Load(stream, decoder, options); + /// A new .> + public static Image Load(Configuration config, Stream stream, out string mimeType) => Load(config, stream, out mimeType); /// /// Create a new instance of the class from the given stream. @@ -84,44 +85,45 @@ namespace ImageSharp public static Image Load(Stream stream) where TPixel : struct, IPixel { - return Load(null, stream, null); + return Load(null, stream); } /// /// Create a new instance of the class from the given stream. /// /// The stream containing image information. - /// The options for the decoder. + /// the mime type of the decoded image. /// /// Thrown if the stream is not readable nor seekable. /// /// The pixel format. /// A new .> - public static Image Load(Stream stream, IDecoderOptions options) + public static Image Load(Stream stream, out string mimeType) where TPixel : struct, IPixel { - return Load(null, stream, options); + return Load(null, stream, out mimeType); } /// /// Create a new instance of the class from the given stream. /// - /// The config for the decoder. /// The stream containing image information. + /// The decoder. /// /// Thrown if the stream is not readable nor seekable. /// /// The pixel format. /// A new .> - public static Image Load(Configuration config, Stream stream) + public static Image Load(Stream stream, IImageDecoder decoder) where TPixel : struct, IPixel { - return Load(config, stream, null); + return WithSeekableStream(stream, s => decoder.Decode(Configuration.Default, s)); } /// /// Create a new instance of the class from the given stream. /// + /// The Configuration. /// The stream containing image information. /// The decoder. /// @@ -129,27 +131,26 @@ namespace ImageSharp /// /// The pixel format. /// A new .> - public static Image Load(Stream stream, IImageDecoder decoder) + public static Image Load(Configuration config, Stream stream, IImageDecoder decoder) where TPixel : struct, IPixel { - return Load(stream, decoder, null); + return WithSeekableStream(stream, s => decoder.Decode(config, s)); } /// /// Create a new instance of the class from the given stream. /// + /// The configuration options. /// The stream containing image information. - /// The decoder. - /// The options for the decoder. /// /// Thrown if the stream is not readable nor seekable. /// /// The pixel format. /// A new .> - public static Image Load(Stream stream, IImageDecoder decoder, IDecoderOptions options) + public static Image Load(Configuration config, Stream stream) where TPixel : struct, IPixel { - return WithSeekableStream(stream, s => decoder.Decode(Configuration.Default, s, options)); + return Load(config, stream, out var _); } /// @@ -157,27 +158,30 @@ namespace ImageSharp /// /// The configuration options. /// The stream containing image information. - /// The options for the decoder. + /// the mime type of the decoded image. /// /// Thrown if the stream is not readable nor seekable. /// /// The pixel format. /// A new .> - public static Image Load(Configuration config, Stream stream, IDecoderOptions options) - where TPixel : struct, IPixel + public static Image Load(Configuration config, Stream stream, out string mimeType) + where TPixel : struct, IPixel { config = config ?? Configuration.Default; - Image img = WithSeekableStream(stream, s => Decode(s, options, config)); + mimeType = null; + (Image img, IImageDecoder decoder) data = WithSeekableStream(stream, s => Decode(s, config)); + + mimeType = data.decoder?.MimeTypes.FirstOrDefault(); - if (img != null) + if (data.img != null) { - return img; + return data.img; } StringBuilder stringBuilder = new StringBuilder(); - stringBuilder.AppendLine("Image cannot be loaded. Available formats:"); + stringBuilder.AppendLine("Image cannot be loaded. Available decoders:"); - foreach (IImageFormat format in config.ImageFormats) + foreach (IImageDecoder format in config.ImageDecoders) { stringBuilder.AppendLine("-" + format); } diff --git a/src/ImageSharp/Image/Image{TPixel}.cs b/src/ImageSharp/Image/Image{TPixel}.cs index 059ccb9a07..27f3801c4f 100644 --- a/src/ImageSharp/Image/Image{TPixel}.cs +++ b/src/ImageSharp/Image/Image{TPixel}.cs @@ -11,6 +11,7 @@ namespace ImageSharp using System.IO; using System.Linq; using System.Numerics; + using System.Text; using System.Threading.Tasks; using Formats; @@ -94,13 +95,7 @@ namespace ImageSharp internal Image(Configuration configuration, int width, int height, ImageMetaData metadata) : base(configuration, width, height) { - if (!this.Configuration.ImageFormats.Any()) - { - throw new InvalidOperationException("No image formats have been configured."); - } - this.MetaData = metadata ?? new ImageMetaData(); - this.CurrentImageFormat = this.Configuration.ImageFormats.First(); } /// @@ -138,11 +133,6 @@ namespace ImageSharp /// The list of frame images. public IList> Frames { get; } = new List>(); - /// - /// Gets the currently loaded image format. - /// - public IImageFormat CurrentImageFormat { get; internal set; } - /// /// Applies the processor to the image. /// @@ -162,48 +152,28 @@ namespace ImageSharp /// Saves the image to the given stream using the currently loaded image format. /// /// The stream to save the image to. + /// The mime type to save the image to. /// Thrown if the stream is null. /// The - public Image Save(Stream stream) + public Image Save(Stream stream, string mimeType) { - return this.Save(stream, (IEncoderOptions)null); - } + Guard.NotNullOrEmpty(mimeType, nameof(mimeType)); + IImageEncoder encoder = this.Configuration.ImageEncoders?.LastOrDefault(x => x?.MimeTypes?.Contains(mimeType, StringComparer.OrdinalIgnoreCase) == true); - /// - /// Saves the image to the given stream using the currently loaded image format. - /// - /// The stream to save the image to. - /// The options for the encoder. - /// Thrown if the stream is null. - /// The - public Image Save(Stream stream, IEncoderOptions options) - { - return this.Save(stream, this.CurrentImageFormat?.Encoder, options); - } + if (encoder == null) + { + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.AppendLine("Can't find encoder for provided mime type. Available encoded:"); - /// - /// Saves the image to the given stream using the given image format. - /// - /// The stream to save the image to. - /// The format to save the image as. - /// The - public Image Save(Stream stream, IImageFormat format) - { - return this.Save(stream, format, null); - } + foreach (IImageEncoder format in this.Configuration.ImageEncoders) + { + stringBuilder.AppendLine("-" + format); + } - /// - /// Saves the image to the given stream using the given image format. - /// - /// The stream to save the image to. - /// The format to save the image as. - /// The options for the encoder. - /// The - public Image Save(Stream stream, IImageFormat format, IEncoderOptions options) - { - Guard.NotNull(format, nameof(format)); + throw new NotSupportedException(stringBuilder.ToString()); + } - return this.Save(stream, format.Encoder, options); + return this.Save(stream, encoder); } /// @@ -216,26 +186,11 @@ namespace ImageSharp /// The . /// public Image Save(Stream stream, IImageEncoder encoder) - { - return this.Save(stream, encoder, null); - } - - /// - /// Saves the image to the given stream using the given image encoder. - /// - /// The stream to save the image to. - /// The encoder to save the image with. - /// The options for the encoder. - /// Thrown if the stream or encoder is null. - /// - /// The . - /// - public Image Save(Stream stream, IImageEncoder encoder, IEncoderOptions options) { Guard.NotNull(stream, nameof(stream)); Guard.NotNull(encoder, nameof(encoder)); - encoder.Encode(this, stream, options); + encoder.Encode(this, stream); return this; } @@ -249,52 +204,24 @@ namespace ImageSharp /// The public Image Save(string filePath) { - return this.Save(filePath, (IEncoderOptions)null); - } + Guard.NotNullOrEmpty(filePath, nameof(filePath)); - /// - /// Saves the image to the given stream using the currently loaded image format. - /// - /// The file path to save the image to. - /// The options for the encoder. - /// Thrown if the stream is null. - /// The - public Image Save(string filePath, IEncoderOptions options) - { string ext = Path.GetExtension(filePath).Trim('.'); - IImageFormat format = this.Configuration.ImageFormats.SingleOrDefault(f => f.SupportedExtensions.Contains(ext, StringComparer.OrdinalIgnoreCase)); - if (format == null) + IImageEncoder encoder = this.Configuration.ImageEncoders?.LastOrDefault(x => x?.FileExtensions?.Contains(ext, StringComparer.OrdinalIgnoreCase) == true); + if (encoder == null) { - throw new InvalidOperationException($"No image formats have been registered for the file extension '{ext}'."); - } + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.AppendLine($"Can't find encoder for file extention '{ext}'. Available encoded:"); - return this.Save(filePath, format, options); - } + foreach (IImageEncoder format in this.Configuration.ImageEncoders) + { + stringBuilder.AppendLine("-" + format); + } - /// - /// Saves the image to the given stream using the currently loaded image format. - /// - /// The file path to save the image to. - /// The format to save the image as. - /// Thrown if the format is null. - /// The - public Image Save(string filePath, IImageFormat format) - { - return this.Save(filePath, format, null); - } + throw new NotSupportedException(stringBuilder.ToString()); + } - /// - /// Saves the image to the given stream using the currently loaded image format. - /// - /// The file path to save the image to. - /// The format to save the image as. - /// The options for the encoder. - /// Thrown if the format is null. - /// The - public Image Save(string filePath, IImageFormat format, IEncoderOptions options) - { - Guard.NotNull(format, nameof(format)); - return this.Save(filePath, format.Encoder, options); + return this.Save(filePath, encoder); } /// @@ -305,24 +232,11 @@ namespace ImageSharp /// Thrown if the encoder is null. /// The public Image Save(string filePath, IImageEncoder encoder) - { - return this.Save(filePath, encoder, null); - } - - /// - /// Saves the image to the given stream using the currently loaded image format. - /// - /// The file path to save the image to. - /// The encoder to save the image with. - /// The options for the encoder. - /// Thrown if the encoder is null. - /// The - public Image Save(string filePath, IImageEncoder encoder, IEncoderOptions options) { Guard.NotNull(encoder, nameof(encoder)); using (Stream fs = this.Configuration.FileSystem.Create(filePath)) { - return this.Save(fs, encoder, options); + return this.Save(fs, encoder); } } #endif @@ -330,21 +244,22 @@ namespace ImageSharp /// public override string ToString() { - return $"Image: {this.Width}x{this.Height}"; + return $"Image<{typeof(TPixel).Name}>: {this.Width}x{this.Height}"; } /// /// Returns a Base64 encoded string from the given image. /// /// + /// The mimeType. /// The - public string ToBase64String() + public string ToBase64String(string mimeType) { using (MemoryStream stream = new MemoryStream()) { - this.Save(stream); + this.Save(stream, mimeType); stream.Flush(); - return $"data:{this.CurrentImageFormat.MimeType};base64,{Convert.ToBase64String(stream.ToArray())}"; + return $"data:{mimeType};base64,{Convert.ToBase64String(stream.ToArray())}"; } } @@ -417,7 +332,6 @@ namespace ImageSharp /// private void CopyProperties(IImage other) { - this.CurrentImageFormat = other.CurrentImageFormat; this.MetaData = new ImageMetaData(other.MetaData); } } diff --git a/src/ImageSharp/ImageSharp.csproj b/src/ImageSharp/ImageSharp.csproj index 17f7bf58f3..6cc981f964 100644 --- a/src/ImageSharp/ImageSharp.csproj +++ b/src/ImageSharp/ImageSharp.csproj @@ -43,6 +43,7 @@ + ..\..\ImageSharp.ruleset diff --git a/src/ImageSharp/MetaData/ImageMetaData.cs b/src/ImageSharp/MetaData/ImageMetaData.cs index 91ea9ac4bf..5361b486d4 100644 --- a/src/ImageSharp/MetaData/ImageMetaData.cs +++ b/src/ImageSharp/MetaData/ImageMetaData.cs @@ -50,7 +50,6 @@ namespace ImageSharp this.HorizontalResolution = other.HorizontalResolution; this.VerticalResolution = other.VerticalResolution; - this.Quality = other.Quality; this.FrameDelay = other.FrameDelay; this.DisposalMethod = other.DisposalMethod; this.RepeatCount = other.RepeatCount; @@ -127,11 +126,6 @@ namespace ImageSharp /// A list of image properties. public IList Properties { get; } = new List(); - /// - /// Gets or sets the quality of the image. This affects the output quality of lossy image formats. - /// - public int Quality { get; set; } - /// /// Gets or sets the number of times any animation is repeated. /// 0 means to repeat indefinitely. diff --git a/tests/ImageSharp.Benchmarks/BenchmarkBase.cs b/tests/ImageSharp.Benchmarks/BenchmarkBase.cs index d6e8ac6925..41574d109f 100644 --- a/tests/ImageSharp.Benchmarks/BenchmarkBase.cs +++ b/tests/ImageSharp.Benchmarks/BenchmarkBase.cs @@ -13,10 +13,6 @@ protected BenchmarkBase() { // Add Image Formats - Configuration.Default.AddImageFormat(new JpegFormat()); - Configuration.Default.AddImageFormat(new PngFormat()); - Configuration.Default.AddImageFormat(new BmpFormat()); - Configuration.Default.AddImageFormat(new GifFormat()); } } } diff --git a/tests/ImageSharp.Benchmarks/Image/EncodeIndexedPng.cs b/tests/ImageSharp.Benchmarks/Image/EncodeIndexedPng.cs index 88e82f0796..aa3112f524 100644 --- a/tests/ImageSharp.Benchmarks/Image/EncodeIndexedPng.cs +++ b/tests/ImageSharp.Benchmarks/Image/EncodeIndexedPng.cs @@ -53,9 +53,9 @@ namespace ImageSharp.Benchmarks.Image { using (MemoryStream memoryStream = new MemoryStream()) { - PngEncoderOptions options = new PngEncoderOptions() { Quantizer = new OctreeQuantizer(), Quality = 256 }; + PngEncoder encoder = new PngEncoder() { Quantizer = new OctreeQuantizer(), Quality = 256 }; - this.bmpCore.SaveAsPng(memoryStream, options); + this.bmpCore.SaveAsPng(memoryStream, encoder); } } @@ -64,7 +64,7 @@ namespace ImageSharp.Benchmarks.Image { using (MemoryStream memoryStream = new MemoryStream()) { - PngEncoderOptions options = new PngEncoderOptions { Quantizer = new OctreeQuantizer { Dither = false }, Quality = 256 }; + PngEncoder options = new PngEncoder { Quantizer = new OctreeQuantizer { Dither = false }, Quality = 256 }; this.bmpCore.SaveAsPng(memoryStream, options); } @@ -75,7 +75,7 @@ namespace ImageSharp.Benchmarks.Image { using (MemoryStream memoryStream = new MemoryStream()) { - PngEncoderOptions options = new PngEncoderOptions { Quantizer = new PaletteQuantizer(), Quality = 256 }; + PngEncoder options = new PngEncoder { Quantizer = new PaletteQuantizer(), Quality = 256 }; this.bmpCore.SaveAsPng(memoryStream, options); } @@ -86,7 +86,7 @@ namespace ImageSharp.Benchmarks.Image { using (MemoryStream memoryStream = new MemoryStream()) { - PngEncoderOptions options = new PngEncoderOptions { Quantizer = new PaletteQuantizer { Dither = false }, Quality = 256 }; + PngEncoder options = new PngEncoder { Quantizer = new PaletteQuantizer { Dither = false }, Quality = 256 }; this.bmpCore.SaveAsPng(memoryStream, options); } @@ -97,7 +97,7 @@ namespace ImageSharp.Benchmarks.Image { using (MemoryStream memoryStream = new MemoryStream()) { - PngEncoderOptions options = new PngEncoderOptions() { Quantizer = new WuQuantizer(), Quality = 256 }; + PngEncoder options = new PngEncoder() { Quantizer = new WuQuantizer(), Quality = 256 }; this.bmpCore.SaveAsPng(memoryStream, options); } diff --git a/tests/ImageSharp.Benchmarks/Image/EncodePng.cs b/tests/ImageSharp.Benchmarks/Image/EncodePng.cs index 3ec83aa7cf..81bb235eea 100644 --- a/tests/ImageSharp.Benchmarks/Image/EncodePng.cs +++ b/tests/ImageSharp.Benchmarks/Image/EncodePng.cs @@ -71,7 +71,7 @@ namespace ImageSharp.Benchmarks.Image new OctreeQuantizer() : new PaletteQuantizer(); - PngEncoderOptions options = new PngEncoderOptions() { Quantizer = quantizer }; + PngEncoder options = new PngEncoder() { Quantizer = quantizer }; this.bmpCore.SaveAsPng(memoryStream, options); } } diff --git a/tests/ImageSharp.Tests/ConfigurationTests.cs b/tests/ImageSharp.Tests/ConfigurationTests.cs index aa3c4edfc1..e2d50649a6 100644 --- a/tests/ImageSharp.Tests/ConfigurationTests.cs +++ b/tests/ImageSharp.Tests/ConfigurationTests.cs @@ -36,7 +36,8 @@ namespace ImageSharp.Tests { var configuration = Configuration.CreateDefaultInstance(); - Assert.Equal(4, configuration.ImageFormats.Count); + Assert.Equal(4, configuration.ImageDecoders.Count); + Assert.Equal(4, configuration.ImageDecoders.Count); } /// @@ -73,7 +74,8 @@ namespace ImageSharp.Tests [Fact] public void TestDefultConfigurationImageFormatsIsNotNull() { - Assert.True(Configuration.Default.ImageFormats != null); + Assert.True(Configuration.Default.ImageDecoders != null); + Assert.True(Configuration.Default.ImageEncoders != null); } /// @@ -85,163 +87,25 @@ namespace ImageSharp.Tests { Assert.Throws(() => { - Configuration.Default.AddImageFormat(null); + Configuration.Default.AddImageFormat((IImageEncoder)null); }); - } - - /// - /// Tests the method throws an exception - /// when the encoder is null. - /// - [Fact] - public void TestAddImageFormatThrowsWithNullEncoder() - { - var format = new TestFormat { Encoder = null }; - - Assert.Throws(() => - { - Configuration.Default.AddImageFormat(format); - }); - } - - /// - /// Tests the method throws an exception - /// when the decoder is null. - /// - [Fact] - public void TestAddImageFormatThrowsWithNullDecoder() - { - var format = new TestFormat { Decoder = null }; - - Assert.Throws(() => - { - Configuration.Default.AddImageFormat(format); - }); - } - - /// - /// Tests the method throws an exception - /// when the mime type is null or an empty string. - /// - [Fact] - public void TestAddImageFormatThrowsWithNullOrEmptyMimeType() - { - var format = new TestFormat { MimeType = null }; - Assert.Throws(() => { - Configuration.Default.AddImageFormat(format); - }); - - format = new TestFormat { MimeType = string.Empty }; - - Assert.Throws(() => - { - Configuration.Default.AddImageFormat(format); + Configuration.Default.AddImageFormat((IImageDecoder)null); }); } - /// - /// Tests the method throws an exception - /// when the extension is null or an empty string. - /// - [Fact] - public void TestAddImageFormatThrowsWithNullOrEmptyExtension() - { - var format = new TestFormat { Extension = null }; - - Assert.Throws(() => - { - Configuration.Default.AddImageFormat(format); - }); - - format = new TestFormat { Extension = string.Empty }; - - Assert.Throws(() => - { - Configuration.Default.AddImageFormat(format); - }); - } - - /// - /// Tests the method throws an exception - /// when the supported extensions list is null or empty. - /// - [Fact] - public void TestAddImageFormatThrowsWenSupportedExtensionsIsNullOrEmpty() - { - var format = new TestFormat { SupportedExtensions = null }; - - Assert.Throws(() => - { - Configuration.Default.AddImageFormat(format); - }); - - format = new TestFormat { SupportedExtensions = Enumerable.Empty() }; - - Assert.Throws(() => - { - Configuration.Default.AddImageFormat(format); - }); - } - - /// - /// Tests the method throws an exception - /// when the supported extensions list does not contain the default extension. - /// - [Fact] - public void TestAddImageFormatThrowsWithoutDefaultExtension() - { - var format = new TestFormat { Extension = "test" }; - - Assert.Throws(() => - { - Configuration.Default.AddImageFormat(format); - }); - } - - /// - /// Tests the method throws an exception - /// when the supported extensions list contains an empty string. - /// - [Fact] - public void TestAddImageFormatThrowsWithEmptySupportedExtension() - { - var format = new TestFormat - { - Extension = "test", - SupportedExtensions = new[] { "test", string.Empty } - }; - - Assert.Throws(() => - { - Configuration.Default.AddImageFormat(format); - }); - } - - /// - /// Test that the method ignores adding duplicate image formats. - /// - [Fact] - public void TestConfigurationIgnoresDuplicateImageFormats() - { - Configuration.Default.AddImageFormat(new PngFormat()); - Configuration.Default.AddImageFormat(new PngFormat()); - - Assert.True(Configuration.Default.ImageFormats.Count(i => i.GetType() == typeof(PngFormat)) == 1); - } - /// /// Test that the default image constructors use default configuration. /// [Fact] public void TestImageUsesDefaultConfiguration() { - Configuration.Default.AddImageFormat(new PngFormat()); + Configuration.Default.AddImageFormat(new PngDecoder()); var image = new Image(1, 1); Assert.Equal(image.Configuration.ParallelOptions, Configuration.Default.ParallelOptions); - Assert.Equal(image.Configuration.ImageFormats, Configuration.Default.ImageFormats); + Assert.Equal(image.Configuration.ImageDecoders, Configuration.Default.ImageDecoders); } /// @@ -250,60 +114,12 @@ namespace ImageSharp.Tests [Fact] public void TestImageCopiesConfiguration() { - Configuration.Default.AddImageFormat(new PngFormat()); + Configuration.Default.AddImageFormat(new PngDecoder()); var image = new Image(1, 1); var image2 = new Image(image); Assert.Equal(image2.Configuration.ParallelOptions, image.Configuration.ParallelOptions); - Assert.True(image2.Configuration.ImageFormats.SequenceEqual(image.Configuration.ImageFormats)); - } - - /// - /// A test image format for testing the configuration. - /// - private class TestFormat : IImageFormat - { - /// - /// Initializes a new instance of the class. - /// - public TestFormat() - { - this.Decoder = new JpegDecoder(); - this.Encoder = new JpegEncoder(); - this.Extension = "jpg"; - this.MimeType = "image/test"; - this.SupportedExtensions = new[] { "jpg" }; - } - - /// - public IImageDecoder Decoder { get; set; } - - /// - public IImageEncoder Encoder { get; set; } - - /// - public string MimeType { get; set; } - - /// - public string Extension { get; set; } - - /// - public IEnumerable SupportedExtensions { get; set; } - - /// - public int HeaderSize - { - get - { - throw new NotImplementedException(); - } - } - - /// - public bool IsSupportedFileFormat(byte[] header) - { - throw new NotImplementedException(); - } + Assert.True(image2.Configuration.ImageDecoders.SequenceEqual(image.Configuration.ImageDecoders)); } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Drawing/BeziersTests.cs b/tests/ImageSharp.Tests/Drawing/BeziersTests.cs index 3d49a70b81..4b53bbb045 100644 --- a/tests/ImageSharp.Tests/Drawing/BeziersTests.cs +++ b/tests/ImageSharp.Tests/Drawing/BeziersTests.cs @@ -24,18 +24,15 @@ namespace ImageSharp.Tests.Drawing string path = this.CreateOutputDirectory("Drawing", "BezierLine"); using (Image image = new Image(500, 500)) { - using (FileStream output = File.OpenWrite($"{path}/Simple.png")) - { - image.BackgroundColor(Rgba32.Blue) - .DrawBeziers(Rgba32.HotPink, 5, - new[] { + image.BackgroundColor(Rgba32.Blue) + .DrawBeziers(Rgba32.HotPink, 5, + new[] { new Vector2(10, 400), new Vector2(30, 10), new Vector2(240, 30), new Vector2(300, 400) - }) - .Save(output); - } + }) + .Save($"{path}/Simple.png"); using (PixelAccessor sourcePixels = image.Lock()) { @@ -66,19 +63,16 @@ namespace ImageSharp.Tests.Drawing using (Image image = new Image(500, 500)) { - using (FileStream output = File.OpenWrite($"{path}/Opacity.png")) - { - image.BackgroundColor(Rgba32.Blue) - .DrawBeziers(color, - 10, - new[] { + image.BackgroundColor(Rgba32.Blue) + .DrawBeziers(color, + 10, + new[] { new Vector2(10, 400), new Vector2(30, 10), new Vector2(240, 30), new Vector2(300, 400) - }) - .Save(output); - } + }) + .Save($"{path}/Opacity.png"); //shift background color towards forground color by the opacity amount Rgba32 mergedColor = new Rgba32(Vector4.Lerp(Rgba32.Blue.ToVector4(), Rgba32.HotPink.ToVector4(), 150f / 255f)); diff --git a/tests/ImageSharp.Tests/Drawing/DrawPathTests.cs b/tests/ImageSharp.Tests/Drawing/DrawPathTests.cs index 4ba4a3a835..50466fcdde 100644 --- a/tests/ImageSharp.Tests/Drawing/DrawPathTests.cs +++ b/tests/ImageSharp.Tests/Drawing/DrawPathTests.cs @@ -34,13 +34,10 @@ namespace ImageSharp.Tests.Drawing ShapePath p = new ShapePath(linerSegemnt, bazierSegment); - using (FileStream output = File.OpenWrite($"{path}/Simple.png")) - { - image - .BackgroundColor(Rgba32.Blue) - .Draw(Rgba32.HotPink, 5, p) - .Save(output); - } + image + .BackgroundColor(Rgba32.Blue) + .Draw(Rgba32.HotPink, 5, p) + .Save($"{path}/Simple.png"); using (PixelAccessor sourcePixels = image.Lock()) { @@ -77,13 +74,10 @@ namespace ImageSharp.Tests.Drawing using (Image image = new Image(500, 500)) { - using (FileStream output = File.OpenWrite($"{path}/Opacity.png")) - { - image - .BackgroundColor(Rgba32.Blue) - .Draw(color, 10, p) - .Save(output); - } + image + .BackgroundColor(Rgba32.Blue) + .Draw(color, 10, p) + .Save($"{path}/Opacity.png"); //shift background color towards forground color by the opacity amount Rgba32 mergedColor = new Rgba32(Vector4.Lerp(Rgba32.Blue.ToVector4(), Rgba32.HotPink.ToVector4(), 150f / 255f)); @@ -115,11 +109,8 @@ namespace ImageSharp.Tests.Drawing image.DrawLines(pen, new Vector2[] { new Vector2(100, 2), new Vector2(-10, i) }); } - using (FileStream output = File.OpenWrite($"{path}/ClippedLines.png")) - { - image - .Save(output); - } + image + .Save($"{path}/ClippedLines.png"); using (PixelAccessor sourcePixels = image.Lock()) { Assert.Equal(Rgba32.White, sourcePixels[0, 90]); diff --git a/tests/ImageSharp.Tests/Drawing/FillPatternTests.cs b/tests/ImageSharp.Tests/Drawing/FillPatternTests.cs index 7bacebe42e..bbabdf0ea6 100644 --- a/tests/ImageSharp.Tests/Drawing/FillPatternTests.cs +++ b/tests/ImageSharp.Tests/Drawing/FillPatternTests.cs @@ -25,10 +25,7 @@ namespace ImageSharp.Tests.Drawing .Fill(background) .Fill(brush); - using (FileStream output = File.OpenWrite($"{path}/{name}.png")) - { - image.Save(output); - } + image.Save($"{path}/{name}.png"); using (PixelAccessor sourcePixels = image.Lock()) { @@ -54,10 +51,7 @@ namespace ImageSharp.Tests.Drawing } } } - using (FileStream output = File.OpenWrite($"{path}/{name}x4.png")) - { - image.Resize(80, 80).Save(output); - } + image.Resize(80, 80).Save($"{path}/{name}x4.png"); } } diff --git a/tests/ImageSharp.Tests/Drawing/FillSolidBrushTests.cs b/tests/ImageSharp.Tests/Drawing/FillSolidBrushTests.cs index dc0b83615d..ce127cfe03 100644 --- a/tests/ImageSharp.Tests/Drawing/FillSolidBrushTests.cs +++ b/tests/ImageSharp.Tests/Drawing/FillSolidBrushTests.cs @@ -16,7 +16,7 @@ namespace ImageSharp.Tests.Drawing using Xunit; - public class FillSolidBrushTests: FileTestBase + public class FillSolidBrushTests : FileTestBase { [Fact] public void ImageShouldBeFloodFilledWithColorOnDefaultBackground() @@ -24,12 +24,9 @@ namespace ImageSharp.Tests.Drawing string path = this.CreateOutputDirectory("Fill", "SolidBrush"); using (Image image = new Image(500, 500)) { - using (FileStream output = File.OpenWrite($"{path}/DefaultBack.png")) - { - image - .Fill(Rgba32.HotPink) - .Save(output); - } + image + .Fill(Rgba32.HotPink) + .Save($"{path}/DefaultBack.png"); using (PixelAccessor sourcePixels = image.Lock()) { @@ -46,13 +43,10 @@ namespace ImageSharp.Tests.Drawing string path = this.CreateOutputDirectory("Fill", "SolidBrush"); using (Image image = new Image(500, 500)) { - using (FileStream output = File.OpenWrite($"{path}/Simple.png")) - { - image - .BackgroundColor(Rgba32.Blue) - .Fill(Rgba32.HotPink) - .Save(output); - } + image + .BackgroundColor(Rgba32.Blue) + .Fill(Rgba32.HotPink) + .Save($"{path}/Simple.png"); using (PixelAccessor sourcePixels = image.Lock()) { @@ -71,13 +65,11 @@ namespace ImageSharp.Tests.Drawing { Rgba32 color = new Rgba32(Rgba32.HotPink.R, Rgba32.HotPink.G, Rgba32.HotPink.B, 150); - using (FileStream output = File.OpenWrite($"{path}/Opacity.png")) - { - image - .BackgroundColor(Rgba32.Blue) - .Fill(color) - .Save(output); - } + image + .BackgroundColor(Rgba32.Blue) + .Fill(color) + .Save($"{path}/Opacity.png"); + //shift background color towards forground color by the opacity amount Rgba32 mergedColor = new Rgba32(Vector4.Lerp(Rgba32.Blue.ToVector4(), Rgba32.HotPink.ToVector4(), 150f / 255f)); diff --git a/tests/ImageSharp.Tests/Drawing/LineComplexPolygonTests.cs b/tests/ImageSharp.Tests/Drawing/LineComplexPolygonTests.cs index 1f35a37884..e058572fb3 100644 --- a/tests/ImageSharp.Tests/Drawing/LineComplexPolygonTests.cs +++ b/tests/ImageSharp.Tests/Drawing/LineComplexPolygonTests.cs @@ -34,13 +34,10 @@ namespace ImageSharp.Tests.Drawing using (Image image = new Image(500, 500)) { - using (FileStream output = File.OpenWrite($"{path}/Simple.png")) - { - image - .BackgroundColor(Rgba32.Blue) - .Draw(Rgba32.HotPink, 5, simplePath.Clip(hole1)) - .Save(output); - } + image + .BackgroundColor(Rgba32.Blue) + .Draw(Rgba32.HotPink, 5, simplePath.Clip(hole1)) + .Save($"{path}/Simple.png"); using (PixelAccessor sourcePixels = image.Lock()) { @@ -84,13 +81,10 @@ namespace ImageSharp.Tests.Drawing using (Image image = new Image(500, 500)) { - using (FileStream output = File.OpenWrite($"{path}/SimpleVanishHole.png")) - { - image - .BackgroundColor(Rgba32.Blue) - .Draw(Rgba32.HotPink, 5, simplePath.Clip(hole1)) - .Save(output); - } + image + .BackgroundColor(Rgba32.Blue) + .Draw(Rgba32.HotPink, 5, simplePath.Clip(hole1)) + .Save($"{path}/SimpleVanishHole.png"); using (PixelAccessor sourcePixels = image.Lock()) { @@ -135,13 +129,10 @@ namespace ImageSharp.Tests.Drawing using (Image image = new Image(500, 500)) { - using (FileStream output = File.OpenWrite($"{path}/SimpleOverlapping.png")) - { - image - .BackgroundColor(Rgba32.Blue) - .Draw(Rgba32.HotPink, 5, simplePath.Clip(hole1)) - .Save(output); - } + image + .BackgroundColor(Rgba32.Blue) + .Draw(Rgba32.HotPink, 5, simplePath.Clip(hole1)) + .Save($"{path}/SimpleOverlapping.png"); using (PixelAccessor sourcePixels = image.Lock()) { @@ -181,13 +172,10 @@ namespace ImageSharp.Tests.Drawing using (Image image = new Image(500, 500)) { - using (FileStream output = File.OpenWrite($"{path}/Dashed.png")) - { - image - .BackgroundColor(Rgba32.Blue) - .Draw(Pens.Dash(Rgba32.HotPink, 5), simplePath.Clip(hole1)) - .Save(output); - } + image + .BackgroundColor(Rgba32.Blue) + .Draw(Pens.Dash(Rgba32.HotPink, 5), simplePath.Clip(hole1)) + .Save($"{path}/Dashed.png"); } } @@ -209,13 +197,10 @@ namespace ImageSharp.Tests.Drawing using (Image image = new Image(500, 500)) { - using (FileStream output = File.OpenWrite($"{path}/Opacity.png")) - { - image - .BackgroundColor(Rgba32.Blue) - .Draw(color, 5, simplePath.Clip(hole1)) - .Save(output); - } + image + .BackgroundColor(Rgba32.Blue) + .Draw(color, 5, simplePath.Clip(hole1)) + .Save($"{path}/Opacity.png"); //shift background color towards forground color by the opacity amount Rgba32 mergedColor = new Rgba32(Vector4.Lerp(Rgba32.Blue.ToVector4(), Rgba32.HotPink.ToVector4(), 150f / 255f)); diff --git a/tests/ImageSharp.Tests/Drawing/LineTests.cs b/tests/ImageSharp.Tests/Drawing/LineTests.cs index e751557b6f..6c1e700136 100644 --- a/tests/ImageSharp.Tests/Drawing/LineTests.cs +++ b/tests/ImageSharp.Tests/Drawing/LineTests.cs @@ -23,18 +23,15 @@ namespace ImageSharp.Tests.Drawing string path = this.CreateOutputDirectory("Drawing", "Lines"); using (Image image = new Image(500, 500)) { - using (FileStream output = File.OpenWrite($"{path}/Simple.png")) - { - image - .BackgroundColor(Rgba32.Blue) - .DrawLines(Rgba32.HotPink, 5, - new[] { + image + .BackgroundColor(Rgba32.Blue) + .DrawLines(Rgba32.HotPink, 5, + new[] { new Vector2(10, 10), new Vector2(200, 150), new Vector2(50, 300) - }) - .Save(output); - } + }) + .Save($"{path}/Simple.png"); using (PixelAccessor sourcePixels = image.Lock()) { @@ -53,19 +50,16 @@ namespace ImageSharp.Tests.Drawing string path = this.CreateOutputDirectory("Drawing", "Lines"); using (Image image = new Image(500, 500)) { - using (FileStream output = File.OpenWrite($"{path}/Simple_noantialias.png")) - { - image - .BackgroundColor(Rgba32.Blue) - .DrawLines(Rgba32.HotPink, 5, - new[] { + image + .BackgroundColor(Rgba32.Blue) + .DrawLines(Rgba32.HotPink, 5, + new[] { new Vector2(10, 10), new Vector2(200, 150), new Vector2(50, 300) - }, - new GraphicsOptions(false)) - .Save(output); - } + }, + new GraphicsOptions(false)) + .Save($"{path}/Simple_noantialias.png"); using (PixelAccessor sourcePixels = image.Lock()) { @@ -84,18 +78,15 @@ namespace ImageSharp.Tests.Drawing string path = this.CreateOutputDirectory("Drawing", "Lines"); using (Image image = new Image(500, 500)) { - using (FileStream output = File.OpenWrite($"{path}/Dashed.png")) - { - image - .BackgroundColor(Rgba32.Blue) - .DrawLines(Pens.Dash(Rgba32.HotPink, 5), - new[] { + image + .BackgroundColor(Rgba32.Blue) + .DrawLines(Pens.Dash(Rgba32.HotPink, 5), + new[] { new Vector2(10, 10), new Vector2(200, 150), new Vector2(50, 300) - }) - .Save(output); - } + }) + .Save($"{path}/Dashed.png"); } } @@ -105,18 +96,15 @@ namespace ImageSharp.Tests.Drawing string path = this.CreateOutputDirectory("Drawing", "Lines"); using (Image image = new Image(500, 500)) { - using (FileStream output = File.OpenWrite($"{path}/Dot.png")) - { - image - .BackgroundColor(Rgba32.Blue) - .DrawLines(Pens.Dot(Rgba32.HotPink, 5), - new[] { + image + .BackgroundColor(Rgba32.Blue) + .DrawLines(Pens.Dot(Rgba32.HotPink, 5), + new[] { new Vector2(10, 10), new Vector2(200, 150), new Vector2(50, 300) - }) - .Save(output); - } + }) + .Save($"{path}/Dot.png"); } } @@ -126,18 +114,15 @@ namespace ImageSharp.Tests.Drawing string path = this.CreateOutputDirectory("Drawing", "Lines"); using (Image image = new Image(500, 500)) { - using (FileStream output = File.OpenWrite($"{path}/DashDot.png")) - { - image - .BackgroundColor(Rgba32.Blue) - .DrawLines(Pens.DashDot(Rgba32.HotPink, 5), - new[] { + image + .BackgroundColor(Rgba32.Blue) + .DrawLines(Pens.DashDot(Rgba32.HotPink, 5), + new[] { new Vector2(10, 10), new Vector2(200, 150), new Vector2(50, 300) - }) - .Save(output); - } + }) + .Save($"{path}/DashDot.png"); } } @@ -147,17 +132,14 @@ namespace ImageSharp.Tests.Drawing string path = this.CreateOutputDirectory("Drawing", "Lines"); Image image = new Image(500, 500); - using (FileStream output = File.OpenWrite($"{path}/DashDotDot.png")) - { - image - .BackgroundColor(Rgba32.Blue) - .DrawLines(Pens.DashDotDot(Rgba32.HotPink, 5), new[] { + image + .BackgroundColor(Rgba32.Blue) + .DrawLines(Pens.DashDotDot(Rgba32.HotPink, 5), new[] { new Vector2(10, 10), new Vector2(200, 150), new Vector2(50, 300) - }) - .Save(output); - } + }) + .Save($"{path}/DashDotDot.png"); } [Fact] @@ -169,21 +151,17 @@ namespace ImageSharp.Tests.Drawing Image image = new Image(500, 500); - - using (FileStream output = File.OpenWrite($"{path}/Opacity.png")) - { - image - .BackgroundColor(Rgba32.Blue) - .DrawLines(color, 10, new[] { + image + .BackgroundColor(Rgba32.Blue) + .DrawLines(color, 10, new[] { new Vector2(10, 10), new Vector2(200, 150), new Vector2(50, 300) - }) - .Save(output); - } + }) + .Save($"{path}/Opacity.png"); //shift background color towards forground color by the opacity amount - Rgba32 mergedColor = new Rgba32(Vector4.Lerp(Rgba32.Blue.ToVector4(), Rgba32.HotPink.ToVector4(), 150f/255f)); + Rgba32 mergedColor = new Rgba32(Vector4.Lerp(Rgba32.Blue.ToVector4(), Rgba32.HotPink.ToVector4(), 150f / 255f)); using (PixelAccessor sourcePixels = image.Lock()) { @@ -202,18 +180,15 @@ namespace ImageSharp.Tests.Drawing Image image = new Image(500, 500); - using (FileStream output = File.OpenWrite($"{path}/Rectangle.png")) - { - image - .BackgroundColor(Rgba32.Blue) - .DrawLines(Rgba32.HotPink, 10, new[] { + image + .BackgroundColor(Rgba32.Blue) + .DrawLines(Rgba32.HotPink, 10, new[] { new Vector2(10, 10), new Vector2(200, 10), new Vector2(200, 150), new Vector2(10, 150) - }) - .Save(output); - } + }) + .Save($"{path}/Rectangle.png"); using (PixelAccessor sourcePixels = image.Lock()) { diff --git a/tests/ImageSharp.Tests/Drawing/PolygonTests.cs b/tests/ImageSharp.Tests/Drawing/PolygonTests.cs index 9bc918d37a..842fc19d2e 100644 --- a/tests/ImageSharp.Tests/Drawing/PolygonTests.cs +++ b/tests/ImageSharp.Tests/Drawing/PolygonTests.cs @@ -24,8 +24,6 @@ namespace ImageSharp.Tests.Drawing using (Image image = new Image(500, 500)) { - using (FileStream output = File.OpenWrite($"{path}/Simple.png")) - { image .BackgroundColor(Rgba32.Blue) .DrawPolygon(Rgba32.HotPink, 5, @@ -34,8 +32,7 @@ namespace ImageSharp.Tests.Drawing new Vector2(200, 150), new Vector2(50, 300) }) - .Save(output); - } + .Save($"{path}/Simple.png"); using (PixelAccessor sourcePixels = image.Lock()) { @@ -64,13 +61,10 @@ namespace ImageSharp.Tests.Drawing using (Image image = new Image(500, 500)) { - using (FileStream output = File.OpenWrite($"{path}/Opacity.png")) - { image .BackgroundColor(Rgba32.Blue) .DrawPolygon(color, 10, simplePath) - .Save(output); - } + .Save($"{path}/Opacity.png"); //shift background color towards forground color by the opacity amount Rgba32 mergedColor = new Rgba32(Vector4.Lerp(Rgba32.Blue.ToVector4(), Rgba32.HotPink.ToVector4(), 150f / 255f)); @@ -95,13 +89,10 @@ namespace ImageSharp.Tests.Drawing using (Image image = new Image(500, 500)) { - using (FileStream output = File.OpenWrite($"{path}/Rectangle.png")) - { image .BackgroundColor(Rgba32.Blue) .Draw(Rgba32.HotPink, 10, new Rectangle(10, 10, 190, 140)) - .Save(output); - } + .Save($"{path}/Rectangle.png"); using (PixelAccessor sourcePixels = image.Lock()) { diff --git a/tests/ImageSharp.Tests/Drawing/RecolorImageTest.cs b/tests/ImageSharp.Tests/Drawing/RecolorImageTest.cs index 83419caaf4..4c082fc36d 100644 --- a/tests/ImageSharp.Tests/Drawing/RecolorImageTest.cs +++ b/tests/ImageSharp.Tests/Drawing/RecolorImageTest.cs @@ -26,11 +26,8 @@ namespace ImageSharp.Tests { using (Image image = file.CreateImage()) { - using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) - { - image.Fill(brush) - .Save(output); - } + image.Fill(brush) + .Save($"{path}/{file.FileName}"); } } } @@ -46,12 +43,9 @@ namespace ImageSharp.Tests { using (Image image = file.CreateImage()) { - using (FileStream output = File.OpenWrite($"{path}/Shaped_{file.FileName}")) - { - int imageHeight = image.Height; - image.Fill(brush, new Rectangle(0, imageHeight/2 - imageHeight/4, image.Width, imageHeight/2)) - .Save(output); - } + int imageHeight = image.Height; + image.Fill(brush, new Rectangle(0, imageHeight / 2 - imageHeight / 4, image.Width, imageHeight / 2)) + .Save($"{path}/Shaped_{file.FileName}"); } } } diff --git a/tests/ImageSharp.Tests/Drawing/SolidBezierTests.cs b/tests/ImageSharp.Tests/Drawing/SolidBezierTests.cs index 6cab7778e0..74f4fe8f64 100644 --- a/tests/ImageSharp.Tests/Drawing/SolidBezierTests.cs +++ b/tests/ImageSharp.Tests/Drawing/SolidBezierTests.cs @@ -28,13 +28,10 @@ namespace ImageSharp.Tests.Drawing }; using (Image image = new Image(500, 500)) { - using (FileStream output = File.OpenWrite($"{path}/Simple.png")) - { image .BackgroundColor(Rgba32.Blue) .Fill(Rgba32.HotPink, new Polygon(new BezierLineSegment(simplePath))) - .Save(output); - } + .Save($"{path}/Simple.png"); using (PixelAccessor sourcePixels = image.Lock()) { @@ -63,13 +60,10 @@ namespace ImageSharp.Tests.Drawing using (Image image = new Image(500, 500)) { - using (FileStream output = File.OpenWrite($"{path}/Opacity.png")) - { image .BackgroundColor(Rgba32.Blue) .Fill(color, new Polygon(new BezierLineSegment(simplePath))) - .Save(output); - } + .Save($"{path}/Opacity.png"); //shift background color towards forground color by the opacity amount Rgba32 mergedColor = new Rgba32(Vector4.Lerp(Rgba32.Blue.ToVector4(), Rgba32.HotPink.ToVector4(), 150f / 255f)); diff --git a/tests/ImageSharp.Tests/Drawing/SolidComplexPolygonTests.cs b/tests/ImageSharp.Tests/Drawing/SolidComplexPolygonTests.cs index 5e0244d02f..c3af3d5c28 100644 --- a/tests/ImageSharp.Tests/Drawing/SolidComplexPolygonTests.cs +++ b/tests/ImageSharp.Tests/Drawing/SolidComplexPolygonTests.cs @@ -31,16 +31,13 @@ namespace ImageSharp.Tests.Drawing new Vector2(93, 85), new Vector2(65, 137))); IPath clipped = simplePath.Clip(hole1); - // var clipped = new Rectangle(10, 10, 100, 100).Clip(new Rectangle(20, 0, 20, 20)); + // var clipped = new Rectangle(10, 10, 100, 100).Clip(new Rectangle(20, 0, 20, 20)); using (Image image = new Image(500, 500)) { - using (FileStream output = File.OpenWrite($"{path}/Simple.png")) - { - image - .BackgroundColor(Rgba32.Blue) - .Fill(Rgba32.HotPink, clipped) - .Save(output); - } + image + .BackgroundColor(Rgba32.Blue) + .Fill(Rgba32.HotPink, clipped) + .Save($"{path}/Simple.png"); using (PixelAccessor sourcePixels = image.Lock()) { @@ -69,13 +66,10 @@ namespace ImageSharp.Tests.Drawing using (Image image = new Image(500, 500)) { - using (FileStream output = File.OpenWrite($"{path}/SimpleOverlapping.png")) - { - image - .BackgroundColor(Rgba32.Blue) - .Fill(Rgba32.HotPink, simplePath.Clip(hole1)) - .Save(output); - } + image + .BackgroundColor(Rgba32.Blue) + .Fill(Rgba32.HotPink, simplePath.Clip(hole1)) + .Save($"{path}/SimpleOverlapping.png"); using (PixelAccessor sourcePixels = image.Lock()) { @@ -104,13 +98,10 @@ namespace ImageSharp.Tests.Drawing using (Image image = new Image(500, 500)) { - using (FileStream output = File.OpenWrite($"{path}/Opacity.png")) - { - image - .BackgroundColor(Rgba32.Blue) - .Fill(color, simplePath.Clip(hole1)) - .Save(output); - } + image + .BackgroundColor(Rgba32.Blue) + .Fill(color, simplePath.Clip(hole1)) + .Save($"{path}/Opacity.png"); //shift background color towards forground color by the opacity amount Rgba32 mergedColor = new Rgba32(Vector4.Lerp(Rgba32.Blue.ToVector4(), Rgba32.HotPink.ToVector4(), 150f / 255f)); diff --git a/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs b/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs index 8ffa62d815..d93bb832eb 100644 --- a/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs +++ b/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs @@ -31,12 +31,9 @@ namespace ImageSharp.Tests.Drawing using (Image image = new Image(500, 500)) { - using (FileStream output = File.OpenWrite($"{path}/Simple.png")) - { - image - .FillPolygon(Rgba32.HotPink, simplePath, new GraphicsOptions(true)) - .Save(output); - } + image + .FillPolygon(Rgba32.HotPink, simplePath, new GraphicsOptions(true)) + .Save($"{path}/Simple.png"); using (PixelAccessor sourcePixels = image.Lock()) { @@ -57,12 +54,9 @@ namespace ImageSharp.Tests.Drawing using (Image image = new Image(500, 500)) { - using (FileStream output = File.OpenWrite($"{path}/Pattern.png")) - { - image - .FillPolygon(Brushes.Horizontal(Rgba32.HotPink), simplePath, new GraphicsOptions(true)) - .Save(output); - } + image + .FillPolygon(Brushes.Horizontal(Rgba32.HotPink), simplePath, new GraphicsOptions(true)) + .Save($"{path}/Pattern.png"); using (PixelAccessor sourcePixels = image.Lock()) { @@ -82,12 +76,11 @@ namespace ImageSharp.Tests.Drawing }; using (Image image = new Image(500, 500)) - using (FileStream output = File.OpenWrite($"{path}/Simple_NoAntialias.png")) { image .BackgroundColor(Rgba32.Blue) .FillPolygon(Rgba32.HotPink, simplePath, new GraphicsOptions(false)) - .Save(output); + .Save($"{path}/Simple_NoAntialias.png"); using (PixelAccessor sourcePixels = image.Lock()) { @@ -114,14 +107,13 @@ namespace ImageSharp.Tests.Drawing using (Image brushImage = TestFile.Create(TestImages.Bmp.Car).CreateImage()) using (Image image = new Image(500, 500)) - using (FileStream output = File.OpenWrite($"{path}/Image.png")) { ImageBrush brush = new ImageBrush(brushImage); image .BackgroundColor(Rgba32.Blue) .FillPolygon(brush, simplePath) - .Save(output); + .Save($"{path}/Image.png"); } } @@ -138,13 +130,10 @@ namespace ImageSharp.Tests.Drawing using (Image image = new Image(500, 500)) { - using (FileStream output = File.OpenWrite($"{path}/Opacity.png")) - { - image - .BackgroundColor(Rgba32.Blue) - .FillPolygon(color, simplePath) - .Save(output); - } + image + .BackgroundColor(Rgba32.Blue) + .FillPolygon(color, simplePath) + .Save($"{path}/Opacity.png"); //shift background color towards forground color by the opacity amount Rgba32 mergedColor = new Rgba32(Vector4.Lerp(Rgba32.Blue.ToVector4(), Rgba32.HotPink.ToVector4(), 150f / 255f)); @@ -163,13 +152,10 @@ namespace ImageSharp.Tests.Drawing using (Image image = new Image(500, 500)) { - using (FileStream output = File.OpenWrite($"{path}/Rectangle.png")) - { - image - .BackgroundColor(Rgba32.Blue) - .Fill(Rgba32.HotPink, new SixLabors.Shapes.Rectangle(10, 10, 190, 140)) - .Save(output); - } + image + .BackgroundColor(Rgba32.Blue) + .Fill(Rgba32.HotPink, new SixLabors.Shapes.Rectangle(10, 10, 190, 140)) + .Save($"{path}/Rectangle.png"); using (PixelAccessor sourcePixels = image.Lock()) { @@ -193,13 +179,10 @@ namespace ImageSharp.Tests.Drawing using (Image image = new Image(100, 100)) { - using (FileStream output = File.OpenWrite($"{path}/Triangle.png")) - { - image - .BackgroundColor(Rgba32.Blue) - .Fill(Rgba32.HotPink, new RegularPolygon(50, 50, 3, 30)) - .Save(output); - } + image + .BackgroundColor(Rgba32.Blue) + .Fill(Rgba32.HotPink, new RegularPolygon(50, 50, 3, 30)) + .Save($"{path}/Triangle.png"); using (PixelAccessor sourcePixels = image.Lock()) { @@ -219,13 +202,10 @@ namespace ImageSharp.Tests.Drawing config.ParallelOptions.MaxDegreeOfParallelism = 1; using (Image image = new Image(config, 100, 100)) { - using (FileStream output = File.OpenWrite($"{path}/Septagon.png")) - { - image - .BackgroundColor(Rgba32.Blue) - .Fill(Rgba32.HotPink, new RegularPolygon(50, 50, 7, 30, -(float)Math.PI)) - .Save(output); - } + image + .BackgroundColor(Rgba32.Blue) + .Fill(Rgba32.HotPink, new RegularPolygon(50, 50, 7, 30, -(float)Math.PI)) + .Save($"{path}/Septagon.png"); } } @@ -238,14 +218,11 @@ namespace ImageSharp.Tests.Drawing config.ParallelOptions.MaxDegreeOfParallelism = 1; using (Image image = new Image(config, 100, 100)) { - using (FileStream output = File.OpenWrite($"{path}/ellipse.png")) - { - image - .BackgroundColor(Rgba32.Blue) - .Fill(Rgba32.HotPink, new Ellipse(50, 50, 30, 50) - .Rotate((float)(Math.PI / 3))) - .Save(output); - } + image + .BackgroundColor(Rgba32.Blue) + .Fill(Rgba32.HotPink, new Ellipse(50, 50, 30, 50) + .Rotate((float)(Math.PI / 3))) + .Save($"{path}/ellipse.png"); } } @@ -258,21 +235,18 @@ namespace ImageSharp.Tests.Drawing config.ParallelOptions.MaxDegreeOfParallelism = 1; using (Image image = new Image(config, 200, 200)) { - using (FileStream output = File.OpenWrite($"{path}/clipped-corner.png")) - { - image - .Fill(Rgba32.Blue) - .FillPolygon(Rgba32.HotPink, new[] - { + image + .Fill(Rgba32.Blue) + .FillPolygon(Rgba32.HotPink, new[] + { new Vector2( 8, 8 ), new Vector2( 64, 8 ), new Vector2( 64, 64 ), new Vector2( 120, 64 ), new Vector2( 120, 120 ), new Vector2( 8, 120 ) - } ) - .Save(output); - } + }) + .Save($"{path}/clipped-corner.png"); } } } diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs index cf073d3d03..e5af28d8b4 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs @@ -31,7 +31,7 @@ namespace ImageSharp.Tests string filename = file.GetFileNameWithoutExtension(bitsPerPixel); using (Image image = file.CreateImage()) { - image.Save($"{path}/{filename}.bmp", new BmpEncoderOptions { BitsPerPixel = bitsPerPixel }); + image.Save($"{path}/{filename}.bmp", new BmpEncoder { BitsPerPixel = bitsPerPixel }); } } } diff --git a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs index ec1a8c4659..659aa317b8 100644 --- a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs +++ b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs @@ -36,7 +36,7 @@ namespace ImageSharp.Tests using (Image image = file.CreateImage()) { string filename = path + "/" + file.FileNameWithoutExtension + ".txt"; - File.WriteAllText(filename, image.ToBase64String()); + File.WriteAllText(filename, image.ToBase64String("image/png")); } } } @@ -50,10 +50,7 @@ namespace ImageSharp.Tests { using (Image image = file.CreateImage()) { - using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) - { - image.Save(output); - } + image.Save($"{path}/{file.FileName}"); } } } @@ -65,14 +62,14 @@ namespace ImageSharp.Tests foreach (TestFile file in Files) { - using (Image srcImage = file.CreateImage()) + using (Image srcImage = Image.Load(file.Bytes, out var mimeType)) { using (Image image = new Image(srcImage)) { using (FileStream output = File.OpenWrite($"{path}/Octree-{file.FileName}")) { image.Quantize(Quantization.Octree) - .Save(output, image.CurrentImageFormat); + .Save(output, mimeType); } } @@ -82,7 +79,7 @@ namespace ImageSharp.Tests using (FileStream output = File.OpenWrite($"{path}/Wu-{file.FileName}")) { image.Quantize(Quantization.Wu) - .Save(output, image.CurrentImageFormat); + .Save(output, mimeType); } } @@ -91,7 +88,7 @@ namespace ImageSharp.Tests using (FileStream output = File.OpenWrite($"{path}/Palette-{file.FileName}")) { image.Quantize(Quantization.Palette) - .Save(output, image.CurrentImageFormat); + .Save(output, mimeType); } } } @@ -138,18 +135,17 @@ namespace ImageSharp.Tests foreach (TestFile file in Files) { byte[] serialized; - using (Image image = file.CreateImage()) + using (Image image = Image.Load(file.Bytes, out string mimeType)) using (MemoryStream memoryStream = new MemoryStream()) { - image.Save(memoryStream); + image.Save(memoryStream, mimeType); memoryStream.Flush(); serialized = memoryStream.ToArray(); } using (Image image2 = Image.Load(serialized)) - using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) { - image2.Save(output); + image2.Save($"{path}/{file.FileName}"); } } } diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs index 89df2d086e..06bfd8990d 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs @@ -33,7 +33,7 @@ namespace ImageSharp.Tests [Fact] public void Decode_IgnoreMetadataIsFalse_CommentsAreRead() { - DecoderOptions options = new DecoderOptions() + GifDecoder options = new GifDecoder() { IgnoreMetadata = false }; @@ -51,7 +51,7 @@ namespace ImageSharp.Tests [Fact] public void Decode_IgnoreMetadataIsTrue_CommentsAreIgnored() { - DecoderOptions options = new DecoderOptions() + GifDecoder options = new GifDecoder() { IgnoreMetadata = true }; @@ -67,7 +67,7 @@ namespace ImageSharp.Tests [Fact] public void Decode_TextEncodingSetToUnicode_TextIsReadWithCorrectEncoding() { - GifDecoderOptions options = new GifDecoderOptions() + GifDecoder options = new GifDecoder() { TextEncoding = Encoding.Unicode }; diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs index 70cc8e2ba6..c365396863 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs @@ -29,7 +29,7 @@ namespace ImageSharp.Tests [Fact] public void Encode_IgnoreMetadataIsFalse_CommentsAreWritten() { - EncoderOptions options = new EncoderOptions() + GifEncoder options = new GifEncoder() { IgnoreMetadata = false }; @@ -40,7 +40,7 @@ namespace ImageSharp.Tests { using (MemoryStream memStream = new MemoryStream()) { - input.Save(memStream, new GifFormat(), options); + input.Save(memStream, options); memStream.Position = 0; using (Image output = Image.Load(memStream)) @@ -56,7 +56,7 @@ namespace ImageSharp.Tests [Fact] public void Encode_IgnoreMetadataIsTrue_CommentsAreNotWritten() { - GifEncoderOptions options = new GifEncoderOptions() + GifEncoder options = new GifEncoder() { IgnoreMetadata = true }; @@ -88,7 +88,7 @@ namespace ImageSharp.Tests using (MemoryStream memStream = new MemoryStream()) { - input.Save(memStream, new GifFormat()); + input.Save(memStream, new GifEncoder()); memStream.Position = 0; using (Image output = Image.Load(memStream)) diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs index 446c5e96ae..ab4850ecf4 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs @@ -62,13 +62,12 @@ namespace ImageSharp.Tests byte[] data; using (Image image = provider.GetImage()) { - JpegEncoder encoder = new JpegEncoder(); - JpegEncoderOptions options = new JpegEncoderOptions { Subsample = subsample, Quality = quality }; + JpegEncoder encoder = new JpegEncoder { Subsample = subsample, Quality = quality }; data = new byte[65536]; using (MemoryStream ms = new MemoryStream(data)) { - image.Save(ms, encoder, options); + image.Save(ms, encoder); } } @@ -91,7 +90,7 @@ namespace ImageSharp.Tests image.Save(ms, new JpegEncoder()); ms.Seek(0, SeekOrigin.Begin); - using (JpegDecoderCore decoder = new JpegDecoderCore(null, null)) + using (JpegDecoderCore decoder = new JpegDecoderCore(null)) { Image mirror = decoder.Decode(ms); @@ -125,14 +124,14 @@ namespace ImageSharp.Tests [Fact] public void Decode_IgnoreMetadataIsFalse_ExifProfileIsRead() { - DecoderOptions options = new DecoderOptions() + JpegDecoder decoder = new JpegDecoder() { IgnoreMetadata = false }; TestFile testFile = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan); - using (Image image = testFile.CreateImage(options)) + using (Image image = testFile.CreateImage(decoder)) { Assert.NotNull(image.MetaData.ExifProfile); } @@ -141,7 +140,7 @@ namespace ImageSharp.Tests [Fact] public void Decode_IgnoreMetadataIsTrue_ExifProfileIgnored() { - DecoderOptions options = new DecoderOptions() + JpegDecoder options = new JpegDecoder() { IgnoreMetadata = true }; diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs index ab81c69b42..feb5f91b21 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs @@ -37,14 +37,12 @@ namespace ImageSharp.Tests { using (Image image = provider.GetImage().Resize(new ResizeOptions { Size = new Size(150, 100), Mode = ResizeMode.Max })) { - image.MetaData.Quality = quality; image.MetaData.ExifProfile = null; // Reduce the size of the file - JpegEncoder encoder = new JpegEncoder(); - JpegEncoderOptions options = new JpegEncoderOptions { Subsample = subsample, Quality = quality }; + JpegEncoder options = new JpegEncoder { Subsample = subsample, Quality = quality }; provider.Utility.TestName += $"{subsample}_Q{quality}"; provider.Utility.SaveTestOutputFile(image, "png"); - provider.Utility.SaveTestOutputFile(image, "jpg", encoder, options); + provider.Utility.SaveTestOutputFile(image, "jpg", options); } } @@ -61,9 +59,7 @@ namespace ImageSharp.Tests using (FileStream outputStream = File.OpenWrite(utility.GetTestOutputFileName("jpg"))) { - JpegEncoder encoder = new JpegEncoder(); - - image.Save(outputStream, encoder, new JpegEncoderOptions() + image.Save(outputStream, new JpegEncoder() { Subsample = subSample, Quality = quality @@ -75,7 +71,7 @@ namespace ImageSharp.Tests [Fact] public void Encode_IgnoreMetadataIsFalse_ExifProfileIsWritten() { - EncoderOptions options = new EncoderOptions() + JpegEncoder options = new JpegEncoder() { IgnoreMetadata = false }; @@ -86,7 +82,7 @@ namespace ImageSharp.Tests { using (MemoryStream memStream = new MemoryStream()) { - input.Save(memStream, new JpegFormat(), options); + input.Save(memStream, options); memStream.Position = 0; using (Image output = Image.Load(memStream)) @@ -100,7 +96,7 @@ namespace ImageSharp.Tests [Fact] public void Encode_IgnoreMetadataIsTrue_ExifProfileIgnored() { - JpegEncoderOptions options = new JpegEncoderOptions() + JpegEncoder options = new JpegEncoder() { IgnoreMetadata = true }; diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs index f3412e45e2..daf8da6a38 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs @@ -82,9 +82,8 @@ namespace ImageSharp.Tests { foreach (Image img in testImages) { - JpegEncoder encoder = new JpegEncoder(); - JpegEncoderOptions options = new JpegEncoderOptions { Quality = quality, Subsample = subsample }; - img.Save(ms, encoder, options); + JpegEncoder options = new JpegEncoder { Quality = quality, Subsample = subsample }; + img.Save(ms, options); ms.Seek(0, SeekOrigin.Begin); } }, diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs index 5ab1fac2f0..6d82b53746 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs @@ -35,7 +35,7 @@ namespace ImageSharp.Tests [Fact] public void Decode_IgnoreMetadataIsFalse_TextChunckIsRead() { - PngDecoderOptions options = new PngDecoderOptions() + PngDecoder options = new PngDecoder() { IgnoreMetadata = false }; @@ -53,7 +53,7 @@ namespace ImageSharp.Tests [Fact] public void Decode_IgnoreMetadataIsTrue_TextChunksAreIgnored() { - PngDecoderOptions options = new PngDecoderOptions() + PngDecoder options = new PngDecoder() { IgnoreMetadata = true }; @@ -69,7 +69,7 @@ namespace ImageSharp.Tests [Fact] public void Decode_TextEncodingSetToUnicode_TextIsReadWithCorrectEncoding() { - PngDecoderOptions options = new PngDecoderOptions() + PngDecoder options = new PngDecoder() { TextEncoding = Encoding.Unicode }; diff --git a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs index 02edf7688f..24907cfdb7 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs @@ -31,13 +31,13 @@ namespace ImageSharp.Tests { using (Image image = provider.GetImage()) { - PngEncoderOptions options = new PngEncoderOptions() + PngEncoder options = new PngEncoder() { PngColorType = pngColorType }; provider.Utility.TestName += "_" + pngColorType; - provider.Utility.SaveTestOutputFile(image, "png", new PngEncoder(), options); + provider.Utility.SaveTestOutputFile(image, "png", options); } } diff --git a/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs index 90f994f366..bb7824914c 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs @@ -50,8 +50,7 @@ namespace ImageSharp.Tests.Formats.Png using (MemoryStream ms = new MemoryStream()) { // image.Save(provider.Utility.GetTestOutputFileName("bmp")); - image.MetaData.Quality = 256; - image.Save(ms, new PngEncoder()); + image.Save(ms, new PngEncoder() { Quality = 256 }); ms.Position = 0; using (Image img2 = Image.Load(ms, new PngDecoder())) { diff --git a/tests/ImageSharp.Tests/Image/ImageLoadTests.cs b/tests/ImageSharp.Tests/Image/ImageLoadTests.cs index 7a5b8ffc53..65c2cc52cf 100644 --- a/tests/ImageSharp.Tests/Image/ImageLoadTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageLoadTests.cs @@ -20,10 +20,8 @@ namespace ImageSharp.Tests public class ImageLoadTests : IDisposable { private readonly Mock fileSystem; - private readonly IDecoderOptions decoderOptions; private Image returnImage; private Mock localDecoder; - private Mock localFormat; private readonly string FilePath; public Configuration LocalConfiguration { get; private set; } @@ -36,18 +34,14 @@ namespace ImageSharp.Tests this.returnImage = new Image(1, 1); this.localDecoder = new Mock(); - this.localFormat = new Mock(); - this.localFormat.Setup(x => x.Decoder).Returns(this.localDecoder.Object); - this.localFormat.Setup(x => x.Encoder).Returns(new Mock().Object); - this.localFormat.Setup(x => x.MimeType).Returns("img/test"); - this.localFormat.Setup(x => x.Extension).Returns("png"); - this.localFormat.Setup(x => x.HeaderSize).Returns(1); - this.localFormat.Setup(x => x.IsSupportedFileFormat(It.IsAny())).Returns(true); - this.localFormat.Setup(x => x.SupportedExtensions).Returns(new string[] { "png", "jpg" }); - - this.localDecoder.Setup(x => x.Decode(It.IsAny(), It.IsAny(), It.IsAny())) - - .Callback((c, s, o) => { + this.localDecoder.Setup(x => x.MimeTypes).Returns(new[] { "img/test" }); + this.localDecoder.Setup(x => x.FileExtensions).Returns(new[] { "png", "jpg" }); + this.localDecoder.Setup(x => x.HeaderSize).Returns(1); + this.localDecoder.Setup(x => x.IsSupportedFileFormat(It.IsAny>())).Returns(true); + + this.localDecoder.Setup(x => x.Decode(It.IsAny(), It.IsAny())) + + .Callback((c, s) => { using (var ms = new MemoryStream()) { s.CopyTo(ms); @@ -58,14 +52,16 @@ namespace ImageSharp.Tests this.fileSystem = new Mock(); - this.LocalConfiguration = new Configuration(this.localFormat.Object) + this.LocalConfiguration = new Configuration() { FileSystem = this.fileSystem.Object }; + + this.LocalConfiguration.AddImageFormat(this.localDecoder.Object); + TestFormat.RegisterGloablTestFormat(); this.Marker = Guid.NewGuid().ToByteArray(); this.DataStream = TestFormat.GlobalTestFormat.CreateStream(this.Marker); - this.decoderOptions = new Mock().Object; this.FilePath = Guid.NewGuid().ToString(); this.fileSystem.Setup(x => x.OpenRead(this.FilePath)).Returns(this.DataStream); @@ -80,10 +76,8 @@ namespace ImageSharp.Tests Image img = Image.Load(this.DataStream); Assert.NotNull(img); - Assert.Equal(TestFormat.GlobalTestFormat, img.CurrentImageFormat); - - TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, null, Configuration.Default); + TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, Configuration.Default); } @@ -94,10 +88,8 @@ namespace ImageSharp.Tests Image img = Image.Load(stream); Assert.NotNull(img); - Assert.Equal(TestFormat.GlobalTestFormat, img.CurrentImageFormat); - - TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, null, Configuration.Default); + TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, Configuration.Default); } @@ -108,36 +100,11 @@ namespace ImageSharp.Tests Assert.NotNull(img); Assert.Equal(TestFormat.GlobalTestFormat.Sample(), img); - Assert.Equal(TestFormat.GlobalTestFormat, img.CurrentImageFormat); - TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, null, Configuration.Default); - - } - - [Fact] - public void LoadFromStreamWithOptions() - { - Image img = Image.Load(this.DataStream, this.decoderOptions); - - Assert.NotNull(img); - Assert.Equal(TestFormat.GlobalTestFormat, img.CurrentImageFormat); - - TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, this.decoderOptions, Configuration.Default); - - } - - [Fact] - public void LoadFromStreamWithTypeAndOptions() - { - Image img = Image.Load(this.DataStream, this.decoderOptions); - - Assert.NotNull(img); - Assert.Equal(TestFormat.GlobalTestFormat.Sample(), img); - Assert.Equal(TestFormat.GlobalTestFormat, img.CurrentImageFormat); - - TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, this.decoderOptions, Configuration.Default); + TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, Configuration.Default); } + [Fact] public void LoadFromStreamWithConfig() @@ -146,9 +113,8 @@ namespace ImageSharp.Tests Image img = Image.Load(this.LocalConfiguration, stream); Assert.NotNull(img); - Assert.Equal(this.localFormat.Object, img.CurrentImageFormat); - this.localDecoder.Verify(x => x.Decode(this.LocalConfiguration, stream, null)); + this.localDecoder.Verify(x => x.Decode(this.LocalConfiguration, stream)); } @@ -160,40 +126,11 @@ namespace ImageSharp.Tests Assert.NotNull(img); Assert.Equal(this.returnImage, img); - Assert.Equal(this.localFormat.Object, img.CurrentImageFormat); - this.localDecoder.Verify(x => x.Decode(this.LocalConfiguration, stream, null)); + this.localDecoder.Verify(x => x.Decode(this.LocalConfiguration, stream)); } - [Fact] - public void LoadFromStreamWithConfigAndOptions() - { - Stream stream = new MemoryStream(); - Image img = Image.Load(this.LocalConfiguration, stream, this.decoderOptions); - - Assert.NotNull(img); - Assert.Equal(this.localFormat.Object, img.CurrentImageFormat); - - this.localDecoder.Verify(x => x.Decode(this.LocalConfiguration, stream, this.decoderOptions)); - - } - - [Fact] - public void LoadFromStreamWithTypeAndConfigAndOptions() - { - Stream stream = new MemoryStream(); - Image img = Image.Load(this.LocalConfiguration, stream, this.decoderOptions); - - Assert.NotNull(img); - Assert.Equal(this.returnImage, img); - Assert.Equal(this.localFormat.Object, img.CurrentImageFormat); - - this.localDecoder.Verify(x => x.Decode(this.LocalConfiguration, stream, this.decoderOptions)); - - } - - [Fact] public void LoadFromStreamWithDecoder() @@ -202,7 +139,7 @@ namespace ImageSharp.Tests Image img = Image.Load(stream, this.localDecoder.Object); Assert.NotNull(img); - this.localDecoder.Verify(x => x.Decode(Configuration.Default, stream, null)); + this.localDecoder.Verify(x => x.Decode(Configuration.Default, stream)); } [Fact] @@ -213,28 +150,7 @@ namespace ImageSharp.Tests Assert.NotNull(img); Assert.Equal(this.returnImage, img); - this.localDecoder.Verify(x => x.Decode(Configuration.Default, 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(Configuration.Default, 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(Configuration.Default, stream, this.decoderOptions)); + this.localDecoder.Verify(x => x.Decode(Configuration.Default, stream)); } [Fact] @@ -243,10 +159,9 @@ namespace ImageSharp.Tests Image img = Image.Load(this.DataStream.ToArray()); Assert.NotNull(img); - Assert.Equal(TestFormat.GlobalTestFormat, img.CurrentImageFormat); - TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, null, Configuration.Default); + TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, Configuration.Default); } @@ -257,34 +172,8 @@ namespace ImageSharp.Tests Assert.NotNull(img); Assert.Equal(TestFormat.GlobalTestFormat.Sample(), img); - Assert.Equal(TestFormat.GlobalTestFormat, img.CurrentImageFormat); - - TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, null, Configuration.Default); - - } - - [Fact] - public void LoadFromBytesWithOptions() - { - Image img = Image.Load(this.DataStream.ToArray(), this.decoderOptions); - - Assert.NotNull(img); - Assert.Equal(TestFormat.GlobalTestFormat, img.CurrentImageFormat); - - TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, this.decoderOptions, Configuration.Default); - - } - - [Fact] - public void LoadFromBytesWithTypeAndOptions() - { - Image img = Image.Load(this.DataStream.ToArray(), this.decoderOptions); - - Assert.NotNull(img); - Assert.Equal(TestFormat.GlobalTestFormat.Sample(), img); - Assert.Equal(TestFormat.GlobalTestFormat, img.CurrentImageFormat); - TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, this.decoderOptions, Configuration.Default); + TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, Configuration.Default); } @@ -294,9 +183,8 @@ namespace ImageSharp.Tests Image img = Image.Load(this.LocalConfiguration, this.DataStream.ToArray()); Assert.NotNull(img); - Assert.Equal(this.localFormat.Object, img.CurrentImageFormat); - this.localDecoder.Verify(x => x.Decode(this.LocalConfiguration, It.IsAny(), null)); + this.localDecoder.Verify(x => x.Decode(this.LocalConfiguration, It.IsAny())); Assert.Equal(this.DataStream.ToArray(), this.DecodedData); } @@ -308,49 +196,19 @@ namespace ImageSharp.Tests Assert.NotNull(img); Assert.Equal(this.returnImage, img); - Assert.Equal(this.localFormat.Object, img.CurrentImageFormat); - - this.localDecoder.Verify(x => x.Decode(this.LocalConfiguration, It.IsAny(), null)); + this.localDecoder.Verify(x => x.Decode(this.LocalConfiguration, It.IsAny())); Assert.Equal(this.DataStream.ToArray(), this.DecodedData); } - [Fact] - public void LoadFromBytesWithConfigAndOptions() - { - Image img = Image.Load(this.LocalConfiguration, this.DataStream.ToArray(), this.decoderOptions); - - Assert.NotNull(img); - Assert.Equal(this.localFormat.Object, img.CurrentImageFormat); - - this.localDecoder.Verify(x => x.Decode(this.LocalConfiguration, It.IsAny(), this.decoderOptions)); - - Assert.Equal(this.DataStream.ToArray(), this.DecodedData); - } - - [Fact] - public void LoadFromBytesWithTypeAndConfigAndOptions() - { - Image img = Image.Load(this.LocalConfiguration, this.DataStream.ToArray(), this.decoderOptions); - - Assert.NotNull(img); - Assert.Equal(this.returnImage, img); - Assert.Equal(this.localFormat.Object, img.CurrentImageFormat); - - this.localDecoder.Verify(x => x.Decode(this.LocalConfiguration, 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(Configuration.Default, It.IsAny(), null)); + this.localDecoder.Verify(x => x.Decode(Configuration.Default, It.IsAny())); Assert.Equal(this.DataStream.ToArray(), this.DecodedData); } @@ -361,28 +219,7 @@ namespace ImageSharp.Tests Assert.NotNull(img); Assert.Equal(this.returnImage, img); - this.localDecoder.Verify(x => x.Decode(Configuration.Default, 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(Configuration.Default, 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(Configuration.Default, It.IsAny(), this.decoderOptions)); + this.localDecoder.Verify(x => x.Decode(Configuration.Default, It.IsAny())); Assert.Equal(this.DataStream.ToArray(), this.DecodedData); } @@ -392,10 +229,9 @@ namespace ImageSharp.Tests Image img = Image.Load(this.DataStream); Assert.NotNull(img); - Assert.Equal(TestFormat.GlobalTestFormat, img.CurrentImageFormat); - TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, null, Configuration.Default); + TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, Configuration.Default); } @@ -406,35 +242,8 @@ namespace ImageSharp.Tests Assert.NotNull(img); Assert.Equal(TestFormat.GlobalTestFormat.Sample(), img); - Assert.Equal(TestFormat.GlobalTestFormat, img.CurrentImageFormat); - - TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, null, Configuration.Default); - - } - - [Fact] - public void LoadFromFileWithOptions() - { - Image img = Image.Load(this.DataStream, this.decoderOptions); - - Assert.NotNull(img); - Assert.Equal(TestFormat.GlobalTestFormat, img.CurrentImageFormat); - - TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, this.decoderOptions, Configuration.Default); - - } - - [Fact] - public void LoadFromFileWithTypeAndOptions() - { - Image img = Image.Load(this.DataStream, this.decoderOptions); - - Assert.NotNull(img); - Assert.Equal(TestFormat.GlobalTestFormat.Sample(), img); - Assert.Equal(TestFormat.GlobalTestFormat, img.CurrentImageFormat); - - TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, this.decoderOptions, Configuration.Default); + TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, Configuration.Default); } [Fact] @@ -443,9 +252,8 @@ namespace ImageSharp.Tests Image img = Image.Load(this.LocalConfiguration, this.FilePath); Assert.NotNull(img); - Assert.Equal(this.localFormat.Object, img.CurrentImageFormat); - this.localDecoder.Verify(x => x.Decode(this.LocalConfiguration, this.DataStream, null)); + this.localDecoder.Verify(x => x.Decode(this.LocalConfiguration, this.DataStream)); } @@ -456,45 +264,17 @@ namespace ImageSharp.Tests Assert.NotNull(img); Assert.Equal(this.returnImage, img); - Assert.Equal(this.localFormat.Object, img.CurrentImageFormat); - - this.localDecoder.Verify(x => x.Decode(this.LocalConfiguration, this.DataStream, null)); - - } - - [Fact] - public void LoadFromFileWithConfigAndOptions() - { - Image img = Image.Load(this.LocalConfiguration, this.FilePath, this.decoderOptions); - - Assert.NotNull(img); - Assert.Equal(this.localFormat.Object, img.CurrentImageFormat); - - this.localDecoder.Verify(x => x.Decode(this.LocalConfiguration, this.DataStream, this.decoderOptions)); - - } - - [Fact] - public void LoadFromFileWithTypeAndConfigAndOptions() - { - Image img = Image.Load(this.LocalConfiguration, this.FilePath, this.decoderOptions); - - Assert.NotNull(img); - Assert.Equal(this.returnImage, img); - Assert.Equal(this.localFormat.Object, img.CurrentImageFormat); - - this.localDecoder.Verify(x => x.Decode(this.LocalConfiguration, this.DataStream, this.decoderOptions)); + this.localDecoder.Verify(x => x.Decode(this.LocalConfiguration, this.DataStream)); } - [Fact] public void LoadFromFileWithDecoder() { Image img = Image.Load(this.FilePath, this.localDecoder.Object); Assert.NotNull(img); - this.localDecoder.Verify(x => x.Decode(Configuration.Default, this.DataStream, null)); + this.localDecoder.Verify(x => x.Decode(Configuration.Default, this.DataStream)); } [Fact] @@ -504,26 +284,7 @@ namespace ImageSharp.Tests Assert.NotNull(img); Assert.Equal(this.returnImage, img); - this.localDecoder.Verify(x => x.Decode(Configuration.Default, 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(Configuration.Default, 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(Configuration.Default, this.DataStream, this.decoderOptions)); + this.localDecoder.Verify(x => x.Decode(Configuration.Default, this.DataStream)); } [Fact] diff --git a/tests/ImageSharp.Tests/Image/ImageSaveTests.cs b/tests/ImageSharp.Tests/Image/ImageSaveTests.cs index 9e9852cb2c..46177ba5fa 100644 --- a/tests/ImageSharp.Tests/Image/ImageSaveTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageSaveTests.cs @@ -22,37 +22,27 @@ namespace ImageSharp.Tests { private readonly Image Image; private readonly Mock fileSystem; - private readonly Mock format; - private readonly Mock formatNotRegistered; private readonly Mock encoder; private readonly Mock encoderNotInFormat; - private readonly IEncoderOptions encoderOptions; public ImageSaveTests() { this.encoder = new Mock(); - this.format = new Mock(); - this.format.Setup(x => x.Encoder).Returns(this.encoder.Object); - this.format.Setup(x => x.Decoder).Returns(new Mock().Object); - this.format.Setup(x => x.MimeType).Returns("img/test"); - this.format.Setup(x => x.Extension).Returns("png"); - this.format.Setup(x => x.SupportedExtensions).Returns(new string[] { "png", "jpg" }); + this.encoder.Setup(x => x.MimeTypes).Returns(new[] { "img/test" }); + this.encoder.Setup(x => x.FileExtensions).Returns(new string[] { "png", "jpg" }); this.encoderNotInFormat = new Mock(); - this.formatNotRegistered = new Mock(); - this.formatNotRegistered.Setup(x => x.Encoder).Returns(this.encoderNotInFormat.Object); - this.formatNotRegistered.Setup(x => x.Decoder).Returns(new Mock().Object); - this.formatNotRegistered.Setup(x => x.MimeType).Returns("img/test"); - this.formatNotRegistered.Setup(x => x.Extension).Returns("png"); - this.formatNotRegistered.Setup(x => x.SupportedExtensions).Returns(new string[] { "png", "jpg" }); + this.encoderNotInFormat.Setup(x => x.MimeTypes).Returns(new[] { "img/test" }); + this.encoderNotInFormat.Setup(x => x.FileExtensions).Returns(new string[] { "png", "jpg" }); this.fileSystem = new Mock(); - this.encoderOptions = new Mock().Object; - this.Image = new Image(new Configuration(this.format.Object) + var config = new Configuration() { FileSystem = this.fileSystem.Object - }, 1, 1); + }; + config.AddImageFormat(this.encoder.Object); + this.Image = new Image(config, 1, 1); } [Fact] @@ -62,19 +52,9 @@ namespace ImageSharp.Tests this.fileSystem.Setup(x => x.Create("path.png")).Returns(stream); this.Image.Save("path.png"); - this.encoder.Verify(x => x.Encode(this.Image, stream, null)); + this.encoder.Verify(x => x.Encode(this.Image, stream)); } - [Fact] - public void SavePathWithOptions() - { - Stream stream = new MemoryStream(); - this.fileSystem.Setup(x => x.Create("path.jpg")).Returns(stream); - - this.Image.Save("path.jpg", this.encoderOptions); - - this.encoder.Verify(x => x.Encode(this.Image, stream, this.encoderOptions)); - } [Fact] public void SavePathWithEncoder() @@ -84,61 +64,24 @@ namespace ImageSharp.Tests this.Image.Save("path.jpg", this.encoderNotInFormat.Object); - this.encoderNotInFormat.Verify(x => x.Encode(this.Image, stream, null)); + this.encoderNotInFormat.Verify(x => x.Encode(this.Image, stream)); } [Fact] - public void SavePathWithEncoderAndOptions() + public void ToBase64String() { - Stream stream = new MemoryStream(); - this.fileSystem.Setup(x => x.Create("path.jpg")).Returns(stream); - - this.Image.Save("path.jpg", this.encoderNotInFormat.Object, this.encoderOptions); - - this.encoderNotInFormat.Verify(x => x.Encode(this.Image, stream, this.encoderOptions)); - } - - - - [Fact] - public void SavePathWithFormat() - { - Stream stream = new MemoryStream(); - this.fileSystem.Setup(x => x.Create("path.jpg")).Returns(stream); - - this.Image.Save("path.jpg", this.encoderNotInFormat.Object); - - this.encoderNotInFormat.Verify(x => x.Encode(this.Image, stream, null)); - } - - [Fact] - public void SavePathWithFormatAndOptions() - { - Stream stream = new MemoryStream(); - this.fileSystem.Setup(x => x.Create("path.jpg")).Returns(stream); - - this.Image.Save("path.jpg", this.encoderNotInFormat.Object, this.encoderOptions); + var str = this.Image.ToBase64String("img/test"); - this.encoderNotInFormat.Verify(x => x.Encode(this.Image, stream, this.encoderOptions)); + this.encoder.Verify(x => x.Encode(this.Image, It.IsAny())); } [Fact] - public void SaveStream() + public void SaveStreamWithMime() { Stream stream = new MemoryStream(); - this.Image.Save(stream); + this.Image.Save(stream, "img/test"); - this.encoder.Verify(x => x.Encode(this.Image, stream, null)); - } - - [Fact] - public void SaveStreamWithOptions() - { - Stream stream = new MemoryStream(); - - this.Image.Save(stream, this.encoderOptions); - - this.encoder.Verify(x => x.Encode(this.Image, stream, this.encoderOptions)); + this.encoder.Verify(x => x.Encode(this.Image, stream)); } [Fact] @@ -148,37 +91,7 @@ namespace ImageSharp.Tests this.Image.Save(stream, this.encoderNotInFormat.Object); - this.encoderNotInFormat.Verify(x => x.Encode(this.Image, stream, null)); - } - - [Fact] - public void SaveStreamWithEncoderAndOptions() - { - Stream stream = new MemoryStream(); - - this.Image.Save(stream, this.encoderNotInFormat.Object, this.encoderOptions); - - this.encoderNotInFormat.Verify(x => x.Encode(this.Image, stream, this.encoderOptions)); - } - - [Fact] - public void SaveStreamWithFormat() - { - Stream stream = new MemoryStream(); - - this.Image.Save(stream, this.formatNotRegistered.Object); - - this.encoderNotInFormat.Verify(x => x.Encode(this.Image, stream, null)); - } - - [Fact] - public void SaveStreamWithFormatAndOptions() - { - Stream stream = new MemoryStream(); - - this.Image.Save(stream, this.formatNotRegistered.Object, this.encoderOptions); - - this.encoderNotInFormat.Verify(x => x.Encode(this.Image, stream, this.encoderOptions)); + this.encoderNotInFormat.Verify(x => x.Encode(this.Image, stream)); } public void Dispose() diff --git a/tests/ImageSharp.Tests/Image/ImageTests.cs b/tests/ImageSharp.Tests/Image/ImageTests.cs index a3ec4cec21..cb8607c091 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.cs @@ -74,10 +74,9 @@ namespace ImageSharp.Tests image.Save(file); } - TestFile c = TestFile.Create("../../TestOutput/Save_DetecedEncoding.png"); - using (Image img = c.CreateImage()) + using (Image img = Image.Load(file, out var mime)) { - Assert.IsType(img.CurrentImageFormat); + Assert.Equal("image/png", mime); } } @@ -85,7 +84,7 @@ namespace ImageSharp.Tests public void Save_UnknownExtensionsEncoding() { string file = TestFile.GetPath("../../TestOutput/Save_DetecedEncoding.tmp"); - InvalidOperationException ex = Assert.Throws( + NotSupportedException ex = Assert.Throws( () => { using (Image image = new Image(10, 10)) @@ -95,23 +94,6 @@ namespace ImageSharp.Tests }); } - [Fact] - public void Save_SetFormat() - { - string file = TestFile.GetPath("../../TestOutput/Save_SetFormat.dat"); - System.IO.DirectoryInfo dir = System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(file)); - using (Image image = new Image(10, 10)) - { - image.Save(file, new PngFormat()); - } - - TestFile c = TestFile.Create("../../TestOutput/Save_SetFormat.dat"); - using (Image img = c.CreateImage()) - { - Assert.IsType(img.CurrentImageFormat); - } - } - [Fact] public void Save_SetEncoding() { @@ -121,11 +103,9 @@ namespace ImageSharp.Tests { image.Save(file, new PngEncoder()); } - - TestFile c = TestFile.Create("../../TestOutput/Save_SetEncoding.dat"); - using (Image img = c.CreateImage()) + using (Image img = Image.Load(file, out var mime)) { - Assert.IsType(img.CurrentImageFormat); + Assert.Equal("image/png", mime); } } } diff --git a/tests/ImageSharp.Tests/MetaData/ImageMetaDataTests.cs b/tests/ImageSharp.Tests/MetaData/ImageMetaDataTests.cs index bc64d613ab..c60c8978ce 100644 --- a/tests/ImageSharp.Tests/MetaData/ImageMetaDataTests.cs +++ b/tests/ImageSharp.Tests/MetaData/ImageMetaDataTests.cs @@ -28,7 +28,6 @@ namespace ImageSharp.Tests metaData.HorizontalResolution = 4; metaData.VerticalResolution = 2; metaData.Properties.Add(imageProperty); - metaData.Quality = 24; metaData.RepeatCount = 1; metaData.DisposalMethod = DisposalMethod.RestoreToBackground; @@ -39,7 +38,6 @@ namespace ImageSharp.Tests Assert.Equal(4, clone.HorizontalResolution); Assert.Equal(2, clone.VerticalResolution); Assert.Equal(imageProperty, clone.Properties[0]); - Assert.Equal(24, clone.Quality); Assert.Equal(1, clone.RepeatCount); Assert.Equal(DisposalMethod.RestoreToBackground, clone.DisposalMethod); } diff --git a/tests/ImageSharp.Tests/TestFile.cs b/tests/ImageSharp.Tests/TestFile.cs index f1b78383cb..c0f9deebb3 100644 --- a/tests/ImageSharp.Tests/TestFile.cs +++ b/tests/ImageSharp.Tests/TestFile.cs @@ -11,7 +11,7 @@ namespace ImageSharp.Tests using System.IO; using System.Linq; using System.Reflection; - + using ImageSharp.Formats; using ImageSharp.PixelFormats; /// @@ -135,13 +135,12 @@ namespace ImageSharp.Tests /// /// Creates a new image. /// - /// The options for the decoder. /// /// The . /// - public Image CreateImage(IDecoderOptions options) + public Image CreateImage(IImageDecoder decoder) { - return Image.Load(this.Bytes, options); + return Image.Load(this.image.Configuration, this.Bytes, decoder); } /// diff --git a/tests/ImageSharp.Tests/TestFormat.cs b/tests/ImageSharp.Tests/TestFormat.cs index 318df5ead3..1d06f7328a 100644 --- a/tests/ImageSharp.Tests/TestFormat.cs +++ b/tests/ImageSharp.Tests/TestFormat.cs @@ -19,13 +19,14 @@ namespace ImageSharp.Tests /// /// A test image file. /// - public class TestFormat : ImageSharp.Formats.IImageFormat + public class TestFormat { public static TestFormat GlobalTestFormat { get; } = new TestFormat(); public static void RegisterGloablTestFormat() { - Configuration.Default.AddImageFormat(GlobalTestFormat); + Configuration.Default.AddImageFormat(GlobalTestFormat.Encoder); + Configuration.Default.AddImageFormat(GlobalTestFormat.Decoder); } public TestFormat() @@ -58,9 +59,9 @@ namespace ImageSharp.Tests Dictionary _sampleImages = new Dictionary(); - public void VerifyDecodeCall(byte[] marker, IDecoderOptions options, Configuration config) + public void VerifyDecodeCall(byte[] marker, Configuration config) { - DecodeOperation[] discovered = this.DecodeCalls.Where(x => x.IsMatch(marker, options, config)).ToArray(); + DecodeOperation[] discovered = this.DecodeCalls.Where(x => x.IsMatch(marker, config)).ToArray(); Assert.True(discovered.Any(), "No calls to decode on this formate with the proveded options happend"); @@ -92,7 +93,7 @@ namespace ImageSharp.Tests public int HeaderSize => this.header.Length; - public bool IsSupportedFileFormat(byte[] header) + public bool IsSupportedFileFormat(Span header) { if (header.Length < this.header.Length) { @@ -110,15 +111,10 @@ namespace ImageSharp.Tests public struct DecodeOperation { public byte[] marker; - public IDecoderOptions options; internal Configuration config; - public bool IsMatch(byte[] testMarker, IDecoderOptions testOptions, Configuration config) + public bool IsMatch(byte[] testMarker, Configuration config) { - if (this.options != testOptions) - { - return false; - } if (this.config != config) { @@ -150,8 +146,13 @@ namespace ImageSharp.Tests this.testFormat = testFormat; } + public IEnumerable MimeTypes => new[] { testFormat.MimeType }; + + public IEnumerable FileExtensions => testFormat.SupportedExtensions; - public Image Decode(Configuration config, Stream stream, IDecoderOptions options) where TPixel : struct, IPixel + public int HeaderSize => testFormat.HeaderSize; + + public Image Decode(Configuration config, Stream stream) where TPixel : struct, IPixel { var ms = new MemoryStream(); @@ -160,13 +161,14 @@ namespace ImageSharp.Tests this.testFormat.DecodeCalls.Add(new DecodeOperation { marker = marker, - options = options, config = config }); // TODO record this happend so we an verify it. return this.testFormat.Sample(); } + + public bool IsSupportedFileFormat(Span header) => testFormat.IsSupportedFileFormat(header); } public class TestEncoder : ImageSharp.Formats.IImageEncoder @@ -178,7 +180,11 @@ namespace ImageSharp.Tests this.testFormat = testFormat; } - public void Encode(Image image, Stream stream, IEncoderOptions options) where TPixel : struct, IPixel + public IEnumerable MimeTypes => new[] { testFormat.MimeType }; + + public IEnumerable FileExtensions => testFormat.SupportedExtensions; + + public void Encode(Image image, Stream stream) where TPixel : struct, IPixel { // TODO record this happend so we an verify it. } diff --git a/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs b/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs index 2901238856..ee52466600 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs @@ -97,18 +97,16 @@ namespace ImageSharp.Tests /// The requested extension /// Optional encoder /// Optional encoder options - public void SaveTestOutputFile(Image image, string extension = null, IImageEncoder encoder = null, IEncoderOptions options = null, string tag = null) + public void SaveTestOutputFile(Image image, string extension = null, IImageEncoder encoder = null, string tag = null) where TPixel : struct, IPixel { string path = this.GetTestOutputFileName(extension: extension, tag:tag); extension = Path.GetExtension(path); - IImageFormat format = GetImageFormatByExtension(extension); - - encoder = encoder ?? format.Encoder; + encoder = encoder ?? GetImageFormatByExtension(extension); using (FileStream stream = File.OpenWrite(path)) { - image.Save(stream, encoder, options); + image.Save(stream, encoder); } } @@ -123,10 +121,10 @@ namespace ImageSharp.Tests this.Init(method.DeclaringType.Name, method.Name); } - private static IImageFormat GetImageFormatByExtension(string extension) + private static IImageEncoder GetImageFormatByExtension(string extension) { extension = extension?.TrimStart('.'); - return Configuration.Default.ImageFormats.First(f => f.SupportedExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase)); + return Configuration.Default.ImageEncoders.Last(f => f.FileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase)); } private string GetTestOutputDir() From 3499d7b6a2e2f5df4e55fd3aa8213b96d578f827 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Thu, 22 Jun 2017 20:00:09 +0100 Subject: [PATCH 02/36] provide IImageFormatProviders and split mimetype detection from decoders. --- src/ImageSharp/Configuration.cs | 180 +++++++++++++----- src/ImageSharp/Formats/Bmp/BmpDecoder.cs | 17 -- src/ImageSharp/Formats/Bmp/BmpEncoder.cs | 6 - .../Formats/Bmp/BmpImageFormatProvider.cs | 42 ++++ .../Formats/Bmp/BmpMimeTypeDetector.cs | 40 ++++ src/ImageSharp/Formats/Gif/GifDecoder.cs | 21 -- src/ImageSharp/Formats/Gif/GifEncoder.cs | 6 - .../Formats/Gif/GifImageFormatProvider.cs | 42 ++++ .../Formats/Gif/GifMimeTypeDetector.cs | 44 +++++ src/ImageSharp/Formats/IImageDecoder.cs | 26 --- src/ImageSharp/Formats/IImageEncoder.cs | 10 - .../Formats/IImageFormatProvider.cs | 56 ++++++ src/ImageSharp/Formats/IMimeTypeDetector.cs | 30 +++ src/ImageSharp/Formats/Jpeg/JpegDecoder.cs | 65 ------- src/ImageSharp/Formats/Jpeg/JpegEncoder.cs | 6 - .../Formats/Jpeg/JpegImageFormatProvider.cs | 42 ++++ .../Formats/Jpeg/JpegMimeTypeDetector.cs | 88 +++++++++ src/ImageSharp/Formats/Png/PngDecoder.cs | 23 --- src/ImageSharp/Formats/Png/PngEncoder.cs | 6 - .../Formats/Png/PngImageFormatProvider.cs | 42 ++++ .../Formats/Png/PngMimeTypeDetector.cs | 46 +++++ src/ImageSharp/Image/Image.Decode.cs | 26 ++- src/ImageSharp/Image/Image.FromBytes.cs | 24 +++ src/ImageSharp/Image/Image.FromFile.cs | 25 +++ src/ImageSharp/Image/Image.FromStream.cs | 33 +++- src/ImageSharp/Image/Image{TPixel}.cs | 22 +-- tests/ImageSharp.Tests/ConfigurationTests.cs | 155 +++++++++++---- .../ImageSharp.Tests/Image/ImageLoadTests.cs | 12 +- .../ImageSharp.Tests/Image/ImageSaveTests.cs | 14 +- tests/ImageSharp.Tests/TestFormat.cs | 45 ++++- .../TestUtilities/ImagingTestCaseUtility.cs | 2 +- 31 files changed, 884 insertions(+), 312 deletions(-) create mode 100644 src/ImageSharp/Formats/Bmp/BmpImageFormatProvider.cs create mode 100644 src/ImageSharp/Formats/Bmp/BmpMimeTypeDetector.cs create mode 100644 src/ImageSharp/Formats/Gif/GifImageFormatProvider.cs create mode 100644 src/ImageSharp/Formats/Gif/GifMimeTypeDetector.cs create mode 100644 src/ImageSharp/Formats/IImageFormatProvider.cs create mode 100644 src/ImageSharp/Formats/IMimeTypeDetector.cs create mode 100644 src/ImageSharp/Formats/Jpeg/JpegImageFormatProvider.cs create mode 100644 src/ImageSharp/Formats/Jpeg/JpegMimeTypeDetector.cs create mode 100644 src/ImageSharp/Formats/Png/PngImageFormatProvider.cs create mode 100644 src/ImageSharp/Formats/Png/PngMimeTypeDetector.cs diff --git a/src/ImageSharp/Configuration.cs b/src/ImageSharp/Configuration.cs index 5734d70fb8..43613867e5 100644 --- a/src/ImageSharp/Configuration.cs +++ b/src/ImageSharp/Configuration.cs @@ -6,6 +6,7 @@ namespace ImageSharp { using System; + using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; @@ -17,7 +18,7 @@ namespace ImageSharp /// /// Provides initialization code which allows extending the library. /// - public class Configuration + public class Configuration : IImageFormatHost { /// /// A lazily initialized configuration default instance. @@ -30,14 +31,24 @@ namespace ImageSharp private readonly object syncRoot = new object(); /// - /// The list of supported . + /// The list of supported keyed to mimestypes. /// - private readonly List encoders = new List(); + private readonly ConcurrentDictionary mimeTypeEncoders = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); /// - /// The list of supported . + /// The list of supported keyed to fiel extensions. /// - private readonly List decoders = new List(); + private readonly ConcurrentDictionary extensionsEncoders = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + + /// + /// The list of supported keyed to mimestypes. + /// + private readonly ConcurrentDictionary mimeTypeDecoders = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + + /// + /// The list of supported s. + /// + private readonly List mimeTypeDetectors = new List(); /// /// Initializes a new instance of the class. @@ -46,30 +57,55 @@ namespace ImageSharp { } + /// + /// Initializes a new instance of the class. + /// + /// A collection of providers to configure + public Configuration(params IImageFormatProvider[] providers) + { + if (providers != null) + { + foreach (IImageFormatProvider p in providers) + { + p.Configure(this); + } + } + } + /// /// Gets the default instance. /// public static Configuration Default { get; } = Lazy.Value; /// - /// Gets the collection of supported + /// Gets the global parallel options for processing tasks in parallel. /// - public IReadOnlyCollection ImageEncoders => new ReadOnlyCollection(this.encoders); + public ParallelOptions ParallelOptions { get; } = new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount }; /// - /// Gets the collection of supported + /// Gets the maximum header size of all formats. /// - public IReadOnlyCollection ImageDecoders => new ReadOnlyCollection(this.decoders); + internal int MaxHeaderSize { get; private set; } /// - /// Gets the global parallel options for processing tasks in parallel. + /// Gets the currently registerd s. /// - public ParallelOptions ParallelOptions { get; } = new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount }; + internal IEnumerable MimeTypeDetectors => this.mimeTypeDetectors; /// - /// Gets the maximum header size of all formats. + /// Gets the typeof of all the current image decoders /// - internal int MaxHeaderSize { get; private set; } + internal IEnumerable AllMimeImageDecoders => this.mimeTypeDecoders.Select(x => x.Value.GetType()).Distinct().ToList(); + + /// + /// Gets the typeof of all the current image decoders + /// + internal IEnumerable AllMimeImageEncoders => this.mimeTypeEncoders.Select(x => x.Value.GetType()).Distinct().ToList(); + + /// + /// Gets the typeof of all the current image decoders + /// + internal IEnumerable AllExtImageEncoders => this.mimeTypeEncoders.Select(x => x.Value.GetType()).Distinct().ToList(); #if !NETSTANDARD1_1 /// @@ -79,36 +115,102 @@ namespace ImageSharp #endif /// - /// Adds a new to the collection of supported image formats. + /// Registers a new format provider. /// - /// The new format to add. - public void AddImageFormat(IImageDecoder decoder) + /// The format providers to call configure on. + public void AddImageFormat(IImageFormatProvider formatProvider) + { + Guard.NotNull(formatProvider, nameof(formatProvider)); + formatProvider.Configure(this); + } + + /// + public void SetMimeTypeEncoder(string mimeType, IImageEncoder encoder) { + Guard.NotNullOrEmpty(mimeType, nameof(mimeType)); + Guard.NotNull(encoder, nameof(encoder)); + this.mimeTypeEncoders.AddOrUpdate(mimeType?.Trim(), encoder, (s, e) => encoder); + } + + /// + public void SetFileExtensionEncoder(string extension, IImageEncoder encoder) + { + Guard.NotNullOrEmpty(extension, nameof(extension)); + Guard.NotNull(encoder, nameof(encoder)); + this.extensionsEncoders.AddOrUpdate(extension?.Trim(), encoder, (s, e) => encoder); + } + + /// + public void SetMimeTypeDecoder(string mimeType, IImageDecoder decoder) + { + Guard.NotNullOrEmpty(mimeType, nameof(mimeType)); Guard.NotNull(decoder, nameof(decoder)); - Guard.NotNullOrEmpty(decoder.FileExtensions, nameof(decoder.FileExtensions)); - Guard.NotNullOrEmpty(decoder.MimeTypes, nameof(decoder.MimeTypes)); + this.mimeTypeDecoders.AddOrUpdate(mimeType, decoder, (s, e) => decoder); + } + + /// + /// Removes all the registerd detectors + /// + public void ClearMimeTypeDetector() + { + this.mimeTypeDetectors.Clear(); + } + + /// + public void AddMimeTypeDetector(IMimeTypeDetector detector) + { + Guard.NotNull(detector, nameof(detector)); + this.mimeTypeDetectors.Add(detector); + this.SetMaxHeaderSize(); + } - lock (this.syncRoot) + /// + /// For the specified mimetype find the decoder. + /// + /// the mimetype to discover + /// the IImageDecoder if found othersize null + public IImageDecoder FindMimeTypeDecoder(string mimeType) + { + Guard.NotNullOrEmpty(mimeType, nameof(mimeType)); + if (this.mimeTypeDecoders.TryGetValue(mimeType, out IImageDecoder dec)) { - this.decoders.Add(decoder); + return dec; + } + + return null; + } - this.SetMaxHeaderSize(); + /// + /// For the specified mimetype find the encoder. + /// + /// the mimetype to discover + /// the IImageEncoder if found othersize null + public IImageEncoder FindMimeTypeEncoder(string mimeType) + { + Guard.NotNullOrEmpty(mimeType, nameof(mimeType)); + if (this.mimeTypeEncoders.TryGetValue(mimeType, out IImageEncoder dec)) + { + return dec; } + + return null; } /// - /// Adds a new to the collection of supported image formats. + /// For the specified mimetype find the encoder. /// - /// The new format to add. - public void AddImageFormat(IImageEncoder encoder) + /// the extensions to discover + /// the IImageEncoder if found othersize null + public IImageEncoder FindFileExtensionsEncoder(string extensions) { - Guard.NotNull(encoder, nameof(encoder)); - Guard.NotNullOrEmpty(encoder.FileExtensions, nameof(encoder.FileExtensions)); - Guard.NotNullOrEmpty(encoder.MimeTypes, nameof(encoder.MimeTypes)); - lock (this.syncRoot) + extensions = extensions?.TrimStart('.'); + Guard.NotNullOrEmpty(extensions, nameof(extensions)); + if (this.extensionsEncoders.TryGetValue(extensions, out IImageEncoder dec)) { - this.encoders.Add(encoder); + return dec; } + + return null; } /// @@ -117,19 +219,11 @@ namespace ImageSharp /// The default configuration of internal static Configuration CreateDefaultInstance() { - Configuration config = new Configuration(); - - // lets try auto loading the known image formats - config.AddImageFormat(new Formats.PngEncoder()); - config.AddImageFormat(new Formats.JpegEncoder()); - config.AddImageFormat(new Formats.GifEncoder()); - config.AddImageFormat(new Formats.BmpEncoder()); - - config.AddImageFormat(new Formats.PngDecoder()); - config.AddImageFormat(new Formats.JpegDecoder()); - config.AddImageFormat(new Formats.GifDecoder()); - config.AddImageFormat(new Formats.BmpDecoder()); - return config; + return new Configuration( + new PngImageFormatProvider(), + new JpegImageFormatProvider(), + new GifImageFormatProvider(), + new BmpImageFormatProvider()); } /// @@ -137,7 +231,7 @@ namespace ImageSharp /// private void SetMaxHeaderSize() { - this.MaxHeaderSize = this.decoders.Max(x => x.HeaderSize); + this.MaxHeaderSize = this.mimeTypeDetectors.Max(x => x.HeaderSize); } } } diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoder.cs b/src/ImageSharp/Formats/Bmp/BmpDecoder.cs index 9ff331490b..e1dc489f40 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoder.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoder.cs @@ -28,23 +28,6 @@ namespace ImageSharp.Formats /// public class BmpDecoder : IImageDecoder { - /// - public IEnumerable MimeTypes => BmpConstants.MimeTypes; - - /// - public IEnumerable FileExtensions => BmpConstants.FileExtensions; - - /// - public int HeaderSize => 2; - - /// - public bool IsSupportedFileFormat(Span header) - { - return header.Length >= this.HeaderSize && - header[0] == 0x42 && // B - header[1] == 0x4D; // M - } - /// public Image Decode(Configuration configuration, Stream stream) diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoder.cs b/src/ImageSharp/Formats/Bmp/BmpEncoder.cs index 25db0eda05..f47bedb818 100644 --- a/src/ImageSharp/Formats/Bmp/BmpEncoder.cs +++ b/src/ImageSharp/Formats/Bmp/BmpEncoder.cs @@ -22,12 +22,6 @@ namespace ImageSharp.Formats /// public BmpBitsPerPixel BitsPerPixel { get; set; } = BmpBitsPerPixel.Pixel24; - /// - public IEnumerable MimeTypes => BmpConstants.MimeTypes; - - /// - public IEnumerable FileExtensions => BmpConstants.FileExtensions; - /// public void Encode(Image image, Stream stream) where TPixel : struct, IPixel diff --git a/src/ImageSharp/Formats/Bmp/BmpImageFormatProvider.cs b/src/ImageSharp/Formats/Bmp/BmpImageFormatProvider.cs new file mode 100644 index 0000000000..145e1fdb67 --- /dev/null +++ b/src/ImageSharp/Formats/Bmp/BmpImageFormatProvider.cs @@ -0,0 +1,42 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Text; + using ImageSharp.PixelFormats; + + /// + /// Detects gif file headers + /// + public class BmpImageFormatProvider : IImageFormatProvider + { + /// + public void Configure(IImageFormatHost host) + { + var encoder = new BmpEncoder(); + foreach (string mimeType in BmpConstants.MimeTypes) + { + host.SetMimeTypeEncoder(mimeType, encoder); + } + + foreach (string mimeType in BmpConstants.FileExtensions) + { + host.SetFileExtensionEncoder(mimeType, encoder); + } + + var decoder = new BmpDecoder(); + foreach (string mimeType in BmpConstants.MimeTypes) + { + host.SetMimeTypeDecoder(mimeType, decoder); + } + + host.AddMimeTypeDetector(new BmpMimeTypeDetector()); + } + } +} diff --git a/src/ImageSharp/Formats/Bmp/BmpMimeTypeDetector.cs b/src/ImageSharp/Formats/Bmp/BmpMimeTypeDetector.cs new file mode 100644 index 0000000000..c13181d6a2 --- /dev/null +++ b/src/ImageSharp/Formats/Bmp/BmpMimeTypeDetector.cs @@ -0,0 +1,40 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Text; + using ImageSharp.PixelFormats; + + /// + /// Detects bmp file headers + /// + internal class BmpMimeTypeDetector : IMimeTypeDetector + { + /// + public int HeaderSize => 2; + + /// + public string DetectMimeType(Span header) + { + if (this.IsSupportedFileFormat(header)) + { + return "image/bmp"; + } + + return null; + } + + private bool IsSupportedFileFormat(Span header) + { + return header.Length >= this.HeaderSize && + header[0] == 0x42 && // B + header[1] == 0x4D; // M + } + } +} diff --git a/src/ImageSharp/Formats/Gif/GifDecoder.cs b/src/ImageSharp/Formats/Gif/GifDecoder.cs index 506b37dc8e..c922767b89 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoder.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoder.cs @@ -16,12 +16,6 @@ namespace ImageSharp.Formats /// public class GifDecoder : IImageDecoder { - /// - public IEnumerable MimeTypes => GifConstants.MimeTypes; - - /// - public IEnumerable FileExtensions => GifConstants.FileExtensions; - /// /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded. /// @@ -32,21 +26,6 @@ namespace ImageSharp.Formats /// public Encoding TextEncoding { get; set; } = GifConstants.DefaultEncoding; - /// - public int HeaderSize => 6; - - /// - public bool IsSupportedFileFormat(Span header) - { - return header.Length >= this.HeaderSize && - header[0] == 0x47 && // G - header[1] == 0x49 && // I - header[2] == 0x46 && // F - header[3] == 0x38 && // 8 - (header[4] == 0x39 || header[4] == 0x37) && // 9 or 7 - header[5] == 0x61; // a - } - /// public Image Decode(Configuration configuration, Stream stream) where TPixel : struct, IPixel diff --git a/src/ImageSharp/Formats/Gif/GifEncoder.cs b/src/ImageSharp/Formats/Gif/GifEncoder.cs index f7dba9266a..3ded884296 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoder.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoder.cs @@ -17,12 +17,6 @@ namespace ImageSharp.Formats /// public class GifEncoder : IImageEncoder { - /// - public IEnumerable MimeTypes => GifConstants.MimeTypes; - - /// - public IEnumerable FileExtensions => GifConstants.FileExtensions; - /// /// Gets or sets a value indicating whether the metadata should be ignored when the image is being encoded. /// diff --git a/src/ImageSharp/Formats/Gif/GifImageFormatProvider.cs b/src/ImageSharp/Formats/Gif/GifImageFormatProvider.cs new file mode 100644 index 0000000000..7e521353f3 --- /dev/null +++ b/src/ImageSharp/Formats/Gif/GifImageFormatProvider.cs @@ -0,0 +1,42 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Text; + using ImageSharp.PixelFormats; + + /// + /// Detects gif file headers + /// + public class GifImageFormatProvider : IImageFormatProvider + { + /// + public void Configure(IImageFormatHost host) + { + var encoder = new GifEncoder(); + foreach (string mimeType in GifConstants.MimeTypes) + { + host.SetMimeTypeEncoder(mimeType, encoder); + } + + foreach (string mimeType in GifConstants.FileExtensions) + { + host.SetFileExtensionEncoder(mimeType, encoder); + } + + var decoder = new GifDecoder(); + foreach (string mimeType in GifConstants.MimeTypes) + { + host.SetMimeTypeDecoder(mimeType, decoder); + } + + host.AddMimeTypeDetector(new GifMimeTypeDetector()); + } + } +} diff --git a/src/ImageSharp/Formats/Gif/GifMimeTypeDetector.cs b/src/ImageSharp/Formats/Gif/GifMimeTypeDetector.cs new file mode 100644 index 0000000000..f4ad8fa8e1 --- /dev/null +++ b/src/ImageSharp/Formats/Gif/GifMimeTypeDetector.cs @@ -0,0 +1,44 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Text; + using ImageSharp.PixelFormats; + + /// + /// Detects gif file headers + /// + public class GifMimeTypeDetector : IMimeTypeDetector + { + /// + public int HeaderSize => 6; + + /// + public string DetectMimeType(Span header) + { + if (this.IsSupportedFileFormat(header)) + { + return "image/gif"; + } + + return null; + } + + private bool IsSupportedFileFormat(Span header) + { + return header.Length >= this.HeaderSize && + header[0] == 0x47 && // G + header[1] == 0x49 && // I + header[2] == 0x46 && // F + header[3] == 0x38 && // 8 + (header[4] == 0x39 || header[4] == 0x37) && // 9 or 7 + header[5] == 0x61; // a + } + } +} diff --git a/src/ImageSharp/Formats/IImageDecoder.cs b/src/ImageSharp/Formats/IImageDecoder.cs index ff655d718d..66eabb1b82 100644 --- a/src/ImageSharp/Formats/IImageDecoder.cs +++ b/src/ImageSharp/Formats/IImageDecoder.cs @@ -16,32 +16,6 @@ namespace ImageSharp.Formats /// public interface IImageDecoder { - /// - /// Gets the collection of mime types that this decoder supports decoding on. - /// - IEnumerable MimeTypes { get; } - - /// - /// Gets the collection of file extensionsthis decoder supports decoding. - /// - IEnumerable FileExtensions { get; } - - /// - /// Gets the size of the header for this image type. - /// - /// The size of the header. - int HeaderSize { get; } - - /// - /// Returns a value indicating whether the supports the specified - /// file header. - /// - /// The containing the file header. - /// - /// True if the decoder supports the file header; otherwise, false. - /// - bool IsSupportedFileFormat(Span header); - /// /// Decodes the image from the specified stream to the . /// diff --git a/src/ImageSharp/Formats/IImageEncoder.cs b/src/ImageSharp/Formats/IImageEncoder.cs index 465f8eec57..4ad41ebc27 100644 --- a/src/ImageSharp/Formats/IImageEncoder.cs +++ b/src/ImageSharp/Formats/IImageEncoder.cs @@ -16,16 +16,6 @@ namespace ImageSharp.Formats /// public interface IImageEncoder { - /// - /// Gets the collection of mime types that this decoder supports encoding for. - /// - IEnumerable MimeTypes { get; } - - /// - /// Gets the collection of file extensionsthis decoder supports encoding for. - /// - IEnumerable FileExtensions { get; } - /// /// Encodes the image to the specified stream from the . /// diff --git a/src/ImageSharp/Formats/IImageFormatProvider.cs b/src/ImageSharp/Formats/IImageFormatProvider.cs new file mode 100644 index 0000000000..c7d354ec6d --- /dev/null +++ b/src/ImageSharp/Formats/IImageFormatProvider.cs @@ -0,0 +1,56 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using System; + using System.Collections.Generic; + using System.Text; + + /// + /// Represents an abstract class that can register image encoders, decoders and mime type detectors + /// + public interface IImageFormatProvider + { + /// + /// Called when loaded so the provider and register its encoders, decodes and mime type detectors into an IImageFormatHost. + /// + /// The host that will retain the encoders, decodes and mime type detectors. + void Configure(IImageFormatHost host); + } + + /// + /// Represents an abstract class that can have encoders decoders and mimetype detecotrs loaded into. + /// + public interface IImageFormatHost + { + /// + /// Sets a specific image encoder as the encoder for a specific mimetype + /// + /// the target mimetype + /// the encoder to use + void SetMimeTypeEncoder(string mimeType, IImageEncoder encoder); // could/should this be an Action??? + + /// + /// Sets a specific image encoder as the encoder for a specific mimetype + /// + /// the target mimetype + /// the encoder to use + void SetFileExtensionEncoder(string extension, IImageEncoder encoder); + + /// + /// Sets a specific image decoder as the decoder for a specific mimetype + /// + /// the target mimetype + /// the decoder to use + void SetMimeTypeDecoder(string mimeType, IImageDecoder decoder); + + /// + /// Adds a new detector for detecting in mime types + /// + /// The detector + void AddMimeTypeDetector(IMimeTypeDetector detector); + } +} diff --git a/src/ImageSharp/Formats/IMimeTypeDetector.cs b/src/ImageSharp/Formats/IMimeTypeDetector.cs new file mode 100644 index 0000000000..f55be71516 --- /dev/null +++ b/src/ImageSharp/Formats/IMimeTypeDetector.cs @@ -0,0 +1,30 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using System; + using System.Collections.Generic; + using System.Text; + + /// + /// Used for detecting mime types from a file header + /// + public interface IMimeTypeDetector + { + /// + /// Gets the size of the header for this image type. + /// + /// The size of the header. + int HeaderSize { get; } + + /// + /// Detect mimetype + /// + /// The containing the file header. + /// returns the mime type of detected othersie returns null + string DetectMimeType(Span header); + } +} diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs index 09575a12e4..b809908e9b 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs @@ -21,22 +21,6 @@ namespace ImageSharp.Formats /// public bool IgnoreMetadata { get; set; } - /// - public IEnumerable MimeTypes => JpegConstants.MimeTypes; - - /// - public IEnumerable FileExtensions => JpegConstants.FileExtensions; - - /// - public int HeaderSize => 11; - - /// - public bool IsSupportedFileFormat(Span header) - { - return header.Length >= this.HeaderSize && - (IsJfif(header) || IsExif(header) || IsJpeg(header)); - } - /// public Image Decode(Configuration configuration, Stream stream) where TPixel : struct, IPixel @@ -49,54 +33,5 @@ namespace ImageSharp.Formats return decoder.Decode(stream); } } - - /// - /// Returns a value indicating whether the given bytes identify Jfif data. - /// - /// The bytes representing the file header. - /// The - private static bool IsJfif(Span header) - { - bool isJfif = - header[6] == 0x4A && // J - header[7] == 0x46 && // F - header[8] == 0x49 && // I - header[9] == 0x46 && // F - header[10] == 0x00; - - return isJfif; - } - - /// - /// Returns a value indicating whether the given bytes identify EXIF data. - /// - /// The bytes representing the file header. - /// The - private static bool IsExif(Span header) - { - bool isExif = - header[6] == 0x45 && // E - header[7] == 0x78 && // X - header[8] == 0x69 && // I - header[9] == 0x66 && // F - header[10] == 0x00; - - return isExif; - } - - /// - /// Returns a value indicating whether the given bytes identify Jpeg data. - /// This is a last chance resort for jpegs that contain ICC information. - /// - /// The bytes representing the file header. - /// The - private static bool IsJpeg(Span header) - { - bool isJpg = - header[0] == 0xFF && // 255 - header[1] == 0xD8; // 216 - - return isJpg; - } } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs index f76df0585c..d0be9eaf8f 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs @@ -34,12 +34,6 @@ namespace ImageSharp.Formats /// The subsample ratio of the jpg image. public JpegSubsample? Subsample { get; set; } - /// - public IEnumerable MimeTypes => JpegConstants.MimeTypes; - - /// - public IEnumerable FileExtensions => JpegConstants.FileExtensions; - /// /// Encodes the image to the specified stream from the . /// diff --git a/src/ImageSharp/Formats/Jpeg/JpegImageFormatProvider.cs b/src/ImageSharp/Formats/Jpeg/JpegImageFormatProvider.cs new file mode 100644 index 0000000000..6cd49e20e5 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/JpegImageFormatProvider.cs @@ -0,0 +1,42 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Text; + using ImageSharp.PixelFormats; + + /// + /// Detects png file headers + /// + public class JpegImageFormatProvider : IImageFormatProvider + { + /// + public void Configure(IImageFormatHost host) + { + var pngEncoder = new JpegEncoder(); + foreach (string mimeType in JpegConstants.MimeTypes) + { + host.SetMimeTypeEncoder(mimeType, pngEncoder); + } + + foreach (string mimeType in JpegConstants.FileExtensions) + { + host.SetFileExtensionEncoder(mimeType, pngEncoder); + } + + var pngDecoder = new JpegDecoder(); + foreach (string mimeType in JpegConstants.MimeTypes) + { + host.SetMimeTypeDecoder(mimeType, pngDecoder); + } + + host.AddMimeTypeDetector(new JpegMimeTypeDetector()); + } + } +} diff --git a/src/ImageSharp/Formats/Jpeg/JpegMimeTypeDetector.cs b/src/ImageSharp/Formats/Jpeg/JpegMimeTypeDetector.cs new file mode 100644 index 0000000000..f84a91f70c --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/JpegMimeTypeDetector.cs @@ -0,0 +1,88 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Text; + using ImageSharp.PixelFormats; + + /// + /// Detects Jpeg file headers + /// + public class JpegMimeTypeDetector : IMimeTypeDetector + { + /// + public int HeaderSize => 11; + + /// + public string DetectMimeType(Span header) + { + if (this.IsSupportedFileFormat(header)) + { + return "image/jpeg"; + } + + return null; + } + + private bool IsSupportedFileFormat(Span header) + { + return header.Length >= this.HeaderSize && + (this.IsJfif(header) || this.IsExif(header) || this.IsJpeg(header)); + } + + /// + /// Returns a value indicating whether the given bytes identify Jfif data. + /// + /// The bytes representing the file header. + /// The + private bool IsJfif(Span header) + { + bool isJfif = + header[6] == 0x4A && // J + header[7] == 0x46 && // F + header[8] == 0x49 && // I + header[9] == 0x46 && // F + header[10] == 0x00; + + return isJfif; + } + + /// + /// Returns a value indicating whether the given bytes identify EXIF data. + /// + /// The bytes representing the file header. + /// The + private bool IsExif(Span header) + { + bool isExif = + header[6] == 0x45 && // E + header[7] == 0x78 && // X + header[8] == 0x69 && // I + header[9] == 0x66 && // F + header[10] == 0x00; + + return isExif; + } + + /// + /// Returns a value indicating whether the given bytes identify Jpeg data. + /// This is a last chance resort for jpegs that contain ICC information. + /// + /// The bytes representing the file header. + /// The + private bool IsJpeg(Span header) + { + bool isJpg = + header[0] == 0xFF && // 255 + header[1] == 0xD8; // 216 + + return isJpg; + } + } +} diff --git a/src/ImageSharp/Formats/Png/PngDecoder.cs b/src/ImageSharp/Formats/Png/PngDecoder.cs index da00ff9069..c9fab8e3fb 100644 --- a/src/ImageSharp/Formats/Png/PngDecoder.cs +++ b/src/ImageSharp/Formats/Png/PngDecoder.cs @@ -38,34 +38,11 @@ namespace ImageSharp.Formats /// public bool IgnoreMetadata { get; set; } - /// - public IEnumerable MimeTypes => PngConstants.MimeTypes; - - /// - public IEnumerable FileExtensions => PngConstants.FileExtensions; - - /// - public int HeaderSize => 8; - /// /// Gets or sets the encoding that should be used when reading text chunks. /// public Encoding TextEncoding { get; set; } = PngConstants.DefaultEncoding; - /// - public bool IsSupportedFileFormat(Span header) - { - return header.Length >= this.HeaderSize && - header[0] == 0x89 && - header[1] == 0x50 && // P - header[2] == 0x4E && // N - header[3] == 0x47 && // G - header[4] == 0x0D && // CR - header[5] == 0x0A && // LF - header[6] == 0x1A && // EOF - header[7] == 0x0A; // LF - } - /// /// Decodes the image from the specified stream to the . /// diff --git a/src/ImageSharp/Formats/Png/PngEncoder.cs b/src/ImageSharp/Formats/Png/PngEncoder.cs index 023e465e90..d15161ded7 100644 --- a/src/ImageSharp/Formats/Png/PngEncoder.cs +++ b/src/ImageSharp/Formats/Png/PngEncoder.cs @@ -16,12 +16,6 @@ namespace ImageSharp.Formats /// public class PngEncoder : IImageEncoder { - /// - public IEnumerable MimeTypes => PngConstants.MimeTypes; - - /// - public IEnumerable FileExtensions => PngConstants.FileExtensions; - /// /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded. /// diff --git a/src/ImageSharp/Formats/Png/PngImageFormatProvider.cs b/src/ImageSharp/Formats/Png/PngImageFormatProvider.cs new file mode 100644 index 0000000000..5708cc812a --- /dev/null +++ b/src/ImageSharp/Formats/Png/PngImageFormatProvider.cs @@ -0,0 +1,42 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Text; + using ImageSharp.PixelFormats; + + /// + /// Detects png file headers + /// + public class PngImageFormatProvider : IImageFormatProvider + { + /// + public void Configure(IImageFormatHost host) + { + var pngEncoder = new PngEncoder(); + foreach (string mimeType in PngConstants.MimeTypes) + { + host.SetMimeTypeEncoder(mimeType, pngEncoder); + } + + foreach (string mimeType in PngConstants.FileExtensions) + { + host.SetFileExtensionEncoder(mimeType, pngEncoder); + } + + var pngDecoder = new PngDecoder(); + foreach (string mimeType in PngConstants.MimeTypes) + { + host.SetMimeTypeDecoder(mimeType, pngDecoder); + } + + host.AddMimeTypeDetector(new PngMimeTypeDetector()); + } + } +} diff --git a/src/ImageSharp/Formats/Png/PngMimeTypeDetector.cs b/src/ImageSharp/Formats/Png/PngMimeTypeDetector.cs new file mode 100644 index 0000000000..6b5d43fbdd --- /dev/null +++ b/src/ImageSharp/Formats/Png/PngMimeTypeDetector.cs @@ -0,0 +1,46 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Text; + using ImageSharp.PixelFormats; + + /// + /// Detects png file headers + /// + public class PngMimeTypeDetector : IMimeTypeDetector + { + /// + public int HeaderSize => 8; + + /// + public string DetectMimeType(Span header) + { + if (this.IsSupportedFileFormat(header)) + { + return "image/png"; + } + + return null; + } + + private bool IsSupportedFileFormat(Span header) + { + return header.Length >= this.HeaderSize && + header[0] == 0x89 && + header[1] == 0x50 && // P + header[2] == 0x4E && // N + header[3] == 0x47 && // G + header[4] == 0x0D && // CR + header[5] == 0x0A && // LF + header[6] == 0x1A && // EOF + header[7] == 0x0A; // LF + } + } +} diff --git a/src/ImageSharp/Image/Image.Decode.cs b/src/ImageSharp/Image/Image.Decode.cs index 2730da8f28..96d5df45f2 100644 --- a/src/ImageSharp/Image/Image.Decode.cs +++ b/src/ImageSharp/Image/Image.Decode.cs @@ -22,8 +22,8 @@ namespace ImageSharp /// /// The image stream to read the header from. /// The configuration. - /// The image format or null if none found. - private static IImageDecoder DiscoverDecoder(Stream stream, Configuration config) + /// The mimetype or null if none found. + private static string InternalDiscoverMimeType(Stream stream, Configuration config) { // This is probably a candidate for making into a public API in the future! int maxHeaderSize = config.MaxHeaderSize; @@ -32,19 +32,31 @@ namespace ImageSharp return null; } - IImageDecoder format; byte[] header = ArrayPool.Shared.Rent(maxHeaderSize); try { long startPosition = stream.Position; stream.Read(header, 0, maxHeaderSize); stream.Position = startPosition; - format = config.ImageDecoders.LastOrDefault(x => x.IsSupportedFileFormat(header)); // we should use last in case user has registerd a new one with their own settings + return config.MimeTypeDetectors.Select(x => x.DetectMimeType(header)).LastOrDefault(x => x != null); } finally { ArrayPool.Shared.Return(header); } + } + + /// + /// By reading the header on the provided stream this calculates the images format. + /// + /// The image stream to read the header from. + /// The configuration. + /// The mimeType. + /// The image format or null if none found. + private static IImageDecoder DiscoverDecoder(Stream stream, Configuration config, out string mimeType) + { + + format = config.FindMimeTypeDecoder(mimeType); return format; } @@ -59,18 +71,18 @@ namespace ImageSharp /// /// A new . /// - private static (Image img, IImageDecoder decoder) Decode(Stream stream, Configuration config) + private static (Image img, string mimeType) Decode(Stream stream, Configuration config) #pragma warning restore SA1008 // Opening parenthesis must be spaced correctly where TPixel : struct, IPixel { - IImageDecoder decoder = DiscoverDecoder(stream, config); + IImageDecoder decoder = DiscoverDecoder(stream, config, out string mimeType); if (decoder == null) { return (null, null); } Image img = decoder.Decode(config, stream); - return (img, decoder); + return (img, mimeType); } } } \ No newline at end of file diff --git a/src/ImageSharp/Image/Image.FromBytes.cs b/src/ImageSharp/Image/Image.FromBytes.cs index 8cc0e8f376..948fadf0c0 100644 --- a/src/ImageSharp/Image/Image.FromBytes.cs +++ b/src/ImageSharp/Image/Image.FromBytes.cs @@ -15,6 +15,30 @@ namespace ImageSharp /// public static partial class Image { + /// + /// By reading the header on the provided byte array this calculates the images mimetype. + /// + /// The byte array containing image data to read the header from. + /// The mimetype or null if none found. + public static string DiscoverMimeType(byte[] data) + { + return DiscoverMimeType(null, data); + } + + /// + /// By reading the header on the provided byte array this calculates the images mimetype. + /// + /// The configuration. + /// The byte array containing image data to read the header from. + /// The mimetype or null if none found. + public static string DiscoverMimeType(Configuration config, byte[] data) + { + using (Stream stream = new MemoryStream(data)) + { + return DiscoverMimeType(config, stream); + } + } + /// /// Create a new instance of the class from the given byte array. /// diff --git a/src/ImageSharp/Image/Image.FromFile.cs b/src/ImageSharp/Image/Image.FromFile.cs index 9912949217..96d509752a 100644 --- a/src/ImageSharp/Image/Image.FromFile.cs +++ b/src/ImageSharp/Image/Image.FromFile.cs @@ -16,6 +16,31 @@ namespace ImageSharp /// public static partial class Image { + /// + /// By reading the header on the provided file this calculates the images mimetype. + /// + /// The image file to open and to read the header from. + /// The mimetype or null if none found. + public static string DiscoverMimeType(string filePath) + { + return DiscoverMimeType(null, filePath); + } + + /// + /// By reading the header on the provided file this calculates the images mimetype. + /// + /// The configuration. + /// The image file to open and to read the header from. + /// The mimetype or null if none found. + public static string DiscoverMimeType(Configuration config, string filePath) + { + config = config ?? Configuration.Default; + using (Stream file = config.FileSystem.OpenRead(filePath)) + { + return DiscoverMimeType(config, file); + } + } + /// /// Create a new instance of the class from the given file. /// diff --git a/src/ImageSharp/Image/Image.FromStream.cs b/src/ImageSharp/Image/Image.FromStream.cs index 79c66af906..1aa93525c1 100644 --- a/src/ImageSharp/Image/Image.FromStream.cs +++ b/src/ImageSharp/Image/Image.FromStream.cs @@ -18,6 +18,27 @@ namespace ImageSharp /// public static partial class Image { + /// + /// By reading the header on the provided stream this calculates the images mimetype. + /// + /// The image stream to read the header from. + /// The mimetype or null if none found. + public static string DiscoverMimeType(Stream stream) + { + return DiscoverMimeType(null, stream); + } + + /// + /// By reading the header on the provided stream this calculates the images mimetype. + /// + /// The configuration. + /// The image stream to read the header from. + /// The mimetype or null if none found. + public static string DiscoverMimeType(Configuration config, Stream stream) + { + return WithSeekableStream(stream, s => InternalDiscoverMimeType(s, config ?? Configuration.Default)); + } + /// /// Create a new instance of the class from the given stream. /// @@ -169,21 +190,21 @@ namespace ImageSharp { config = config ?? Configuration.Default; mimeType = null; - (Image img, IImageDecoder decoder) data = WithSeekableStream(stream, s => Decode(s, config)); + (Image img, string mimeType) data = WithSeekableStream(stream, s => Decode(s, config)); - mimeType = data.decoder?.MimeTypes.FirstOrDefault(); + mimeType = data.mimeType; if (data.img != null) { return data.img; } - StringBuilder stringBuilder = new StringBuilder(); + var stringBuilder = new StringBuilder(); stringBuilder.AppendLine("Image cannot be loaded. Available decoders:"); - foreach (IImageDecoder format in config.ImageDecoders) + foreach (Type format in config.AllMimeImageDecoders) { - stringBuilder.AppendLine("-" + format); + stringBuilder.AppendLine(" - " + format.Name); } throw new NotSupportedException(stringBuilder.ToString()); @@ -202,7 +223,7 @@ namespace ImageSharp } // We want to be able to load images from things like HttpContext.Request.Body - using (MemoryStream ms = new MemoryStream()) + using (var ms = new MemoryStream()) { stream.CopyTo(ms); ms.Position = 0; diff --git a/src/ImageSharp/Image/Image{TPixel}.cs b/src/ImageSharp/Image/Image{TPixel}.cs index 27f3801c4f..af9cc3914a 100644 --- a/src/ImageSharp/Image/Image{TPixel}.cs +++ b/src/ImageSharp/Image/Image{TPixel}.cs @@ -158,16 +158,16 @@ namespace ImageSharp public Image Save(Stream stream, string mimeType) { Guard.NotNullOrEmpty(mimeType, nameof(mimeType)); - IImageEncoder encoder = this.Configuration.ImageEncoders?.LastOrDefault(x => x?.MimeTypes?.Contains(mimeType, StringComparer.OrdinalIgnoreCase) == true); + IImageEncoder encoder = this.Configuration.FindMimeTypeEncoder(mimeType); if (encoder == null) { - StringBuilder stringBuilder = new StringBuilder(); + var stringBuilder = new StringBuilder(); stringBuilder.AppendLine("Can't find encoder for provided mime type. Available encoded:"); - foreach (IImageEncoder format in this.Configuration.ImageEncoders) + foreach (Type format in this.Configuration.AllMimeImageEncoders) { - stringBuilder.AppendLine("-" + format); + stringBuilder.AppendLine(" - " + format); } throw new NotSupportedException(stringBuilder.ToString()); @@ -207,15 +207,15 @@ namespace ImageSharp Guard.NotNullOrEmpty(filePath, nameof(filePath)); string ext = Path.GetExtension(filePath).Trim('.'); - IImageEncoder encoder = this.Configuration.ImageEncoders?.LastOrDefault(x => x?.FileExtensions?.Contains(ext, StringComparer.OrdinalIgnoreCase) == true); + IImageEncoder encoder = this.Configuration.FindFileExtensionsEncoder(ext); if (encoder == null) { - StringBuilder stringBuilder = new StringBuilder(); + var stringBuilder = new StringBuilder(); stringBuilder.AppendLine($"Can't find encoder for file extention '{ext}'. Available encoded:"); - foreach (IImageEncoder format in this.Configuration.ImageEncoders) + foreach (Type format in this.Configuration.AllExtImageEncoders) { - stringBuilder.AppendLine("-" + format); + stringBuilder.AppendLine(" - " + format); } throw new NotSupportedException(stringBuilder.ToString()); @@ -255,7 +255,7 @@ namespace ImageSharp /// The public string ToBase64String(string mimeType) { - using (MemoryStream stream = new MemoryStream()) + using (var stream = new MemoryStream()) { this.Save(stream, mimeType); stream.Flush(); @@ -274,7 +274,7 @@ namespace ImageSharp { scaleFunc = PackedPixelConverterHelper.ComputeScaleFunction(scaleFunc); - Image target = new Image(this.Configuration, this.Width, this.Height); + var target = new Image(this.Configuration, this.Width, this.Height); target.CopyProperties(this); using (PixelAccessor pixels = this.Lock()) @@ -288,7 +288,7 @@ namespace ImageSharp { for (int x = 0; x < target.Width; x++) { - TPixel2 color = default(TPixel2); + var color = default(TPixel2); color.PackFromVector4(scaleFunc(pixels[x, y].ToVector4())); targetPixels[x, y] = color; } diff --git a/tests/ImageSharp.Tests/ConfigurationTests.cs b/tests/ImageSharp.Tests/ConfigurationTests.cs index e2d50649a6..e8927c75c4 100644 --- a/tests/ImageSharp.Tests/ConfigurationTests.cs +++ b/tests/ImageSharp.Tests/ConfigurationTests.cs @@ -13,7 +13,7 @@ namespace ImageSharp.Tests using ImageSharp.Formats; using ImageSharp.IO; using ImageSharp.PixelFormats; - + using Moq; using Xunit; /// @@ -21,23 +21,27 @@ namespace ImageSharp.Tests /// public class ConfigurationTests { + public Configuration ConfigurationEmpty { get; private set; } + public Configuration DefaultConfiguration { get; private set; } + + public ConfigurationTests() + { + this.DefaultConfiguration = Configuration.CreateDefaultInstance(); + this.ConfigurationEmpty = Configuration.CreateDefaultInstance(); + } + [Fact] public void DefaultsToLocalFileSystem() { - var configuration = Configuration.CreateDefaultInstance(); - - ImageSharp.IO.IFileSystem fs = configuration.FileSystem; - - Assert.IsType(fs); + Assert.IsType(DefaultConfiguration.FileSystem); + Assert.IsType(ConfigurationEmpty.FileSystem); } [Fact] public void IfAutoloadWellknwonFormatesIsTrueAllFormateAreLoaded() { - var configuration = Configuration.CreateDefaultInstance(); - - Assert.Equal(4, configuration.ImageDecoders.Count); - Assert.Equal(4, configuration.ImageDecoders.Count); + Assert.Equal(4, DefaultConfiguration.AllMimeImageDecoders.Count()); + Assert.Equal(4, DefaultConfiguration.AllMimeImageDecoders.Count()); } /// @@ -68,58 +72,129 @@ namespace ImageSharp.Tests Assert.True(Configuration.Default.ParallelOptions.MaxDegreeOfParallelism == Environment.ProcessorCount); } - /// - /// Test that the default configuration parallel options is not null. - /// [Fact] - public void TestDefultConfigurationImageFormatsIsNotNull() + public void AddMimeTypeDetectorNullthrows() { - Assert.True(Configuration.Default.ImageDecoders != null); - Assert.True(Configuration.Default.ImageEncoders != null); + Assert.Throws(() => + { + DefaultConfiguration.AddMimeTypeDetector(null); + }); } - /// - /// Tests the method throws an exception - /// when the format is null. - /// [Fact] - public void TestAddImageFormatThrowsWithNullFormat() + public void RegisterNullMimeTypeEncoder() { Assert.Throws(() => { - Configuration.Default.AddImageFormat((IImageEncoder)null); + DefaultConfiguration.SetMimeTypeEncoder(null, new Mock().Object); }); Assert.Throws(() => { - Configuration.Default.AddImageFormat((IImageDecoder)null); + DefaultConfiguration.SetMimeTypeEncoder("sdsdsd", null); + }); + Assert.Throws(() => + { + DefaultConfiguration.SetMimeTypeEncoder(null, null); + }); + } + + [Fact] + public void RegisterNullFileExtEncoder() + { + Assert.Throws(() => + { + DefaultConfiguration.SetFileExtensionEncoder(null, new Mock().Object); + }); + Assert.Throws(() => + { + DefaultConfiguration.SetFileExtensionEncoder("sdsdsd", null); + }); + Assert.Throws(() => + { + DefaultConfiguration.SetFileExtensionEncoder(null, null); }); } - /// - /// Test that the default image constructors use default configuration. - /// [Fact] - public void TestImageUsesDefaultConfiguration() + public void RegisterNullMimeTypeDecoder() { - Configuration.Default.AddImageFormat(new PngDecoder()); + Assert.Throws(() => + { + DefaultConfiguration.SetMimeTypeDecoder(null, new Mock().Object); + }); + Assert.Throws(() => + { + DefaultConfiguration.SetMimeTypeDecoder("sdsdsd", null); + }); + Assert.Throws(() => + { + DefaultConfiguration.SetMimeTypeDecoder(null, null); + }); + } - var image = new Image(1, 1); - Assert.Equal(image.Configuration.ParallelOptions, Configuration.Default.ParallelOptions); - Assert.Equal(image.Configuration.ImageDecoders, Configuration.Default.ImageDecoders); + [Fact] + public void RegisterMimeTypeEncoderReplacesLast() + { + var encoder1 = new Mock().Object; + ConfigurationEmpty.SetMimeTypeEncoder("test", encoder1); + var found = ConfigurationEmpty.FindMimeTypeEncoder("TEST"); + Assert.Equal(encoder1, found); + + var encoder2 = new Mock().Object; + ConfigurationEmpty.SetMimeTypeEncoder("TEST", encoder2); + var found2 = ConfigurationEmpty.FindMimeTypeEncoder("test"); + Assert.Equal(encoder2, found2); + Assert.NotEqual(found, found2); + } + + [Fact] + public void RegisterFileExtEnecoderReplacesLast() + { + var encoder1 = new Mock().Object; + ConfigurationEmpty.SetFileExtensionEncoder("TEST", encoder1); + var found = ConfigurationEmpty.FindFileExtensionsEncoder("test"); + Assert.Equal(encoder1, found); + + var encoder2 = new Mock().Object; + ConfigurationEmpty.SetFileExtensionEncoder("test", encoder2); + var found2 = ConfigurationEmpty.FindFileExtensionsEncoder("TEST"); + Assert.Equal(encoder2, found2); + Assert.NotEqual(found, found2); + } + + [Fact] + public void RegisterMimeTypeDecoderReplacesLast() + { + var decoder1 = new Mock().Object; + ConfigurationEmpty.SetMimeTypeDecoder("test", decoder1); + var found = ConfigurationEmpty.FindMimeTypeDecoder("TEST"); + Assert.Equal(decoder1, found); + + var decoder2 = new Mock().Object; + ConfigurationEmpty.SetMimeTypeDecoder("TEST", decoder2); + var found2 = ConfigurationEmpty.FindMimeTypeDecoder("test"); + Assert.Equal(decoder2, found2); + Assert.NotEqual(found, found2); + } + + + [Fact] + public void ConstructorCallConfigureOnFormatProvider() + { + var provider = new Mock(); + var config = new Configuration(provider.Object); + + provider.Verify(x => x.Configure(config)); } - /// - /// Test that the default image constructor copies the configuration. - /// [Fact] - public void TestImageCopiesConfiguration() + public void AddFormatCallsConfig() { - Configuration.Default.AddImageFormat(new PngDecoder()); + var provider = new Mock(); + var config = new Configuration(); + config.AddImageFormat(provider.Object); - var image = new Image(1, 1); - var image2 = new Image(image); - Assert.Equal(image2.Configuration.ParallelOptions, image.Configuration.ParallelOptions); - Assert.True(image2.Configuration.ImageDecoders.SequenceEqual(image.Configuration.ImageDecoders)); + provider.Verify(x => x.Configure(config)); } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Image/ImageLoadTests.cs b/tests/ImageSharp.Tests/Image/ImageLoadTests.cs index 65c2cc52cf..efd7b69350 100644 --- a/tests/ImageSharp.Tests/Image/ImageLoadTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageLoadTests.cs @@ -23,6 +23,7 @@ namespace ImageSharp.Tests private Image returnImage; private Mock localDecoder; private readonly string FilePath; + private readonly Mock localMimeTypeDetector; public Configuration LocalConfiguration { get; private set; } public byte[] Marker { get; private set; } @@ -34,10 +35,9 @@ namespace ImageSharp.Tests this.returnImage = new Image(1, 1); this.localDecoder = new Mock(); - this.localDecoder.Setup(x => x.MimeTypes).Returns(new[] { "img/test" }); - this.localDecoder.Setup(x => x.FileExtensions).Returns(new[] { "png", "jpg" }); - this.localDecoder.Setup(x => x.HeaderSize).Returns(1); - this.localDecoder.Setup(x => x.IsSupportedFileFormat(It.IsAny>())).Returns(true); + this.localMimeTypeDetector = new Mock(); + this.localMimeTypeDetector.Setup(x => x.HeaderSize).Returns(1); + this.localMimeTypeDetector.Setup(x => x.DetectMimeType(It.IsAny>())).Returns("test"); this.localDecoder.Setup(x => x.Decode(It.IsAny(), It.IsAny())) @@ -56,8 +56,8 @@ namespace ImageSharp.Tests { FileSystem = this.fileSystem.Object }; - - this.LocalConfiguration.AddImageFormat(this.localDecoder.Object); + this.LocalConfiguration.AddMimeTypeDetector(this.localMimeTypeDetector.Object); + this.LocalConfiguration.SetMimeTypeDecoder("test", this.localDecoder.Object); TestFormat.RegisterGloablTestFormat(); this.Marker = Guid.NewGuid().ToByteArray(); diff --git a/tests/ImageSharp.Tests/Image/ImageSaveTests.cs b/tests/ImageSharp.Tests/Image/ImageSaveTests.cs index 46177ba5fa..e2e0b13648 100644 --- a/tests/ImageSharp.Tests/Image/ImageSaveTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageSaveTests.cs @@ -24,24 +24,26 @@ namespace ImageSharp.Tests private readonly Mock fileSystem; private readonly Mock encoder; private readonly Mock encoderNotInFormat; + private Mock localMimeTypeDetector; public ImageSaveTests() { - this.encoder = new Mock(); - this.encoder.Setup(x => x.MimeTypes).Returns(new[] { "img/test" }); - this.encoder.Setup(x => x.FileExtensions).Returns(new string[] { "png", "jpg" }); + this.localMimeTypeDetector = new Mock(); + this.localMimeTypeDetector.Setup(x => x.HeaderSize).Returns(1); + this.localMimeTypeDetector.Setup(x => x.DetectMimeType(It.IsAny>())).Returns("img/test"); + this.encoder = new Mock(); this.encoderNotInFormat = new Mock(); - this.encoderNotInFormat.Setup(x => x.MimeTypes).Returns(new[] { "img/test" }); - this.encoderNotInFormat.Setup(x => x.FileExtensions).Returns(new string[] { "png", "jpg" }); this.fileSystem = new Mock(); var config = new Configuration() { FileSystem = this.fileSystem.Object }; - config.AddImageFormat(this.encoder.Object); + config.AddMimeTypeDetector(this.localMimeTypeDetector.Object); + config.SetMimeTypeEncoder("img/test", this.encoder.Object); + config.SetFileExtensionEncoder("png", this.encoder.Object); this.Image = new Image(config, 1, 1); } diff --git a/tests/ImageSharp.Tests/TestFormat.cs b/tests/ImageSharp.Tests/TestFormat.cs index 1d06f7328a..701b02c09e 100644 --- a/tests/ImageSharp.Tests/TestFormat.cs +++ b/tests/ImageSharp.Tests/TestFormat.cs @@ -19,14 +19,13 @@ namespace ImageSharp.Tests /// /// A test image file. /// - public class TestFormat + public class TestFormat : IImageFormatProvider { public static TestFormat GlobalTestFormat { get; } = new TestFormat(); public static void RegisterGloablTestFormat() { - Configuration.Default.AddImageFormat(GlobalTestFormat.Encoder); - Configuration.Default.AddImageFormat(GlobalTestFormat.Decoder); + Configuration.Default.AddImageFormat(GlobalTestFormat); } public TestFormat() @@ -66,7 +65,8 @@ namespace ImageSharp.Tests Assert.True(discovered.Any(), "No calls to decode on this formate with the proveded options happend"); - foreach (DecodeOperation d in discovered) { + foreach (DecodeOperation d in discovered) + { this.DecodeCalls.Remove(d); } } @@ -80,7 +80,7 @@ namespace ImageSharp.Tests { this._sampleImages.Add(typeof(TPixel), new Image(1, 1)); } - + return (Image)this._sampleImages[typeof(TPixel)]; } } @@ -108,12 +108,25 @@ namespace ImageSharp.Tests } return true; } + + public void Configure(IImageFormatHost host) + { + host.AddMimeTypeDetector(new TestHeader(this)); + foreach (var ext in this.SupportedExtensions) + { + host.SetFileExtensionEncoder(ext, new TestEncoder(this)); + } + + host.SetMimeTypeEncoder(this.MimeType, new TestEncoder(this)); + host.SetMimeTypeDecoder(this.MimeType, new TestDecoder(this)); + } + public struct DecodeOperation { public byte[] marker; internal Configuration config; - public bool IsMatch(byte[] testMarker, Configuration config) + public bool IsMatch(byte[] testMarker, Configuration config) { if (this.config != config) @@ -137,6 +150,26 @@ namespace ImageSharp.Tests } } + public class TestHeader : IMimeTypeDetector + { + + private TestFormat testFormat; + + public int HeaderSize => testFormat.HeaderSize; + + public string DetectMimeType(Span header) + { + if (testFormat.IsSupportedFileFormat(header)) + return testFormat.MimeType; + + return null; + } + + public TestHeader(TestFormat testFormat) + { + this.testFormat = testFormat; + } + } public class TestDecoder : ImageSharp.Formats.IImageDecoder { private TestFormat testFormat; diff --git a/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs b/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs index ee52466600..21b167ca20 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs @@ -124,7 +124,7 @@ namespace ImageSharp.Tests private static IImageEncoder GetImageFormatByExtension(string extension) { extension = extension?.TrimStart('.'); - return Configuration.Default.ImageEncoders.Last(f => f.FileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase)); + return Configuration.Default.FindFileExtensionsEncoder(extension); } private string GetTestOutputDir() From fe61328183d3b075e6a8798a61af694b341d6219 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Sat, 24 Jun 2017 10:07:32 +0100 Subject: [PATCH 03/36] fix formats + added sample for switching out encoders/decoders --- ImageSharp.sln | 19 +++++- .../ChangeDefaultEncoderOptions.csproj | 12 ++++ .../ChangeDefaultEncoderOptions/Program.cs | 35 +++++++++++ src/ImageSharp/Configuration.cs | 59 ++++++++++++------- .../Formats/Bmp/BmpImageFormatProvider.cs | 7 ++- src/ImageSharp/Formats/Gif/GifDecoder.cs | 6 ++ src/ImageSharp/Formats/Gif/GifDecoderCore.cs | 12 ++++ src/ImageSharp/Formats/Gif/GifEncoder.cs | 13 ++-- src/ImageSharp/Formats/Gif/GifEncoderCore.cs | 21 ++++--- .../Formats/Gif/GifImageFormatProvider.cs | 7 ++- .../Formats/IImageFormatProvider.cs | 4 +- .../Formats/Jpeg/JpegImageFormatProvider.cs | 7 ++- src/ImageSharp/Formats/Png/PngEncoder.cs | 8 +-- src/ImageSharp/Formats/Png/PngEncoderCore.cs | 16 ++--- .../Formats/Png/PngImageFormatProvider.cs | 7 ++- src/ImageSharp/Image/Image.Decode.cs | 9 ++- src/ImageSharp/Image/Image.FromStream.cs | 5 +- src/ImageSharp/Image/Image{TPixel}.cs | 23 ++++++-- .../Image/EncodeIndexedPng.cs | 10 ++-- tests/ImageSharp.Tests/ConfigurationTests.cs | 22 +++---- .../Formats/Gif/GifDecoderTests.cs | 13 ++++ .../Formats/Png/PngSmokeTests.cs | 2 +- .../ImageSharp.Tests/Image/ImageSaveTests.cs | 2 +- tests/ImageSharp.Tests/TestFormat.cs | 2 +- 24 files changed, 234 insertions(+), 87 deletions(-) create mode 100644 samples/ChangeDefaultEncoderOptions/ChangeDefaultEncoderOptions.csproj create mode 100644 samples/ChangeDefaultEncoderOptions/Program.cs diff --git a/ImageSharp.sln b/ImageSharp.sln index 35998e1aa0..aa8ec2b457 100644 --- a/ImageSharp.sln +++ b/ImageSharp.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.26430.6 +VisualStudioVersion = 15.0.26430.14 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SolutionItems", "SolutionItems", "{C317F1B1-D75E-4C6D-83EB-80367343E0D7}" ProjectSection(SolutionItems) = preProject @@ -47,7 +47,9 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImageSharp.Sandbox46", "tes EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{7CC6D57E-B916-43B8-B315-A0BB92F260A2}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AvatarWithRoundedCorner", "samples\AvatarWithRoundedCorner\AvatarWithRoundedCorner.csproj", "{844FC582-4E78-4371-847D-EFD4D1103578}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AvatarWithRoundedCorner", "samples\AvatarWithRoundedCorner\AvatarWithRoundedCorner.csproj", "{844FC582-4E78-4371-847D-EFD4D1103578}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChangeDefaultEncoderOptions", "samples\ChangeDefaultEncoderOptions\ChangeDefaultEncoderOptions.csproj", "{07EE511D-4BAB-4323-BAFC-3AF2BF9366F0}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -143,6 +145,18 @@ Global {844FC582-4E78-4371-847D-EFD4D1103578}.Release|x64.Build.0 = Release|Any CPU {844FC582-4E78-4371-847D-EFD4D1103578}.Release|x86.ActiveCfg = Release|Any CPU {844FC582-4E78-4371-847D-EFD4D1103578}.Release|x86.Build.0 = Release|Any CPU + {07EE511D-4BAB-4323-BAFC-3AF2BF9366F0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {07EE511D-4BAB-4323-BAFC-3AF2BF9366F0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {07EE511D-4BAB-4323-BAFC-3AF2BF9366F0}.Debug|x64.ActiveCfg = Debug|Any CPU + {07EE511D-4BAB-4323-BAFC-3AF2BF9366F0}.Debug|x64.Build.0 = Debug|Any CPU + {07EE511D-4BAB-4323-BAFC-3AF2BF9366F0}.Debug|x86.ActiveCfg = Debug|Any CPU + {07EE511D-4BAB-4323-BAFC-3AF2BF9366F0}.Debug|x86.Build.0 = Debug|Any CPU + {07EE511D-4BAB-4323-BAFC-3AF2BF9366F0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {07EE511D-4BAB-4323-BAFC-3AF2BF9366F0}.Release|Any CPU.Build.0 = Release|Any CPU + {07EE511D-4BAB-4323-BAFC-3AF2BF9366F0}.Release|x64.ActiveCfg = Release|Any CPU + {07EE511D-4BAB-4323-BAFC-3AF2BF9366F0}.Release|x64.Build.0 = Release|Any CPU + {07EE511D-4BAB-4323-BAFC-3AF2BF9366F0}.Release|x86.ActiveCfg = Release|Any CPU + {07EE511D-4BAB-4323-BAFC-3AF2BF9366F0}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -156,5 +170,6 @@ Global {2BF743D8-2A06-412D-96D7-F448F00C5EA5} = {56801022-D71A-4FBE-BC5B-CBA08E2284EC} {96188137-5FA6-4924-AB6E-4EFF79C6E0BB} = {56801022-D71A-4FBE-BC5B-CBA08E2284EC} {844FC582-4E78-4371-847D-EFD4D1103578} = {7CC6D57E-B916-43B8-B315-A0BB92F260A2} + {07EE511D-4BAB-4323-BAFC-3AF2BF9366F0} = {7CC6D57E-B916-43B8-B315-A0BB92F260A2} EndGlobalSection EndGlobal diff --git a/samples/ChangeDefaultEncoderOptions/ChangeDefaultEncoderOptions.csproj b/samples/ChangeDefaultEncoderOptions/ChangeDefaultEncoderOptions.csproj new file mode 100644 index 0000000000..5797be0f56 --- /dev/null +++ b/samples/ChangeDefaultEncoderOptions/ChangeDefaultEncoderOptions.csproj @@ -0,0 +1,12 @@ + + + + Exe + netcoreapp1.1 + + + + + + + \ No newline at end of file diff --git a/samples/ChangeDefaultEncoderOptions/Program.cs b/samples/ChangeDefaultEncoderOptions/Program.cs new file mode 100644 index 0000000000..5ba6f52b56 --- /dev/null +++ b/samples/ChangeDefaultEncoderOptions/Program.cs @@ -0,0 +1,35 @@ +using System; +using ImageSharp; + +namespace ChangeDefaultEncoderOptions +{ + class Program + { + static void Main(string[] args) + { + // lets switch out the default encoder for jpeg to one + // that saves at 90 quality and ignores the matadata + Configuration.Default.SetMimeTypeEncoder("image/jpeg", new ImageSharp.Formats.JpegEncoder() + { + Quality = 90, + IgnoreMetadata = true + }); + + // now lets say we don't want animated gifs, lets skip decoding the alternative frames + Configuration.Default.SetMimeTypeDecoder("image/gif", new ImageSharp.Formats.GifDecoder() + { + IgnoreFrames = true, + IgnoreMetadata = true + }); + + // and just to be douple sure we don't want animations lets disable them on encode too. + Configuration.Default.SetMimeTypeEncoder("image/gif", new ImageSharp.Formats.GifEncoder() + { + IgnoreFrames = true, + IgnoreMetadata = true + }); + + + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Configuration.cs b/src/ImageSharp/Configuration.cs index 43613867e5..890a0cbf94 100644 --- a/src/ImageSharp/Configuration.cs +++ b/src/ImageSharp/Configuration.cs @@ -38,7 +38,7 @@ namespace ImageSharp /// /// The list of supported keyed to fiel extensions. /// - private readonly ConcurrentDictionary extensionsEncoders = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + private readonly ConcurrentDictionary extensionsMap = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); /// /// The list of supported keyed to mimestypes. @@ -95,17 +95,17 @@ namespace ImageSharp /// /// Gets the typeof of all the current image decoders /// - internal IEnumerable AllMimeImageDecoders => this.mimeTypeDecoders.Select(x => x.Value.GetType()).Distinct().ToList(); + internal IEnumerable> ImageDecoders => this.mimeTypeDecoders; /// /// Gets the typeof of all the current image decoders /// - internal IEnumerable AllMimeImageEncoders => this.mimeTypeEncoders.Select(x => x.Value.GetType()).Distinct().ToList(); + internal IEnumerable> ImageEncoders => this.mimeTypeEncoders; /// /// Gets the typeof of all the current image decoders /// - internal IEnumerable AllExtImageEncoders => this.mimeTypeEncoders.Select(x => x.Value.GetType()).Distinct().ToList(); + internal IEnumerable> ImageExtensionToMimeTypeMapping => this.extensionsMap; #if !NETSTANDARD1_1 /// @@ -133,11 +133,11 @@ namespace ImageSharp } /// - public void SetFileExtensionEncoder(string extension, IImageEncoder encoder) + public void SetFileExtensionToMimeTypeMapping(string extension, string mimetype) { Guard.NotNullOrEmpty(extension, nameof(extension)); - Guard.NotNull(encoder, nameof(encoder)); - this.extensionsEncoders.AddOrUpdate(extension?.Trim(), encoder, (s, e) => encoder); + Guard.NotNullOrEmpty(mimetype, nameof(mimetype)); + this.extensionsMap.AddOrUpdate(extension?.Trim(), mimetype, (s, e) => mimetype); } /// @@ -151,7 +151,7 @@ namespace ImageSharp /// /// Removes all the registerd detectors /// - public void ClearMimeTypeDetector() + public void ClearMimeTypeDetectors() { this.mimeTypeDetectors.Clear(); } @@ -164,12 +164,25 @@ namespace ImageSharp this.SetMaxHeaderSize(); } + /// + /// Creates the default instance, with Png, Jpeg, Gif and Bmp preregisterd (if they have been referenced) + /// + /// The default configuration of + internal static Configuration CreateDefaultInstance() + { + return new Configuration( + new PngImageFormatProvider(), + new JpegImageFormatProvider(), + new GifImageFormatProvider(), + new BmpImageFormatProvider()); + } + /// /// For the specified mimetype find the decoder. /// /// the mimetype to discover /// the IImageDecoder if found othersize null - public IImageDecoder FindMimeTypeDecoder(string mimeType) + internal IImageDecoder FindMimeTypeDecoder(string mimeType) { Guard.NotNullOrEmpty(mimeType, nameof(mimeType)); if (this.mimeTypeDecoders.TryGetValue(mimeType, out IImageDecoder dec)) @@ -185,7 +198,7 @@ namespace ImageSharp /// /// the mimetype to discover /// the IImageEncoder if found othersize null - public IImageEncoder FindMimeTypeEncoder(string mimeType) + internal IImageEncoder FindMimeTypeEncoder(string mimeType) { Guard.NotNullOrEmpty(mimeType, nameof(mimeType)); if (this.mimeTypeEncoders.TryGetValue(mimeType, out IImageEncoder dec)) @@ -201,29 +214,33 @@ namespace ImageSharp /// /// the extensions to discover /// the IImageEncoder if found othersize null - public IImageEncoder FindFileExtensionsEncoder(string extensions) + internal IImageEncoder FindFileExtensionsEncoder(string extensions) { extensions = extensions?.TrimStart('.'); Guard.NotNullOrEmpty(extensions, nameof(extensions)); - if (this.extensionsEncoders.TryGetValue(extensions, out IImageEncoder dec)) + if (this.extensionsMap.TryGetValue(extensions, out string mime)) { - return dec; + return this.FindMimeTypeEncoder(mime); } return null; } /// - /// Creates the default instance, with Png, Jpeg, Gif and Bmp preregisterd (if they have been referenced) + /// For the specified mimetype find the encoder. /// - /// The default configuration of - internal static Configuration CreateDefaultInstance() + /// the extensions to discover + /// the IImageEncoder if found othersize null + internal string FindFileExtensionsMimeType(string extensions) { - return new Configuration( - new PngImageFormatProvider(), - new JpegImageFormatProvider(), - new GifImageFormatProvider(), - new BmpImageFormatProvider()); + extensions = extensions?.TrimStart('.'); + Guard.NotNullOrEmpty(extensions, nameof(extensions)); + if (this.extensionsMap.TryGetValue(extensions, out string mime)) + { + return mime; + } + + return null; } /// diff --git a/src/ImageSharp/Formats/Bmp/BmpImageFormatProvider.cs b/src/ImageSharp/Formats/Bmp/BmpImageFormatProvider.cs index 145e1fdb67..1c4cada768 100644 --- a/src/ImageSharp/Formats/Bmp/BmpImageFormatProvider.cs +++ b/src/ImageSharp/Formats/Bmp/BmpImageFormatProvider.cs @@ -25,9 +25,12 @@ namespace ImageSharp.Formats host.SetMimeTypeEncoder(mimeType, encoder); } - foreach (string mimeType in BmpConstants.FileExtensions) + foreach (string ext in BmpConstants.FileExtensions) { - host.SetFileExtensionEncoder(mimeType, encoder); + foreach (string mimeType in BmpConstants.MimeTypes) + { + host.SetFileExtensionToMimeTypeMapping(ext, mimeType); + } } var decoder = new BmpDecoder(); diff --git a/src/ImageSharp/Formats/Gif/GifDecoder.cs b/src/ImageSharp/Formats/Gif/GifDecoder.cs index c922767b89..caf39c2e1f 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoder.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoder.cs @@ -21,6 +21,11 @@ namespace ImageSharp.Formats /// public bool IgnoreMetadata { get; set; } = false; + /// + /// Gets or sets a value indicating whether the additional frames should be ignored when the image is being decoded. + /// + public bool IgnoreFrames { get; set; } = false; + /// /// Gets or sets the encoding that should be used when reading comments. /// @@ -32,6 +37,7 @@ namespace ImageSharp.Formats { var decoder = new GifDecoderCore(this.TextEncoding, configuration); decoder.IgnoreMetadata = this.IgnoreMetadata; + decoder.IgnoreFrames = this.IgnoreFrames; return decoder.Decode(stream); } } diff --git a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs index 0bd64b0577..ad34009b65 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs @@ -96,6 +96,11 @@ namespace ImageSharp.Formats /// public Encoding TextEncoding { get; private set; } + /// + /// Gets or sets a value indicating whether the additional frames should be ignored when the image is being decoded. + /// + public bool IgnoreFrames { get; internal set; } + /// /// Decodes the stream to the image. /// @@ -357,6 +362,13 @@ namespace ImageSharp.Formats /// The private unsafe void ReadFrameColors(byte[] indices, byte[] colorTable, int colorTableLength, GifImageDescriptor descriptor) { + if (this.IgnoreFrames && this.image != null) + { + // we already have our images skip this + // TODO move this higher up the stack to prevent some of the data loading higher up. + return; + } + int imageWidth = this.logicalScreenDescriptor.Width; int imageHeight = this.logicalScreenDescriptor.Height; diff --git a/src/ImageSharp/Formats/Gif/GifEncoder.cs b/src/ImageSharp/Formats/Gif/GifEncoder.cs index 3ded884296..eb87000a82 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoder.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoder.cs @@ -22,16 +22,20 @@ namespace ImageSharp.Formats /// public bool IgnoreMetadata { get; set; } = false; + /// + /// Gets or sets a value indicating whether the additional frames should be ignored when the image is being encoded. + /// + public bool IgnoreFrames { get; set; } = false; + /// /// Gets or sets the encoding that should be used when writing comments. /// public Encoding TextEncoding { get; set; } = GifConstants.DefaultEncoding; /// - /// Gets or sets the quality of output for images. + /// Gets or sets the size of the color palette to use. For gifs the value ranges from 1 to 256. Leave as zero for default size. /// - /// For gifs the value ranges from 1 to 256. - public int Quality { get; set; } + public int PaletteSize { get; set; } = 0; /// /// Gets or sets the transparency threshold. @@ -50,8 +54,9 @@ namespace ImageSharp.Formats GifEncoderCore encoder = new GifEncoderCore(this.TextEncoding); encoder.Quantizer = this.Quantizer; encoder.Threshold = this.Threshold; - encoder.Quality = this.Quality; + encoder.PaletteSize = this.PaletteSize; encoder.IgnoreMetadata = this.IgnoreMetadata; + encoder.IgnoreFrames = this.IgnoreFrames; encoder.Encode(image, stream); } } diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs index bc7014f195..ccaa9a0193 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs @@ -62,13 +62,18 @@ namespace ImageSharp.Formats /// /// Gets or sets the quality of output for images. /// - public int Quality { get; internal set; } + public int PaletteSize { get; internal set; } /// /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded. /// public bool IgnoreMetadata { get; internal set; } + /// + /// Gets or sets a value indicating whether the additional frames should be ignored when the image is being encoded. + /// + public bool IgnoreFrames { get; internal set; } + /// /// Encodes the image to the specified stream from the . /// @@ -87,20 +92,20 @@ namespace ImageSharp.Formats var writer = new EndianBinaryWriter(Endianness.LittleEndian, stream); // Ensure that quality can be set but has a fallback. - int quality = this.Quality; - quality = quality > 0 ? quality.Clamp(1, 256) : 256; + int paletteSize = this.PaletteSize; + paletteSize = paletteSize > 0 ? paletteSize.Clamp(1, 256) : 256; // Get the number of bits. - this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(quality); + this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(paletteSize); - // Quantize the image returning a palette. - this.hasFrames = image.Frames.Any(); + this.hasFrames = !this.IgnoreFrames && image.Frames.Any(); // Dithering when animating gifs is a bad idea as we introduce pixel tearing across frames. var ditheredQuantizer = (IQuantizer)this.Quantizer; ditheredQuantizer.Dither = !this.hasFrames; - QuantizedImage quantized = ditheredQuantizer.Quantize(image, quality); + // Quantize the image returning a palette. + QuantizedImage quantized = ditheredQuantizer.Quantize(image, paletteSize); int index = this.GetTransparentIndex(quantized); @@ -126,7 +131,7 @@ namespace ImageSharp.Formats for (int i = 0; i < image.Frames.Count; i++) { ImageFrame frame = image.Frames[i]; - QuantizedImage quantizedFrame = ditheredQuantizer.Quantize(frame, quality); + QuantizedImage quantizedFrame = ditheredQuantizer.Quantize(frame, paletteSize); this.WriteGraphicalControlExtension(frame.MetaData, writer, this.GetTransparentIndex(quantizedFrame)); this.WriteImageDescriptor(frame, writer); diff --git a/src/ImageSharp/Formats/Gif/GifImageFormatProvider.cs b/src/ImageSharp/Formats/Gif/GifImageFormatProvider.cs index 7e521353f3..54dd294116 100644 --- a/src/ImageSharp/Formats/Gif/GifImageFormatProvider.cs +++ b/src/ImageSharp/Formats/Gif/GifImageFormatProvider.cs @@ -25,9 +25,12 @@ namespace ImageSharp.Formats host.SetMimeTypeEncoder(mimeType, encoder); } - foreach (string mimeType in GifConstants.FileExtensions) + foreach (string ext in GifConstants.FileExtensions) { - host.SetFileExtensionEncoder(mimeType, encoder); + foreach (string mimeType in GifConstants.MimeTypes) + { + host.SetFileExtensionToMimeTypeMapping(ext, mimeType); + } } var decoder = new GifDecoder(); diff --git a/src/ImageSharp/Formats/IImageFormatProvider.cs b/src/ImageSharp/Formats/IImageFormatProvider.cs index c7d354ec6d..81f78729b5 100644 --- a/src/ImageSharp/Formats/IImageFormatProvider.cs +++ b/src/ImageSharp/Formats/IImageFormatProvider.cs @@ -37,8 +37,8 @@ namespace ImageSharp.Formats /// Sets a specific image encoder as the encoder for a specific mimetype /// /// the target mimetype - /// the encoder to use - void SetFileExtensionEncoder(string extension, IImageEncoder encoder); + /// the mimetype this extenion equates to + void SetFileExtensionToMimeTypeMapping(string extension, string mimetype); /// /// Sets a specific image decoder as the decoder for a specific mimetype diff --git a/src/ImageSharp/Formats/Jpeg/JpegImageFormatProvider.cs b/src/ImageSharp/Formats/Jpeg/JpegImageFormatProvider.cs index 6cd49e20e5..ca2d019fe5 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegImageFormatProvider.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegImageFormatProvider.cs @@ -25,9 +25,12 @@ namespace ImageSharp.Formats host.SetMimeTypeEncoder(mimeType, pngEncoder); } - foreach (string mimeType in JpegConstants.FileExtensions) + foreach (string ext in JpegConstants.FileExtensions) { - host.SetFileExtensionEncoder(mimeType, pngEncoder); + foreach (string mimeType in JpegConstants.MimeTypes) + { + host.SetFileExtensionToMimeTypeMapping(ext, mimeType); + } } var pngDecoder = new JpegDecoder(); diff --git a/src/ImageSharp/Formats/Png/PngEncoder.cs b/src/ImageSharp/Formats/Png/PngEncoder.cs index d15161ded7..954150e8e8 100644 --- a/src/ImageSharp/Formats/Png/PngEncoder.cs +++ b/src/ImageSharp/Formats/Png/PngEncoder.cs @@ -17,14 +17,14 @@ namespace ImageSharp.Formats public class PngEncoder : IImageEncoder { /// - /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded. + /// Gets or sets a value indicating whether the metadata should be ignored when the image is being encoded. /// public bool IgnoreMetadata { get; set; } /// - /// Gets or sets the quality of output for images. + /// Gets or sets the size of the color palette to use. Set to zero to leav png encoding to use pixel data. /// - public int Quality { get; set; } + public int PaletteSize { get; set; } = 0; /// /// Gets or sets the png color type @@ -74,7 +74,7 @@ namespace ImageSharp.Formats { encode.IgnoreMetadata = this.IgnoreMetadata; - encode.Quality = this.Quality > 0 ? this.Quality.Clamp(1, int.MaxValue) : int.MaxValue; + encode.PaletteSize = this.PaletteSize > 0 ? this.PaletteSize.Clamp(1, int.MaxValue) : int.MaxValue; encode.PngColorType = this.PngColorType; encode.CompressionLevel = this.CompressionLevel; encode.Gamma = this.Gamma; diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index c96d60dbd2..c0cd0ffaf1 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -133,7 +133,7 @@ namespace ImageSharp.Formats /// /// Gets or sets the Quality value /// - public int Quality { get; internal set; } + public int PaletteSize { get; internal set; } /// /// Gets or sets the Quality value @@ -196,19 +196,19 @@ namespace ImageSharp.Formats this.quantizer = this.Quantizer; // Set correct color type if the color count is 256 or less. - if (this.Quality <= 256) + if (this.PaletteSize <= 256) { this.pngColorType = PngColorType.Palette; } - if (this.pngColorType == PngColorType.Palette && this.Quality > 256) + if (this.pngColorType == PngColorType.Palette && this.PaletteSize > 256) { - this.Quality = 256; + this.PaletteSize = 256; } // Set correct bit depth. - this.bitDepth = this.Quality <= 256 - ? (byte)ImageMaths.GetBitsNeededForColorDepth(this.Quality).Clamp(1, 8) + this.bitDepth = this.PaletteSize <= 256 + ? (byte)ImageMaths.GetBitsNeededForColorDepth(this.PaletteSize).Clamp(1, 8) : (byte)8; // Png only supports in four pixel depths: 1, 2, 4, and 8 bits when using the PLTE chunk @@ -538,7 +538,7 @@ namespace ImageSharp.Formats private QuantizedImage WritePaletteChunk(Stream stream, PngHeader header, ImageBase image) where TPixel : struct, IPixel { - if (this.Quality > 256) + if (this.PaletteSize > 256) { return null; } @@ -549,7 +549,7 @@ namespace ImageSharp.Formats } // Quantize the image returning a palette. This boxing is icky. - QuantizedImage quantized = ((IQuantizer)this.quantizer).Quantize(image, this.Quality); + QuantizedImage quantized = ((IQuantizer)this.quantizer).Quantize(image, this.PaletteSize); // Grab the palette and write it to the stream. TPixel[] palette = quantized.Palette; diff --git a/src/ImageSharp/Formats/Png/PngImageFormatProvider.cs b/src/ImageSharp/Formats/Png/PngImageFormatProvider.cs index 5708cc812a..fc5ac44501 100644 --- a/src/ImageSharp/Formats/Png/PngImageFormatProvider.cs +++ b/src/ImageSharp/Formats/Png/PngImageFormatProvider.cs @@ -25,9 +25,12 @@ namespace ImageSharp.Formats host.SetMimeTypeEncoder(mimeType, pngEncoder); } - foreach (string mimeType in PngConstants.FileExtensions) + foreach (string ext in PngConstants.FileExtensions) { - host.SetFileExtensionEncoder(mimeType, pngEncoder); + foreach (string mimeType in PngConstants.MimeTypes) + { + host.SetFileExtensionToMimeTypeMapping(ext, mimeType); + } } var pngDecoder = new PngDecoder(); diff --git a/src/ImageSharp/Image/Image.Decode.cs b/src/ImageSharp/Image/Image.Decode.cs index 96d5df45f2..00725d72ba 100644 --- a/src/ImageSharp/Image/Image.Decode.cs +++ b/src/ImageSharp/Image/Image.Decode.cs @@ -55,10 +55,13 @@ namespace ImageSharp /// The image format or null if none found. private static IImageDecoder DiscoverDecoder(Stream stream, Configuration config, out string mimeType) { - - format = config.FindMimeTypeDecoder(mimeType); + mimeType = InternalDiscoverMimeType(stream, config); + if (mimeType != null) + { + return config.FindMimeTypeDecoder(mimeType); + } - return format; + return null; } #pragma warning disable SA1008 // Opening parenthesis must be spaced correctly diff --git a/src/ImageSharp/Image/Image.FromStream.cs b/src/ImageSharp/Image/Image.FromStream.cs index 1aa93525c1..5bd87b145a 100644 --- a/src/ImageSharp/Image/Image.FromStream.cs +++ b/src/ImageSharp/Image/Image.FromStream.cs @@ -6,6 +6,7 @@ namespace ImageSharp { using System; + using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; @@ -202,9 +203,9 @@ namespace ImageSharp var stringBuilder = new StringBuilder(); stringBuilder.AppendLine("Image cannot be loaded. Available decoders:"); - foreach (Type format in config.AllMimeImageDecoders) + foreach (KeyValuePair val in config.ImageDecoders) { - stringBuilder.AppendLine(" - " + format.Name); + stringBuilder.AppendLine($" - {val.Key} : {val.Value.GetType().Name}"); } throw new NotSupportedException(stringBuilder.ToString()); diff --git a/src/ImageSharp/Image/Image{TPixel}.cs b/src/ImageSharp/Image/Image{TPixel}.cs index af9cc3914a..d8aff50419 100644 --- a/src/ImageSharp/Image/Image{TPixel}.cs +++ b/src/ImageSharp/Image/Image{TPixel}.cs @@ -165,9 +165,9 @@ namespace ImageSharp var stringBuilder = new StringBuilder(); stringBuilder.AppendLine("Can't find encoder for provided mime type. Available encoded:"); - foreach (Type format in this.Configuration.AllMimeImageEncoders) + foreach (KeyValuePair val in this.Configuration.ImageEncoders) { - stringBuilder.AppendLine(" - " + format); + stringBuilder.AppendLine($" - {val.Key} : {val.Value.GetType().Name}"); } throw new NotSupportedException(stringBuilder.ToString()); @@ -211,11 +211,22 @@ namespace ImageSharp if (encoder == null) { var stringBuilder = new StringBuilder(); - stringBuilder.AppendLine($"Can't find encoder for file extention '{ext}'. Available encoded:"); - - foreach (Type format in this.Configuration.AllExtImageEncoders) + string mime = this.Configuration.FindFileExtensionsMimeType(ext); + if (mime == null) + { + stringBuilder.AppendLine($"Can't find a mime type for the file extention '{ext}'. Registerd File extension maps include:"); + foreach (KeyValuePair map in this.Configuration.ImageExtensionToMimeTypeMapping) + { + stringBuilder.AppendLine($" - {map.Key} : {map.Value}"); + } + } + else { - stringBuilder.AppendLine(" - " + format); + stringBuilder.AppendLine($"Can't find encoder for file extention '{ext}' using mime type '{mime}'. Registerd encoders include:"); + foreach (KeyValuePair enc in this.Configuration.ImageEncoders) + { + stringBuilder.AppendLine($" - {enc.Key} : {enc.Value.GetType().Name}"); + } } throw new NotSupportedException(stringBuilder.ToString()); diff --git a/tests/ImageSharp.Benchmarks/Image/EncodeIndexedPng.cs b/tests/ImageSharp.Benchmarks/Image/EncodeIndexedPng.cs index aa3112f524..02e3211a76 100644 --- a/tests/ImageSharp.Benchmarks/Image/EncodeIndexedPng.cs +++ b/tests/ImageSharp.Benchmarks/Image/EncodeIndexedPng.cs @@ -53,7 +53,7 @@ namespace ImageSharp.Benchmarks.Image { using (MemoryStream memoryStream = new MemoryStream()) { - PngEncoder encoder = new PngEncoder() { Quantizer = new OctreeQuantizer(), Quality = 256 }; + PngEncoder encoder = new PngEncoder() { Quantizer = new OctreeQuantizer(), PaletteSize = 256 }; this.bmpCore.SaveAsPng(memoryStream, encoder); } @@ -64,7 +64,7 @@ namespace ImageSharp.Benchmarks.Image { using (MemoryStream memoryStream = new MemoryStream()) { - PngEncoder options = new PngEncoder { Quantizer = new OctreeQuantizer { Dither = false }, Quality = 256 }; + PngEncoder options = new PngEncoder { Quantizer = new OctreeQuantizer { Dither = false }, PaletteSize = 256 }; this.bmpCore.SaveAsPng(memoryStream, options); } @@ -75,7 +75,7 @@ namespace ImageSharp.Benchmarks.Image { using (MemoryStream memoryStream = new MemoryStream()) { - PngEncoder options = new PngEncoder { Quantizer = new PaletteQuantizer(), Quality = 256 }; + PngEncoder options = new PngEncoder { Quantizer = new PaletteQuantizer(), PaletteSize = 256 }; this.bmpCore.SaveAsPng(memoryStream, options); } @@ -86,7 +86,7 @@ namespace ImageSharp.Benchmarks.Image { using (MemoryStream memoryStream = new MemoryStream()) { - PngEncoder options = new PngEncoder { Quantizer = new PaletteQuantizer { Dither = false }, Quality = 256 }; + PngEncoder options = new PngEncoder { Quantizer = new PaletteQuantizer { Dither = false }, PaletteSize = 256 }; this.bmpCore.SaveAsPng(memoryStream, options); } @@ -97,7 +97,7 @@ namespace ImageSharp.Benchmarks.Image { using (MemoryStream memoryStream = new MemoryStream()) { - PngEncoder options = new PngEncoder() { Quantizer = new WuQuantizer(), Quality = 256 }; + PngEncoder options = new PngEncoder() { Quantizer = new WuQuantizer(), PaletteSize = 256 }; this.bmpCore.SaveAsPng(memoryStream, options); } diff --git a/tests/ImageSharp.Tests/ConfigurationTests.cs b/tests/ImageSharp.Tests/ConfigurationTests.cs index e8927c75c4..64b96168a5 100644 --- a/tests/ImageSharp.Tests/ConfigurationTests.cs +++ b/tests/ImageSharp.Tests/ConfigurationTests.cs @@ -40,8 +40,8 @@ namespace ImageSharp.Tests [Fact] public void IfAutoloadWellknwonFormatesIsTrueAllFormateAreLoaded() { - Assert.Equal(4, DefaultConfiguration.AllMimeImageDecoders.Count()); - Assert.Equal(4, DefaultConfiguration.AllMimeImageDecoders.Count()); + Assert.Equal(6, DefaultConfiguration.ImageEncoders.Count()); + Assert.Equal(6, DefaultConfiguration.ImageDecoders.Count()); } /// @@ -103,15 +103,15 @@ namespace ImageSharp.Tests { Assert.Throws(() => { - DefaultConfiguration.SetFileExtensionEncoder(null, new Mock().Object); + DefaultConfiguration.SetFileExtensionToMimeTypeMapping(null, "str"); }); Assert.Throws(() => { - DefaultConfiguration.SetFileExtensionEncoder("sdsdsd", null); + DefaultConfiguration.SetFileExtensionToMimeTypeMapping("sdsdsd", null); }); Assert.Throws(() => { - DefaultConfiguration.SetFileExtensionEncoder(null, null); + DefaultConfiguration.SetFileExtensionToMimeTypeMapping(null, null); }); } @@ -150,14 +150,14 @@ namespace ImageSharp.Tests [Fact] public void RegisterFileExtEnecoderReplacesLast() { - var encoder1 = new Mock().Object; - ConfigurationEmpty.SetFileExtensionEncoder("TEST", encoder1); - var found = ConfigurationEmpty.FindFileExtensionsEncoder("test"); + var encoder1 = "mime1"; + ConfigurationEmpty.SetFileExtensionToMimeTypeMapping("TEST", encoder1); + var found = ConfigurationEmpty.FindFileExtensionsMimeType("test"); Assert.Equal(encoder1, found); - var encoder2 = new Mock().Object; - ConfigurationEmpty.SetFileExtensionEncoder("test", encoder2); - var found2 = ConfigurationEmpty.FindFileExtensionsEncoder("TEST"); + var encoder2 = "mime2"; + ConfigurationEmpty.SetFileExtensionToMimeTypeMapping("test", encoder2); + var found2 = ConfigurationEmpty.FindFileExtensionsMimeType("TEST"); Assert.Equal(encoder2, found2); Assert.NotEqual(found, found2); } diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs index 06bfd8990d..cc7935fbfc 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs @@ -18,6 +18,19 @@ namespace ImageSharp.Tests public static readonly string[] TestFiles = { TestImages.Gif.Giphy, TestImages.Gif.Rings, TestImages.Gif.Trans }; + [Fact] + public void SkipDecodingFrames() + { + var file = TestFile.GetPath(TestImages.Gif.Giphy); + + using (Image image = Image.Load(file, new GifDecoder() { IgnoreFrames = true })) + using (Image imageWithFrames = Image.Load(file, new GifDecoder() { IgnoreFrames = false })) + { + Assert.NotEmpty(imageWithFrames.Frames); + Assert.Empty(image.Frames); + } + } + [Theory] [WithFileCollection(nameof(TestFiles), PixelTypes)] public void DecodeAndReSave(TestImageProvider imageProvider) diff --git a/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs index bb7824914c..bac340a71c 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs @@ -50,7 +50,7 @@ namespace ImageSharp.Tests.Formats.Png using (MemoryStream ms = new MemoryStream()) { // image.Save(provider.Utility.GetTestOutputFileName("bmp")); - image.Save(ms, new PngEncoder() { Quality = 256 }); + image.Save(ms, new PngEncoder() { PaletteSize = 256 }); ms.Position = 0; using (Image img2 = Image.Load(ms, new PngDecoder())) { diff --git a/tests/ImageSharp.Tests/Image/ImageSaveTests.cs b/tests/ImageSharp.Tests/Image/ImageSaveTests.cs index e2e0b13648..72461cfc7c 100644 --- a/tests/ImageSharp.Tests/Image/ImageSaveTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageSaveTests.cs @@ -43,7 +43,7 @@ namespace ImageSharp.Tests }; config.AddMimeTypeDetector(this.localMimeTypeDetector.Object); config.SetMimeTypeEncoder("img/test", this.encoder.Object); - config.SetFileExtensionEncoder("png", this.encoder.Object); + config.SetFileExtensionToMimeTypeMapping("png", "img/test"); this.Image = new Image(config, 1, 1); } diff --git a/tests/ImageSharp.Tests/TestFormat.cs b/tests/ImageSharp.Tests/TestFormat.cs index 701b02c09e..85e11ee876 100644 --- a/tests/ImageSharp.Tests/TestFormat.cs +++ b/tests/ImageSharp.Tests/TestFormat.cs @@ -114,7 +114,7 @@ namespace ImageSharp.Tests host.AddMimeTypeDetector(new TestHeader(this)); foreach (var ext in this.SupportedExtensions) { - host.SetFileExtensionEncoder(ext, new TestEncoder(this)); + host.SetFileExtensionToMimeTypeMapping(ext, this.MimeType); } host.SetMimeTypeEncoder(this.MimeType, new TestEncoder(this)); From 8cd195cb34d6eddac22daa9d3c508fee25ea2692 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Sat, 24 Jun 2017 12:15:42 +0100 Subject: [PATCH 04/36] discover types tests --- .../Image/ImageDiscoverMimeType.cs | 107 ++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 tests/ImageSharp.Tests/Image/ImageDiscoverMimeType.cs diff --git a/tests/ImageSharp.Tests/Image/ImageDiscoverMimeType.cs b/tests/ImageSharp.Tests/Image/ImageDiscoverMimeType.cs new file mode 100644 index 0000000000..80414662b6 --- /dev/null +++ b/tests/ImageSharp.Tests/Image/ImageDiscoverMimeType.cs @@ -0,0 +1,107 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System; + using System.IO; + + using ImageSharp.Formats; + using ImageSharp.IO; + using ImageSharp.PixelFormats; + using Moq; + using Xunit; + + /// + /// Tests the class. + /// + public class DiscoverMimeTypeTests + { + private readonly Mock fileSystem; + private readonly string FilePath; + private readonly Mock localMimeTypeDetector; + + public Configuration LocalConfiguration { get; private set; } + public byte[] Marker { get; private set; } + public MemoryStream DataStream { get; private set; } + public byte[] DecodedData { get; private set; } + private const string localMimeType = "image/local"; + + public DiscoverMimeTypeTests() + { + this.localMimeTypeDetector = new Mock(); + this.localMimeTypeDetector.Setup(x => x.HeaderSize).Returns(1); + this.localMimeTypeDetector.Setup(x => x.DetectMimeType(It.IsAny>())).Returns(localMimeType); + + this.fileSystem = new Mock(); + + this.LocalConfiguration = new Configuration() + { + FileSystem = this.fileSystem.Object + }; + this.LocalConfiguration.AddMimeTypeDetector(this.localMimeTypeDetector.Object); + + TestFormat.RegisterGloablTestFormat(); + this.Marker = Guid.NewGuid().ToByteArray(); + this.DataStream = TestFormat.GlobalTestFormat.CreateStream(this.Marker); + + 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] + public void DiscoverMimeTypeByteArray() + { + var type = Image.DiscoverMimeType(DataStream.ToArray()); + Assert.Equal(TestFormat.GlobalTestFormat.MimeType, type); + } + + [Fact] + public void DiscoverMimeTypeByteArray_WithConfig() + { + var type = Image.DiscoverMimeType(this.LocalConfiguration, DataStream.ToArray()); + Assert.Equal(localMimeType, type); + } + + [Fact] + public void DiscoverMimeTypeFile() + { + var type = Image.DiscoverMimeType(this.FilePath); + Assert.Equal(TestFormat.GlobalTestFormat.MimeType, type); + } + + [Fact] + public void DiscoverMimeTypeFilePath_WithConfig() + { + var type = Image.DiscoverMimeType(this.LocalConfiguration, FilePath); + Assert.Equal(localMimeType, type); + } + + + [Fact] + public void DiscoverMimeTypeStream() + { + var type = Image.DiscoverMimeType(this.DataStream); + Assert.Equal(TestFormat.GlobalTestFormat.MimeType, type); + } + + [Fact] + public void DiscoverMimeTypeFileStream_WithConfig() + { + var type = Image.DiscoverMimeType(this.LocalConfiguration, DataStream); + Assert.Equal(localMimeType, type); + } + + [Fact] + public void DiscoverMimeTypeNoDetectorsRegisterdShouldReturnNull() + { + var type = Image.DiscoverMimeType(new Configuration(), DataStream); + Assert.Null(type); + } + } +} From fbed11bb78ac6b235023fc4d93dbdda59d617a09 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Sat, 24 Jun 2017 13:47:17 +0100 Subject: [PATCH 05/36] fix comments --- src/ImageSharp/Formats/Bmp/BmpConstants.cs | 2 +- src/ImageSharp/Formats/Gif/GifConstants.cs | 2 +- src/ImageSharp/Formats/Jpeg/JpegConstants.cs | 2 +- src/ImageSharp/Formats/Png/PngConstants.cs | 2 +- tests/ImageSharp.Tests/ConfigurationTests.cs | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/ImageSharp/Formats/Bmp/BmpConstants.cs b/src/ImageSharp/Formats/Bmp/BmpConstants.cs index f2454f24dd..43b6dd9007 100644 --- a/src/ImageSharp/Formats/Bmp/BmpConstants.cs +++ b/src/ImageSharp/Formats/Bmp/BmpConstants.cs @@ -18,7 +18,7 @@ namespace ImageSharp.Formats public static readonly IEnumerable MimeTypes = new[] { "image/bmp", "image/x-windows-bmp" }; /// - /// The list of mimetypes that equate to a bmp + /// The list of file extensions that equate to a bmp /// public static readonly IEnumerable FileExtensions = new[] { "bm", "bmp", "dip" }; } diff --git a/src/ImageSharp/Formats/Gif/GifConstants.cs b/src/ImageSharp/Formats/Gif/GifConstants.cs index 5c4d806d70..e4d3be3d61 100644 --- a/src/ImageSharp/Formats/Gif/GifConstants.cs +++ b/src/ImageSharp/Formats/Gif/GifConstants.cs @@ -99,7 +99,7 @@ namespace ImageSharp.Formats public static readonly IEnumerable MimeTypes = new[] { "image/gif" }; /// - /// The list of mimetypes that equate to a bmp + /// The list of file extensions that equate to a bmp /// public static readonly IEnumerable FileExtensions = new[] { "gif" }; } diff --git a/src/ImageSharp/Formats/Jpeg/JpegConstants.cs b/src/ImageSharp/Formats/Jpeg/JpegConstants.cs index 9598136119..27ba4190ea 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegConstants.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegConstants.cs @@ -23,7 +23,7 @@ namespace ImageSharp.Formats public static readonly IEnumerable MimeTypes = new[] { "image/jpeg", "image/pjpeg" }; /// - /// The list of mimetypes that equate to a jpeg + /// The list of file extensions that equate to a jpeg /// public static readonly IEnumerable FileExtensions = new[] { "jpg", "jpeg", "jfif" }; diff --git a/src/ImageSharp/Formats/Png/PngConstants.cs b/src/ImageSharp/Formats/Png/PngConstants.cs index b44ab76638..8528e93ee3 100644 --- a/src/ImageSharp/Formats/Png/PngConstants.cs +++ b/src/ImageSharp/Formats/Png/PngConstants.cs @@ -23,7 +23,7 @@ namespace ImageSharp.Formats public static readonly IEnumerable MimeTypes = new[] { "image/png" }; /// - /// The list of mimetypes that equate to a jpeg + /// The list of file extensions that equate to a jpeg /// public static readonly IEnumerable FileExtensions = new[] { "png" }; } diff --git a/tests/ImageSharp.Tests/ConfigurationTests.cs b/tests/ImageSharp.Tests/ConfigurationTests.cs index 64b96168a5..aa09cf8166 100644 --- a/tests/ImageSharp.Tests/ConfigurationTests.cs +++ b/tests/ImageSharp.Tests/ConfigurationTests.cs @@ -27,7 +27,7 @@ namespace ImageSharp.Tests public ConfigurationTests() { this.DefaultConfiguration = Configuration.CreateDefaultInstance(); - this.ConfigurationEmpty = Configuration.CreateDefaultInstance(); + this.ConfigurationEmpty = new Configuration(); } [Fact] From f8f1305076a14da3dbd6cd0dc3aea3b97efb34ce Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Sat, 24 Jun 2017 14:01:27 +0100 Subject: [PATCH 06/36] update comment --- src/ImageSharp/Formats/IImageFormatProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/IImageFormatProvider.cs b/src/ImageSharp/Formats/IImageFormatProvider.cs index 81f78729b5..34e90e2f6d 100644 --- a/src/ImageSharp/Formats/IImageFormatProvider.cs +++ b/src/ImageSharp/Formats/IImageFormatProvider.cs @@ -34,7 +34,7 @@ namespace ImageSharp.Formats void SetMimeTypeEncoder(string mimeType, IImageEncoder encoder); // could/should this be an Action??? /// - /// Sets a specific image encoder as the encoder for a specific mimetype + /// Sets a mapping value between a file extension and a mimetype /// /// the target mimetype /// the mimetype this extenion equates to From 6d4e4f2f23e5b67653eb2cf6f774aeefd9ef56b0 Mon Sep 17 00:00:00 2001 From: Dirk Lemstra Date: Sun, 25 Jun 2017 12:08:40 +0200 Subject: [PATCH 07/36] Various comment fixes and some variable renames. --- src/ImageSharp/Configuration.cs | 72 ++++++++++--------- src/ImageSharp/Formats/Bmp/BmpConstants.cs | 4 +- src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs | 2 +- .../Formats/Bmp/BmpImageFormatProvider.cs | 2 +- src/ImageSharp/Formats/Gif/GifConstants.cs | 4 +- src/ImageSharp/Formats/Gif/GifEncoder.cs | 2 +- src/ImageSharp/Formats/Gif/GifEncoderCore.cs | 4 +- .../Formats/Gif/GifImageFormatProvider.cs | 2 +- .../Formats/IImageFormatProvider.cs | 24 +++---- src/ImageSharp/Formats/Jpeg/JpegConstants.cs | 4 +- .../Formats/Jpeg/JpegImageFormatProvider.cs | 2 +- src/ImageSharp/Formats/Png/PngConstants.cs | 6 +- src/ImageSharp/Formats/Png/PngEncoder.cs | 20 +++--- .../Formats/Png/PngImageFormatProvider.cs | 2 +- src/ImageSharp/Image/Image.Decode.cs | 2 +- src/ImageSharp/Image/Image.FromBytes.cs | 36 +++++----- src/ImageSharp/Image/Image.FromFile.cs | 28 ++++---- src/ImageSharp/Image/Image.FromStream.cs | 16 ++--- src/ImageSharp/Image/Image{TPixel}.cs | 4 +- 19 files changed, 120 insertions(+), 116 deletions(-) diff --git a/src/ImageSharp/Configuration.cs b/src/ImageSharp/Configuration.cs index 890a0cbf94..f48aefbcd8 100644 --- a/src/ImageSharp/Configuration.cs +++ b/src/ImageSharp/Configuration.cs @@ -31,17 +31,17 @@ namespace ImageSharp private readonly object syncRoot = new object(); /// - /// The list of supported keyed to mimestypes. + /// The list of supported keyed to mime types. /// private readonly ConcurrentDictionary mimeTypeEncoders = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); /// - /// The list of supported keyed to fiel extensions. + /// The list of supported mime types keyed to file extensions. /// private readonly ConcurrentDictionary extensionsMap = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); /// - /// The list of supported keyed to mimestypes. + /// The list of supported keyed to mime types. /// private readonly ConcurrentDictionary mimeTypeDecoders = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); @@ -83,27 +83,27 @@ namespace ImageSharp public ParallelOptions ParallelOptions { get; } = new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount }; /// - /// Gets the maximum header size of all formats. + /// Gets the maximum header size of all the formats. /// internal int MaxHeaderSize { get; private set; } /// - /// Gets the currently registerd s. + /// Gets the currently registered s. /// internal IEnumerable MimeTypeDetectors => this.mimeTypeDetectors; /// - /// Gets the typeof of all the current image decoders + /// Gets the currently registered s. /// internal IEnumerable> ImageDecoders => this.mimeTypeDecoders; /// - /// Gets the typeof of all the current image decoders + /// Gets the currently registered s. /// internal IEnumerable> ImageEncoders => this.mimeTypeEncoders; /// - /// Gets the typeof of all the current image decoders + /// Gets the currently registered file extensions. /// internal IEnumerable> ImageExtensionToMimeTypeMapping => this.extensionsMap; @@ -133,11 +133,11 @@ namespace ImageSharp } /// - public void SetFileExtensionToMimeTypeMapping(string extension, string mimetype) + public void SetFileExtensionToMimeTypeMapping(string extension, string mimeType) { Guard.NotNullOrEmpty(extension, nameof(extension)); - Guard.NotNullOrEmpty(mimetype, nameof(mimetype)); - this.extensionsMap.AddOrUpdate(extension?.Trim(), mimetype, (s, e) => mimetype); + Guard.NotNullOrEmpty(mimeType, nameof(mimeType)); + this.extensionsMap.AddOrUpdate(extension?.Trim(), mimeType, (s, e) => mimeType); } /// @@ -149,7 +149,7 @@ namespace ImageSharp } /// - /// Removes all the registerd detectors + /// Removes all the registered mime type detectors. /// public void ClearMimeTypeDetectors() { @@ -165,9 +165,13 @@ namespace ImageSharp } /// - /// Creates the default instance, with Png, Jpeg, Gif and Bmp preregisterd (if they have been referenced) + /// Creates the default instance with the following s preregistered: + /// + /// + /// + /// /// - /// The default configuration of + /// The default configuration of internal static Configuration CreateDefaultInstance() { return new Configuration( @@ -178,73 +182,73 @@ namespace ImageSharp } /// - /// For the specified mimetype find the decoder. + /// For the specified mime type find the decoder. /// - /// the mimetype to discover - /// the IImageDecoder if found othersize null + /// The mime type to discover + /// The if found otherwise null internal IImageDecoder FindMimeTypeDecoder(string mimeType) { Guard.NotNullOrEmpty(mimeType, nameof(mimeType)); - if (this.mimeTypeDecoders.TryGetValue(mimeType, out IImageDecoder dec)) + if (this.mimeTypeDecoders.TryGetValue(mimeType, out IImageDecoder decoder)) { - return dec; + return decoder; } return null; } /// - /// For the specified mimetype find the encoder. + /// For the specified mime type find the encoder. /// - /// the mimetype to discover - /// the IImageEncoder if found othersize null + /// The mime type to discover + /// The if found otherwise null internal IImageEncoder FindMimeTypeEncoder(string mimeType) { Guard.NotNullOrEmpty(mimeType, nameof(mimeType)); - if (this.mimeTypeEncoders.TryGetValue(mimeType, out IImageEncoder dec)) + if (this.mimeTypeEncoders.TryGetValue(mimeType, out IImageEncoder encoder)) { - return dec; + return encoder; } return null; } /// - /// For the specified mimetype find the encoder. + /// For the specified mime type find the encoder. /// - /// the extensions to discover - /// the IImageEncoder if found othersize null + /// The extensions to discover + /// The if found otherwise null internal IImageEncoder FindFileExtensionsEncoder(string extensions) { extensions = extensions?.TrimStart('.'); Guard.NotNullOrEmpty(extensions, nameof(extensions)); - if (this.extensionsMap.TryGetValue(extensions, out string mime)) + if (this.extensionsMap.TryGetValue(extensions, out string mimeType)) { - return this.FindMimeTypeEncoder(mime); + return this.FindMimeTypeEncoder(mimeType); } return null; } /// - /// For the specified mimetype find the encoder. + /// For the specified extension find the mime type. /// /// the extensions to discover - /// the IImageEncoder if found othersize null + /// The mime type if found otherwise null internal string FindFileExtensionsMimeType(string extensions) { extensions = extensions?.TrimStart('.'); Guard.NotNullOrEmpty(extensions, nameof(extensions)); - if (this.extensionsMap.TryGetValue(extensions, out string mime)) + if (this.extensionsMap.TryGetValue(extensions, out string mimeType)) { - return mime; + return mimeType; } return null; } /// - /// Sets max header size. + /// Sets the max header size. /// private void SetMaxHeaderSize() { diff --git a/src/ImageSharp/Formats/Bmp/BmpConstants.cs b/src/ImageSharp/Formats/Bmp/BmpConstants.cs index 43b6dd9007..d394b61f6e 100644 --- a/src/ImageSharp/Formats/Bmp/BmpConstants.cs +++ b/src/ImageSharp/Formats/Bmp/BmpConstants.cs @@ -13,12 +13,12 @@ namespace ImageSharp.Formats internal static class BmpConstants { /// - /// The list of mimetypes that equate to a bmp + /// The list of mimetypes that equate to a bmp. /// public static readonly IEnumerable MimeTypes = new[] { "image/bmp", "image/x-windows-bmp" }; /// - /// The list of file extensions that equate to a bmp + /// The list of file extensions that equate to a bmp. /// public static readonly IEnumerable FileExtensions = new[] { "bm", "bmp", "dip" }; } diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs index 615ff23ee2..daff65cbc7 100644 --- a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs @@ -30,7 +30,7 @@ namespace ImageSharp.Formats } /// - /// Gets or sets the BitsPerPixel + /// Gets or sets the number of bits per pixel. /// public BmpBitsPerPixel BitsPerPixel { get; internal set; } = BmpBitsPerPixel.Pixel24; diff --git a/src/ImageSharp/Formats/Bmp/BmpImageFormatProvider.cs b/src/ImageSharp/Formats/Bmp/BmpImageFormatProvider.cs index 1c4cada768..b532ccfb51 100644 --- a/src/ImageSharp/Formats/Bmp/BmpImageFormatProvider.cs +++ b/src/ImageSharp/Formats/Bmp/BmpImageFormatProvider.cs @@ -12,7 +12,7 @@ namespace ImageSharp.Formats using ImageSharp.PixelFormats; /// - /// Detects gif file headers + /// Registers the image encoders, decoders and mime type detectors for the bmp format. /// public class BmpImageFormatProvider : IImageFormatProvider { diff --git a/src/ImageSharp/Formats/Gif/GifConstants.cs b/src/ImageSharp/Formats/Gif/GifConstants.cs index e4d3be3d61..7b215b773b 100644 --- a/src/ImageSharp/Formats/Gif/GifConstants.cs +++ b/src/ImageSharp/Formats/Gif/GifConstants.cs @@ -94,12 +94,12 @@ namespace ImageSharp.Formats public static readonly Encoding DefaultEncoding = Encoding.GetEncoding("ASCII"); /// - /// The list of mimetypes that equate to a bmp + /// The list of mimetypes that equate to a gif. /// public static readonly IEnumerable MimeTypes = new[] { "image/gif" }; /// - /// The list of file extensions that equate to a bmp + /// The list of file extensions that equate to a gif. /// public static readonly IEnumerable FileExtensions = new[] { "gif" }; } diff --git a/src/ImageSharp/Formats/Gif/GifEncoder.cs b/src/ImageSharp/Formats/Gif/GifEncoder.cs index eb87000a82..ee78c32668 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoder.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoder.cs @@ -33,7 +33,7 @@ namespace ImageSharp.Formats public Encoding TextEncoding { get; set; } = GifConstants.DefaultEncoding; /// - /// Gets or sets the size of the color palette to use. For gifs the value ranges from 1 to 256. Leave as zero for default size. + /// Gets or sets the size of the color palette to use. For gifs the value ranges from 1 to 256. Leave as zero for default size. /// public int PaletteSize { get; set; } = 0; diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs index ccaa9a0193..3191aafc97 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs @@ -60,7 +60,7 @@ namespace ImageSharp.Formats public byte Threshold { get; internal set; } /// - /// Gets or sets the quality of output for images. + /// Gets or sets the size of the color palette to use. /// public int PaletteSize { get; internal set; } @@ -91,7 +91,7 @@ namespace ImageSharp.Formats // Do not use IDisposable pattern here as we want to preserve the stream. var writer = new EndianBinaryWriter(Endianness.LittleEndian, stream); - // Ensure that quality can be set but has a fallback. + // Ensure that pallete size can be set but has a fallback. int paletteSize = this.PaletteSize; paletteSize = paletteSize > 0 ? paletteSize.Clamp(1, 256) : 256; diff --git a/src/ImageSharp/Formats/Gif/GifImageFormatProvider.cs b/src/ImageSharp/Formats/Gif/GifImageFormatProvider.cs index 54dd294116..7e16bff720 100644 --- a/src/ImageSharp/Formats/Gif/GifImageFormatProvider.cs +++ b/src/ImageSharp/Formats/Gif/GifImageFormatProvider.cs @@ -12,7 +12,7 @@ namespace ImageSharp.Formats using ImageSharp.PixelFormats; /// - /// Detects gif file headers + /// Registers the image encoders, decoders and mime type detectors for the gif format. /// public class GifImageFormatProvider : IImageFormatProvider { diff --git a/src/ImageSharp/Formats/IImageFormatProvider.cs b/src/ImageSharp/Formats/IImageFormatProvider.cs index 34e90e2f6d..1e5ea7f93e 100644 --- a/src/ImageSharp/Formats/IImageFormatProvider.cs +++ b/src/ImageSharp/Formats/IImageFormatProvider.cs @@ -10,47 +10,47 @@ namespace ImageSharp.Formats using System.Text; /// - /// Represents an abstract class that can register image encoders, decoders and mime type detectors + /// Represents an interface that can register image encoders, decoders and mime type detectors. /// public interface IImageFormatProvider { /// - /// Called when loaded so the provider and register its encoders, decodes and mime type detectors into an IImageFormatHost. + /// Called when loaded so the provider and register its encoders, decoders and mime type detectors into an IImageFormatHost. /// /// The host that will retain the encoders, decodes and mime type detectors. void Configure(IImageFormatHost host); } /// - /// Represents an abstract class that can have encoders decoders and mimetype detecotrs loaded into. + /// Represents an interface that can have encoders, decoders and mime type detectors loaded into. /// public interface IImageFormatHost { /// - /// Sets a specific image encoder as the encoder for a specific mimetype + /// Sets a specific image encoder as the encoder for a specific mime type. /// /// the target mimetype /// the encoder to use void SetMimeTypeEncoder(string mimeType, IImageEncoder encoder); // could/should this be an Action??? /// - /// Sets a mapping value between a file extension and a mimetype + /// Sets a mapping value between a file extension and a mime type. /// - /// the target mimetype - /// the mimetype this extenion equates to + /// The target mime type + /// The mime type this extension equates to void SetFileExtensionToMimeTypeMapping(string extension, string mimetype); /// - /// Sets a specific image decoder as the decoder for a specific mimetype + /// Sets a specific image decoder as the decoder for a specific mime type. /// - /// the target mimetype - /// the decoder to use + /// The target mime type + /// The decoder to use void SetMimeTypeDecoder(string mimeType, IImageDecoder decoder); /// - /// Adds a new detector for detecting in mime types + /// Adds a new detector for detecting mime types. /// - /// The detector + /// The detector to add void AddMimeTypeDetector(IMimeTypeDetector detector); } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegConstants.cs b/src/ImageSharp/Formats/Jpeg/JpegConstants.cs index 27ba4190ea..99c0399dcc 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegConstants.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegConstants.cs @@ -18,12 +18,12 @@ namespace ImageSharp.Formats public const ushort MaxLength = 65535; /// - /// The list of mimetypes that equate to a jpeg + /// The list of mimetypes that equate to a jpeg. /// public static readonly IEnumerable MimeTypes = new[] { "image/jpeg", "image/pjpeg" }; /// - /// The list of file extensions that equate to a jpeg + /// The list of file extensions that equate to a jpeg. /// public static readonly IEnumerable FileExtensions = new[] { "jpg", "jpeg", "jfif" }; diff --git a/src/ImageSharp/Formats/Jpeg/JpegImageFormatProvider.cs b/src/ImageSharp/Formats/Jpeg/JpegImageFormatProvider.cs index ca2d019fe5..5eefa5db16 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegImageFormatProvider.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegImageFormatProvider.cs @@ -12,7 +12,7 @@ namespace ImageSharp.Formats using ImageSharp.PixelFormats; /// - /// Detects png file headers + /// Registers the image encoders, decoders and mime type detectors for the jpeg format. /// public class JpegImageFormatProvider : IImageFormatProvider { diff --git a/src/ImageSharp/Formats/Png/PngConstants.cs b/src/ImageSharp/Formats/Png/PngConstants.cs index 8528e93ee3..8c10ad9601 100644 --- a/src/ImageSharp/Formats/Png/PngConstants.cs +++ b/src/ImageSharp/Formats/Png/PngConstants.cs @@ -13,17 +13,17 @@ namespace ImageSharp.Formats internal static class PngConstants { /// - /// The default encoding for text metadata + /// The default encoding for text metadata. /// public static readonly Encoding DefaultEncoding = Encoding.GetEncoding("ASCII"); /// - /// The list of mimetypes that equate to a jpeg + /// The list of mimetypes that equate to a png. /// public static readonly IEnumerable MimeTypes = new[] { "image/png" }; /// - /// The list of file extensions that equate to a jpeg + /// The list of file extensions that equate to a png. /// public static readonly IEnumerable FileExtensions = new[] { "png" }; } diff --git a/src/ImageSharp/Formats/Png/PngEncoder.cs b/src/ImageSharp/Formats/Png/PngEncoder.cs index 954150e8e8..8d5b09d0c3 100644 --- a/src/ImageSharp/Formats/Png/PngEncoder.cs +++ b/src/ImageSharp/Formats/Png/PngEncoder.cs @@ -70,19 +70,19 @@ namespace ImageSharp.Formats public void Encode(Image image, Stream stream) where TPixel : struct, IPixel { - using (var encode = new PngEncoderCore()) + using (var encoder = new PngEncoderCore()) { - encode.IgnoreMetadata = this.IgnoreMetadata; + encoder.IgnoreMetadata = this.IgnoreMetadata; - encode.PaletteSize = this.PaletteSize > 0 ? this.PaletteSize.Clamp(1, int.MaxValue) : int.MaxValue; - encode.PngColorType = this.PngColorType; - encode.CompressionLevel = this.CompressionLevel; - encode.Gamma = this.Gamma; - encode.Quantizer = this.Quantizer; - encode.Threshold = this.Threshold; - encode.WriteGamma = this.WriteGamma; + encoder.PaletteSize = this.PaletteSize > 0 ? this.PaletteSize.Clamp(1, int.MaxValue) : int.MaxValue; + encoder.PngColorType = this.PngColorType; + encoder.CompressionLevel = this.CompressionLevel; + encoder.Gamma = this.Gamma; + encoder.Quantizer = this.Quantizer; + encoder.Threshold = this.Threshold; + encoder.WriteGamma = this.WriteGamma; - encode.Encode(image, stream); + encoder.Encode(image, stream); } } } diff --git a/src/ImageSharp/Formats/Png/PngImageFormatProvider.cs b/src/ImageSharp/Formats/Png/PngImageFormatProvider.cs index fc5ac44501..abbbaa6d57 100644 --- a/src/ImageSharp/Formats/Png/PngImageFormatProvider.cs +++ b/src/ImageSharp/Formats/Png/PngImageFormatProvider.cs @@ -12,7 +12,7 @@ namespace ImageSharp.Formats using ImageSharp.PixelFormats; /// - /// Detects png file headers + /// Registers the image encoders, decoders and mime type detectors for the png format. /// public class PngImageFormatProvider : IImageFormatProvider { diff --git a/src/ImageSharp/Image/Image.Decode.cs b/src/ImageSharp/Image/Image.Decode.cs index 00725d72ba..436cde466d 100644 --- a/src/ImageSharp/Image/Image.Decode.cs +++ b/src/ImageSharp/Image/Image.Decode.cs @@ -22,7 +22,7 @@ namespace ImageSharp /// /// The image stream to read the header from. /// The configuration. - /// The mimetype or null if none found. + /// The mime type or null if none found. private static string InternalDiscoverMimeType(Stream stream, Configuration config) { // This is probably a candidate for making into a public API in the future! diff --git a/src/ImageSharp/Image/Image.FromBytes.cs b/src/ImageSharp/Image/Image.FromBytes.cs index 5fb460a487..3b95211023 100644 --- a/src/ImageSharp/Image/Image.FromBytes.cs +++ b/src/ImageSharp/Image/Image.FromBytes.cs @@ -16,21 +16,21 @@ namespace ImageSharp public static partial class Image { /// - /// By reading the header on the provided byte array this calculates the images mimetype. + /// By reading the header on the provided byte array this calculates the images mime type. /// /// The byte array containing image data to read the header from. - /// The mimetype or null if none found. + /// The mime type or null if none found. public static string DiscoverMimeType(byte[] data) { return DiscoverMimeType(null, data); } /// - /// By reading the header on the provided byte array this calculates the images mimetype. + /// By reading the header on the provided byte array this calculates the images mime type. /// /// The configuration. /// The byte array containing image data to read the header from. - /// The mimetype or null if none found. + /// The mime type or null if none found. public static string DiscoverMimeType(Configuration config, byte[] data) { using (Stream stream = new MemoryStream(data)) @@ -50,12 +50,12 @@ namespace ImageSharp /// Create a new instance of the class from the given byte array. /// /// The byte array containing image data. - /// the mime type of the decoded image. + /// The mime type of the decoded image. /// A new . public static Image Load(byte[] data, out string mimeType) => Load(null, data, out mimeType); /// - /// Create a new instance of the class from the given byte array. + /// Create a new instance of the class from the given byte array. /// /// The config for the decoder. /// The byte array containing image data. @@ -63,11 +63,11 @@ namespace ImageSharp public static Image Load(Configuration config, byte[] data) => Load(config, data); /// - /// Create a new instance of the class from the given byte array. + /// Create a new instance of the class from the given byte array. /// /// The config for the decoder. /// The byte array containing image data. - /// the mime type of the decoded image. + /// The mime type of the decoded image. /// A new . public static Image Load(Configuration config, byte[] data, out string mimeType) => Load(config, data, out mimeType); @@ -104,7 +104,7 @@ namespace ImageSharp /// Create a new instance of the class from the given byte array. /// /// The byte array containing image data. - /// the mime type of the decoded image. + /// The mime type of the decoded image. /// The pixel format. /// A new . public static Image Load(byte[] data, out string mimeType) @@ -123,9 +123,9 @@ namespace ImageSharp public static Image Load(Configuration config, byte[] data) where TPixel : struct, IPixel { - using (MemoryStream ms = new MemoryStream(data)) + using (var memoryStream = new MemoryStream(data)) { - return Load(config, ms); + return Load(config, memoryStream); } } @@ -134,15 +134,15 @@ namespace ImageSharp /// /// The configuration options. /// The byte array containing image data. - /// the mime type of the decoded image. + /// The mime type of the decoded image. /// The pixel format. /// A new . public static Image Load(Configuration config, byte[] data, out string mimeType) where TPixel : struct, IPixel { - using (MemoryStream ms = new MemoryStream(data)) + using (var memoryStream = new MemoryStream(data)) { - return Load(config, ms, out mimeType); + return Load(config, memoryStream, out mimeType); } } @@ -156,9 +156,9 @@ namespace ImageSharp public static Image Load(byte[] data, IImageDecoder decoder) where TPixel : struct, IPixel { - using (var ms = new MemoryStream(data)) + using (var memoryStream = new MemoryStream(data)) { - return Load(ms, decoder); + return Load(memoryStream, decoder); } } @@ -173,9 +173,9 @@ namespace ImageSharp public static Image Load(Configuration config, byte[] data, IImageDecoder decoder) where TPixel : struct, IPixel { - using (var ms = new MemoryStream(data)) + using (var memoryStream = new MemoryStream(data)) { - return Load(config, ms, decoder); + return Load(config, memoryStream, decoder); } } } diff --git a/src/ImageSharp/Image/Image.FromFile.cs b/src/ImageSharp/Image/Image.FromFile.cs index 96d509752a..c44a5da5e6 100644 --- a/src/ImageSharp/Image/Image.FromFile.cs +++ b/src/ImageSharp/Image/Image.FromFile.cs @@ -17,21 +17,21 @@ namespace ImageSharp public static partial class Image { /// - /// By reading the header on the provided file this calculates the images mimetype. + /// By reading the header on the provided file this calculates the images mime type. /// /// The image file to open and to read the header from. - /// The mimetype or null if none found. + /// The mime type or null if none found. public static string DiscoverMimeType(string filePath) { return DiscoverMimeType(null, filePath); } /// - /// By reading the header on the provided file this calculates the images mimetype. + /// By reading the header on the provided file this calculates the images mime type. /// /// The configuration. /// The image file to open and to read the header from. - /// The mimetype or null if none found. + /// The mime type or null if none found. public static string DiscoverMimeType(Configuration config, string filePath) { config = config ?? Configuration.Default; @@ -55,7 +55,7 @@ namespace ImageSharp /// Create a new instance of the class from the given file. /// /// The file path to the image. - /// the mime type of the decoded image. + /// The mime type of the decoded image. /// /// Thrown if the stream is not readable nor seekable. /// @@ -78,7 +78,7 @@ namespace ImageSharp /// /// The config for the decoder. /// The file path to the image. - /// the mime type of the decoded image. + /// The mime type of the decoded image. /// /// Thrown if the stream is not readable nor seekable. /// @@ -127,7 +127,7 @@ namespace ImageSharp /// Create a new instance of the class from the given file. /// /// The file path to the image. - /// the mime type of the decoded image. + /// The mime type of the decoded image. /// /// Thrown if the stream is not readable nor seekable. /// @@ -153,9 +153,9 @@ namespace ImageSharp where TPixel : struct, IPixel { config = config ?? Configuration.Default; - using (Stream s = config.FileSystem.OpenRead(path)) + using (Stream stream = config.FileSystem.OpenRead(path)) { - return Load(config, s); + return Load(config, stream); } } @@ -164,7 +164,7 @@ namespace ImageSharp /// /// The configuration options. /// The file path to the image. - /// the mime type of the decoded image. + /// The mime type of the decoded image. /// /// Thrown if the stream is not readable nor seekable. /// @@ -174,9 +174,9 @@ namespace ImageSharp where TPixel : struct, IPixel { config = config ?? Configuration.Default; - using (Stream s = config.FileSystem.OpenRead(path)) + using (Stream stream = config.FileSystem.OpenRead(path)) { - return Load(config, s, out mimeType); + return Load(config, stream, out mimeType); } } @@ -211,9 +211,9 @@ namespace ImageSharp where TPixel : struct, IPixel { config = config ?? Configuration.Default; - using (Stream s = config.FileSystem.OpenRead(path)) + using (Stream stream = config.FileSystem.OpenRead(path)) { - return Load(config, s, decoder); + return Load(config, stream, decoder); } } } diff --git a/src/ImageSharp/Image/Image.FromStream.cs b/src/ImageSharp/Image/Image.FromStream.cs index 5bd87b145a..19f9304931 100644 --- a/src/ImageSharp/Image/Image.FromStream.cs +++ b/src/ImageSharp/Image/Image.FromStream.cs @@ -20,21 +20,21 @@ namespace ImageSharp public static partial class Image { /// - /// By reading the header on the provided stream this calculates the images mimetype. + /// By reading the header on the provided stream this calculates the images mime type. /// /// The image stream to read the header from. - /// The mimetype or null if none found. + /// The mime type or null if none found. public static string DiscoverMimeType(Stream stream) { return DiscoverMimeType(null, stream); } /// - /// By reading the header on the provided stream this calculates the images mimetype. + /// By reading the header on the provided stream this calculates the images mime type. /// /// The configuration. /// The image stream to read the header from. - /// The mimetype or null if none found. + /// The mime type or null if none found. public static string DiscoverMimeType(Configuration config, Stream stream) { return WithSeekableStream(stream, s => InternalDiscoverMimeType(s, config ?? Configuration.Default)); @@ -224,12 +224,12 @@ namespace ImageSharp } // We want to be able to load images from things like HttpContext.Request.Body - using (var ms = new MemoryStream()) + using (var memoryStream = new MemoryStream()) { - stream.CopyTo(ms); - ms.Position = 0; + stream.CopyTo(memoryStream); + memoryStream.Position = 0; - return action(ms); + return action(memoryStream); } } } diff --git a/src/ImageSharp/Image/Image{TPixel}.cs b/src/ImageSharp/Image/Image{TPixel}.cs index d8aff50419..5165bc857c 100644 --- a/src/ImageSharp/Image/Image{TPixel}.cs +++ b/src/ImageSharp/Image/Image{TPixel}.cs @@ -214,7 +214,7 @@ namespace ImageSharp string mime = this.Configuration.FindFileExtensionsMimeType(ext); if (mime == null) { - stringBuilder.AppendLine($"Can't find a mime type for the file extention '{ext}'. Registerd File extension maps include:"); + stringBuilder.AppendLine($"Can't find a mime type for the file extention '{ext}'. Registered file extension maps include:"); foreach (KeyValuePair map in this.Configuration.ImageExtensionToMimeTypeMapping) { stringBuilder.AppendLine($" - {map.Key} : {map.Value}"); @@ -222,7 +222,7 @@ namespace ImageSharp } else { - stringBuilder.AppendLine($"Can't find encoder for file extention '{ext}' using mime type '{mime}'. Registerd encoders include:"); + stringBuilder.AppendLine($"Can't find encoder for file extention '{ext}' using mime type '{mime}'. Registered encoders include:"); foreach (KeyValuePair enc in this.Configuration.ImageEncoders) { stringBuilder.AppendLine($" - {enc.Key} : {enc.Value.GetType().Name}"); From babd2bc642b4f2bc84938b3dab1442163e037ca9 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Sun, 25 Jun 2017 13:04:25 +0100 Subject: [PATCH 08/36] add internal IXXOptions to provider cleaner passing of params to core encoders/decoders --- .../ChangeDefaultEncoderOptions/Program.cs | 17 +---- src/ImageSharp/Formats/Bmp/BmpDecoder.cs | 4 +- src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs | 3 +- src/ImageSharp/Formats/Bmp/BmpEncoder.cs | 5 +- src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs | 20 +++--- .../Formats/Bmp/IBmpDecoderOptions.cs | 21 ++++++ .../Formats/Bmp/IBmpEncoderOptions.cs | 25 +++++++ src/ImageSharp/Formats/Gif/GifDecoder.cs | 11 +-- src/ImageSharp/Formats/Gif/GifDecoderCore.cs | 19 ++---- src/ImageSharp/Formats/Gif/GifEncoder.cs | 14 +--- src/ImageSharp/Formats/Gif/GifEncoderCore.cs | 44 ++++++------ .../Formats/Gif/IGifDecoderOptions.cs | 29 ++++++++ .../Formats/Gif/IGifEncoderOptions.cs | 45 ++++++++++++ .../Formats/Jpeg/IJpegDecoderOptions.cs | 24 +++++++ .../Formats/Jpeg/IJpegEncoderOptions.cs | 37 ++++++++++ src/ImageSharp/Formats/Jpeg/JpegDecoder.cs | 5 +- .../Formats/Jpeg/JpegDecoderCore.cs | 8 ++- src/ImageSharp/Formats/Jpeg/JpegEncoder.cs | 16 +---- .../Formats/Jpeg/JpegEncoderCore.cs | 41 +++++++---- .../Formats/Png/IPngDecoderOptions.cs | 29 ++++++++ .../Formats/Png/IPngEncoderOptions.cs | 64 +++++++++++++++++ src/ImageSharp/Formats/Png/PngDecoder.cs | 5 +- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 31 +++++---- src/ImageSharp/Formats/Png/PngEncoder.cs | 14 +--- src/ImageSharp/Formats/Png/PngEncoderCore.cs | 68 +++++++++---------- .../Formats/Gif/GifDecoderTests.cs | 13 ---- .../Formats/Jpg/JpegDecoderTests.cs | 2 +- 27 files changed, 410 insertions(+), 204 deletions(-) create mode 100644 src/ImageSharp/Formats/Bmp/IBmpDecoderOptions.cs create mode 100644 src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs create mode 100644 src/ImageSharp/Formats/Gif/IGifDecoderOptions.cs create mode 100644 src/ImageSharp/Formats/Gif/IGifEncoderOptions.cs create mode 100644 src/ImageSharp/Formats/Jpeg/IJpegDecoderOptions.cs create mode 100644 src/ImageSharp/Formats/Jpeg/IJpegEncoderOptions.cs create mode 100644 src/ImageSharp/Formats/Png/IPngDecoderOptions.cs create mode 100644 src/ImageSharp/Formats/Png/IPngEncoderOptions.cs diff --git a/samples/ChangeDefaultEncoderOptions/Program.cs b/samples/ChangeDefaultEncoderOptions/Program.cs index 5ba6f52b56..b085e468a1 100644 --- a/samples/ChangeDefaultEncoderOptions/Program.cs +++ b/samples/ChangeDefaultEncoderOptions/Program.cs @@ -1,5 +1,6 @@ using System; using ImageSharp; +using ImageSharp.Formats; namespace ChangeDefaultEncoderOptions { @@ -14,22 +15,6 @@ namespace ChangeDefaultEncoderOptions Quality = 90, IgnoreMetadata = true }); - - // now lets say we don't want animated gifs, lets skip decoding the alternative frames - Configuration.Default.SetMimeTypeDecoder("image/gif", new ImageSharp.Formats.GifDecoder() - { - IgnoreFrames = true, - IgnoreMetadata = true - }); - - // and just to be douple sure we don't want animations lets disable them on encode too. - Configuration.Default.SetMimeTypeEncoder("image/gif", new ImageSharp.Formats.GifEncoder() - { - IgnoreFrames = true, - IgnoreMetadata = true - }); - - } } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoder.cs b/src/ImageSharp/Formats/Bmp/BmpDecoder.cs index e1dc489f40..66af2fa75a 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoder.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoder.cs @@ -26,7 +26,7 @@ namespace ImageSharp.Formats /// Formats will be supported in a later releases. We advise always /// to use only 24 Bit Windows bitmaps. /// - public class BmpDecoder : IImageDecoder + public class BmpDecoder : IImageDecoder, IBmpDecoderOptions { /// public Image Decode(Configuration configuration, Stream stream) @@ -35,7 +35,7 @@ namespace ImageSharp.Formats { Guard.NotNull(stream, "stream"); - return new BmpDecoderCore(configuration).Decode(stream); + return new BmpDecoderCore(configuration, this).Decode(stream); } } } diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs index 997a77d6c5..817d00f7e7 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs @@ -52,7 +52,8 @@ namespace ImageSharp.Formats /// Initializes a new instance of the class. /// /// The configuration. - public BmpDecoderCore(Configuration configuration) + /// The options + public BmpDecoderCore(Configuration configuration, IBmpDecoderOptions options) { this.configuration = configuration; } diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoder.cs b/src/ImageSharp/Formats/Bmp/BmpEncoder.cs index f47bedb818..b0064a5086 100644 --- a/src/ImageSharp/Formats/Bmp/BmpEncoder.cs +++ b/src/ImageSharp/Formats/Bmp/BmpEncoder.cs @@ -15,7 +15,7 @@ namespace ImageSharp.Formats /// Image encoder for writing an image to a stream as a Windows bitmap. /// /// The encoder can currently only write 24-bit rgb images to streams. - public class BmpEncoder : IImageEncoder + public class BmpEncoder : IImageEncoder, IBmpEncoderOptions { /// /// Gets or sets the number of bits per pixel. @@ -26,8 +26,7 @@ namespace ImageSharp.Formats public void Encode(Image image, Stream stream) where TPixel : struct, IPixel { - BmpEncoderCore encoder = new BmpEncoderCore(); - encoder.BitsPerPixel = this.BitsPerPixel; + var encoder = new BmpEncoderCore(this); encoder.Encode(image, stream); } } diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs index daff65cbc7..e41c295012 100644 --- a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs @@ -23,16 +23,18 @@ namespace ImageSharp.Formats private int padding; /// - /// Initializes a new instance of the class. + /// Gets or sets the number of bits per pixel. /// - public BmpEncoderCore() - { - } + private BmpBitsPerPixel bitsPerPixel; /// - /// Gets or sets the number of bits per pixel. + /// Initializes a new instance of the class. /// - public BmpBitsPerPixel BitsPerPixel { get; internal set; } = BmpBitsPerPixel.Pixel24; + /// The encoder options + public BmpEncoderCore(IBmpEncoderOptions options) + { + this.bitsPerPixel = options.BitsPerPixel; + } /// /// Encodes the image to the specified stream from the . @@ -47,9 +49,9 @@ namespace ImageSharp.Formats Guard.NotNull(stream, nameof(stream)); // Cast to int will get the bytes per pixel - short bpp = (short)(8 * (int)this.BitsPerPixel); + short bpp = (short)(8 * (int)this.bitsPerPixel); int bytesPerLine = 4 * (((image.Width * bpp) + 31) / 32); - this.padding = bytesPerLine - (image.Width * (int)this.BitsPerPixel); + this.padding = bytesPerLine - (image.Width * (int)this.bitsPerPixel); // Do not use IDisposable pattern here as we want to preserve the stream. EndianBinaryWriter writer = new EndianBinaryWriter(Endianness.LittleEndian, stream); @@ -134,7 +136,7 @@ namespace ImageSharp.Formats { using (PixelAccessor pixels = image.Lock()) { - switch (this.BitsPerPixel) + switch (this.bitsPerPixel) { case BmpBitsPerPixel.Pixel32: this.Write32Bit(writer, pixels); diff --git a/src/ImageSharp/Formats/Bmp/IBmpDecoderOptions.cs b/src/ImageSharp/Formats/Bmp/IBmpDecoderOptions.cs new file mode 100644 index 0000000000..9285b9cf7a --- /dev/null +++ b/src/ImageSharp/Formats/Bmp/IBmpDecoderOptions.cs @@ -0,0 +1,21 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using System; + using System.Collections.Generic; + using System.IO; + + using ImageSharp.PixelFormats; + + /// + /// Image decoder options for decoding Windows bitmap streams. + /// + internal interface IBmpDecoderOptions + { + // added this for consistancy so we can add stuff as required, no options currently availible + } +} diff --git a/src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs b/src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs new file mode 100644 index 0000000000..dd17043fad --- /dev/null +++ b/src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs @@ -0,0 +1,25 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using System; + using System.Collections.Generic; + using System.IO; + + using ImageSharp.PixelFormats; + + /// + /// Configuration options for use during bmp encoding + /// + /// The encoder can currently only write 24-bit rgb images to streams. + internal interface IBmpEncoderOptions + { + /// + /// Gets the number of bits per pixel. + /// + BmpBitsPerPixel BitsPerPixel { get; } + } +} diff --git a/src/ImageSharp/Formats/Gif/GifDecoder.cs b/src/ImageSharp/Formats/Gif/GifDecoder.cs index caf39c2e1f..e026cc29bc 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoder.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoder.cs @@ -14,18 +14,13 @@ namespace ImageSharp.Formats /// /// Decoder for generating an image out of a gif encoded stream. /// - public class GifDecoder : IImageDecoder + public class GifDecoder : IImageDecoder, IGifDecoderOptions { /// /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded. /// public bool IgnoreMetadata { get; set; } = false; - /// - /// Gets or sets a value indicating whether the additional frames should be ignored when the image is being decoded. - /// - public bool IgnoreFrames { get; set; } = false; - /// /// Gets or sets the encoding that should be used when reading comments. /// @@ -35,9 +30,7 @@ namespace ImageSharp.Formats public Image Decode(Configuration configuration, Stream stream) where TPixel : struct, IPixel { - var decoder = new GifDecoderCore(this.TextEncoding, configuration); - decoder.IgnoreMetadata = this.IgnoreMetadata; - decoder.IgnoreFrames = this.IgnoreFrames; + var decoder = new GifDecoderCore(configuration, this); return decoder.Decode(stream); } } diff --git a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs index ad34009b65..bbecf32dd6 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs @@ -78,11 +78,12 @@ namespace ImageSharp.Formats /// /// Initializes a new instance of the class. /// - /// The decoder encoding. /// The configuration. - public GifDecoderCore(Encoding encoding, Configuration configuration) + /// The decoder options. + public GifDecoderCore(Configuration configuration, IGifDecoderOptions options) { - this.TextEncoding = encoding ?? GifConstants.DefaultEncoding; + this.TextEncoding = options.TextEncoding ?? GifConstants.DefaultEncoding; + this.IgnoreMetadata = options.IgnoreMetadata; this.configuration = configuration ?? Configuration.Default; } @@ -96,11 +97,6 @@ namespace ImageSharp.Formats /// public Encoding TextEncoding { get; private set; } - /// - /// Gets or sets a value indicating whether the additional frames should be ignored when the image is being decoded. - /// - public bool IgnoreFrames { get; internal set; } - /// /// Decodes the stream to the image. /// @@ -362,13 +358,6 @@ namespace ImageSharp.Formats /// The private unsafe void ReadFrameColors(byte[] indices, byte[] colorTable, int colorTableLength, GifImageDescriptor descriptor) { - if (this.IgnoreFrames && this.image != null) - { - // we already have our images skip this - // TODO move this higher up the stack to prevent some of the data loading higher up. - return; - } - int imageWidth = this.logicalScreenDescriptor.Width; int imageHeight = this.logicalScreenDescriptor.Height; diff --git a/src/ImageSharp/Formats/Gif/GifEncoder.cs b/src/ImageSharp/Formats/Gif/GifEncoder.cs index ee78c32668..b725f12603 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoder.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoder.cs @@ -15,18 +15,13 @@ namespace ImageSharp.Formats /// /// Image encoder for writing image data to a stream in gif format. /// - public class GifEncoder : IImageEncoder + public class GifEncoder : IImageEncoder, IGifEncoderOptions { /// /// Gets or sets a value indicating whether the metadata should be ignored when the image is being encoded. /// public bool IgnoreMetadata { get; set; } = false; - /// - /// Gets or sets a value indicating whether the additional frames should be ignored when the image is being encoded. - /// - public bool IgnoreFrames { get; set; } = false; - /// /// Gets or sets the encoding that should be used when writing comments. /// @@ -51,12 +46,7 @@ namespace ImageSharp.Formats public void Encode(Image image, Stream stream) where TPixel : struct, IPixel { - GifEncoderCore encoder = new GifEncoderCore(this.TextEncoding); - encoder.Quantizer = this.Quantizer; - encoder.Threshold = this.Threshold; - encoder.PaletteSize = this.PaletteSize; - encoder.IgnoreMetadata = this.IgnoreMetadata; - encoder.IgnoreFrames = this.IgnoreFrames; + GifEncoderCore encoder = new GifEncoderCore(this); encoder.Encode(image, stream); } } diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs index 3191aafc97..81b3cfba4a 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs @@ -35,44 +35,44 @@ namespace ImageSharp.Formats /// private bool hasFrames; - /// - /// Initializes a new instance of the class. - /// - /// The encoding for the encoder. - public GifEncoderCore(Encoding encoding) - { - this.TextEncoding = encoding ?? GifConstants.DefaultEncoding; - } - /// /// Gets the TextEncoding /// - public Encoding TextEncoding { get; private set; } + private Encoding textEncoding; /// /// Gets or sets the quantizer for reducing the color count. /// - public IQuantizer Quantizer { get; set; } + private IQuantizer quantizer; /// /// Gets or sets the threshold. /// - public byte Threshold { get; internal set; } + private byte threshold; /// /// Gets or sets the size of the color palette to use. /// - public int PaletteSize { get; internal set; } + private int paletteSize; /// /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded. /// - public bool IgnoreMetadata { get; internal set; } + private bool ignoreMetadata; /// - /// Gets or sets a value indicating whether the additional frames should be ignored when the image is being encoded. + /// Initializes a new instance of the class. /// - public bool IgnoreFrames { get; internal set; } + /// The options for the encoder. + public GifEncoderCore(IGifEncoderOptions options) + { + this.textEncoding = options.TextEncoding ?? GifConstants.DefaultEncoding; + + this.quantizer = options.Quantizer; + this.threshold = options.Threshold; + this.paletteSize = options.PaletteSize; + this.ignoreMetadata = options.IgnoreMetadata; + } /// /// Encodes the image to the specified stream from the . @@ -86,22 +86,22 @@ namespace ImageSharp.Formats Guard.NotNull(image, nameof(image)); Guard.NotNull(stream, nameof(stream)); - this.Quantizer = this.Quantizer ?? new OctreeQuantizer(); + this.quantizer = this.quantizer ?? new OctreeQuantizer(); // Do not use IDisposable pattern here as we want to preserve the stream. var writer = new EndianBinaryWriter(Endianness.LittleEndian, stream); // Ensure that pallete size can be set but has a fallback. - int paletteSize = this.PaletteSize; + int paletteSize = this.paletteSize; paletteSize = paletteSize > 0 ? paletteSize.Clamp(1, 256) : 256; // Get the number of bits. this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(paletteSize); - this.hasFrames = !this.IgnoreFrames && image.Frames.Any(); + this.hasFrames = image.Frames.Any(); // Dithering when animating gifs is a bad idea as we introduce pixel tearing across frames. - var ditheredQuantizer = (IQuantizer)this.Quantizer; + var ditheredQuantizer = (IQuantizer)this.quantizer; ditheredQuantizer.Dither = !this.hasFrames; // Quantize the image returning a palette. @@ -260,7 +260,7 @@ namespace ImageSharp.Formats private void WriteComments(Image image, EndianBinaryWriter writer) where TPixel : struct, IPixel { - if (this.IgnoreMetadata) + if (this.ignoreMetadata) { return; } @@ -271,7 +271,7 @@ namespace ImageSharp.Formats return; } - byte[] comments = this.TextEncoding.GetBytes(property.Value); + byte[] comments = this.textEncoding.GetBytes(property.Value); int count = Math.Min(comments.Length, 255); diff --git a/src/ImageSharp/Formats/Gif/IGifDecoderOptions.cs b/src/ImageSharp/Formats/Gif/IGifDecoderOptions.cs new file mode 100644 index 0000000000..caaa8932bb --- /dev/null +++ b/src/ImageSharp/Formats/Gif/IGifDecoderOptions.cs @@ -0,0 +1,29 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Text; + using ImageSharp.PixelFormats; + + /// + /// Decoder for generating an image out of a gif encoded stream. + /// + internal interface IGifDecoderOptions + { + /// + /// Gets a value indicating whether the metadata should be ignored when the image is being decoded. + /// + bool IgnoreMetadata { get; } + + /// + /// Gets the encoding that should be used when reading comments. + /// + Encoding TextEncoding { get; } + } +} diff --git a/src/ImageSharp/Formats/Gif/IGifEncoderOptions.cs b/src/ImageSharp/Formats/Gif/IGifEncoderOptions.cs new file mode 100644 index 0000000000..c38ec7e451 --- /dev/null +++ b/src/ImageSharp/Formats/Gif/IGifEncoderOptions.cs @@ -0,0 +1,45 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Text; + using ImageSharp.PixelFormats; + using ImageSharp.Quantizers; + + /// + /// The configuration options used for encoding gifs + /// + internal interface IGifEncoderOptions + { + /// + /// Gets a value indicating whether the metadata should be ignored when the image is being encoded. + /// + bool IgnoreMetadata { get; } + + /// + /// Gets the encoding that should be used when writing comments. + /// + Encoding TextEncoding { get; } + + /// + /// Gets the size of the color palette to use. For gifs the value ranges from 1 to 256. Leave as zero for default size. + /// + int PaletteSize { get; } + + /// + /// Gets the transparency threshold. + /// + byte Threshold { get; } + + /// + /// Gets the quantizer for reducing the color count. + /// + IQuantizer Quantizer { get; } + } +} diff --git a/src/ImageSharp/Formats/Jpeg/IJpegDecoderOptions.cs b/src/ImageSharp/Formats/Jpeg/IJpegDecoderOptions.cs new file mode 100644 index 0000000000..6830e2e4a5 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/IJpegDecoderOptions.cs @@ -0,0 +1,24 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using System; + using System.Collections.Generic; + using System.IO; + + using ImageSharp.PixelFormats; + + /// + /// Image decoder for generating an image out of a jpg stream. + /// + internal interface IJpegDecoderOptions + { + /// + /// Gets a value indicating whether the metadata should be ignored when the image is being decoded. + /// + bool IgnoreMetadata { get; } + } +} diff --git a/src/ImageSharp/Formats/Jpeg/IJpegEncoderOptions.cs b/src/ImageSharp/Formats/Jpeg/IJpegEncoderOptions.cs new file mode 100644 index 0000000000..947c98ee2a --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/IJpegEncoderOptions.cs @@ -0,0 +1,37 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using System; + using System.Collections.Generic; + using System.IO; + + using ImageSharp.PixelFormats; + + /// + /// Encoder for writing the data image to a stream in jpeg format. + /// + internal interface IJpegEncoderOptions + { + /// + /// Gets a value indicating whether the metadata should be ignored when the image is being decoded. + /// + bool IgnoreMetadata { get; } + + /// + /// Gets the quality, that will be used to encode the image. Quality + /// index must be between 0 and 100 (compression from max to min). + /// + /// The quality of the jpg image from 0 to 100. + int Quality { get; } + + /// + /// Gets the subsample ration, that will be used to encode the image. + /// + /// The subsample ratio of the jpg image. + JpegSubsample? Subsample { get; } + } +} diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs index b809908e9b..0dc186697e 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs @@ -14,7 +14,7 @@ namespace ImageSharp.Formats /// /// Image decoder for generating an image out of a jpg stream. /// - public class JpegDecoder : IImageDecoder + public class JpegDecoder : IImageDecoder, IJpegDecoderOptions { /// /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded. @@ -27,9 +27,8 @@ namespace ImageSharp.Formats { Guard.NotNull(stream, "stream"); - using (JpegDecoderCore decoder = new JpegDecoderCore(configuration)) + using (JpegDecoderCore decoder = new JpegDecoderCore(configuration, this)) { - decoder.IgnoreMetadata = this.IgnoreMetadata; return decoder.Decode(stream); } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index f6456620ef..1ce78a8b1f 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -99,8 +99,10 @@ namespace ImageSharp.Formats /// Initializes a new instance of the class. /// /// The configuration. - public JpegDecoderCore(Configuration configuration) + /// The options. + public JpegDecoderCore(Configuration configuration, IJpegDecoderOptions options) { + this.IgnoreMetadata = options.IgnoreMetadata; this.configuration = configuration ?? Configuration.Default; this.HuffmanTrees = HuffmanTree.CreateHuffmanTrees(); this.QuantizationTables = new Block8x8F[MaxTq + 1]; @@ -184,9 +186,9 @@ namespace ImageSharp.Formats public int TotalMCUCount => this.MCUCountX * this.MCUCountY; /// - /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded. + /// Gets a value indicating whether the metadata should be ignored when the image is being decoded. /// - public bool IgnoreMetadata { get; internal set; } + public bool IgnoreMetadata { get; private set; } /// /// Decodes the image from the specified and sets diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs index d0be9eaf8f..350856471c 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs @@ -14,7 +14,7 @@ namespace ImageSharp.Formats /// /// Encoder for writing the data image to a stream in jpeg format. /// - public class JpegEncoder : IImageEncoder + public class JpegEncoder : IImageEncoder, IJpegEncoderOptions { /// /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded. @@ -43,19 +43,7 @@ namespace ImageSharp.Formats public void Encode(Image image, Stream stream) where TPixel : struct, IPixel { - JpegEncoderCore encoder = new JpegEncoderCore(); - - var quality = this.Quality; - if (quality == 0) - { - quality = 75; - } - - encoder.Quality = quality; - encoder.Subsample = this.Subsample ?? (quality >= 91 ? JpegSubsample.Ratio444 : JpegSubsample.Ratio420); - - encoder.IgnoreMetadata = this.IgnoreMetadata; - + var encoder = new JpegEncoderCore(this); encoder.Encode(image, stream); } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs index 168e473111..2d67f67353 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs @@ -149,29 +149,40 @@ namespace ImageSharp.Formats /// private Stream outputStream; - /// - /// Initializes a new instance of the class. - /// - public JpegEncoderCore() - { - } - /// /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded. /// - public bool IgnoreMetadata { get; internal set; } = false; + private bool ignoreMetadata = false; /// /// Gets or sets the quality, that will be used to encode the image. Quality /// index must be between 0 and 100 (compression from max to min). /// /// The quality of the jpg image from 0 to 100. - public int Quality { get; internal set; } + private int quality = 0; /// /// Gets or sets the subsampling method to use. /// - public JpegSubsample? Subsample { get; internal set; } + private JpegSubsample? subsample; + + /// + /// Initializes a new instance of the class. + /// + /// The options + public JpegEncoderCore(IJpegEncoderOptions options) + { + int quality = options.Quality; + if (quality == 0) + { + quality = 75; + } + + this.quality = quality; + this.subsample = options.Subsample ?? (quality >= 91 ? JpegSubsample.Ratio444 : JpegSubsample.Ratio420); + + this.ignoreMetadata = options.IgnoreMetadata; + } /// /// Encode writes the image to the jpeg baseline format with the given options. @@ -193,11 +204,11 @@ namespace ImageSharp.Formats this.outputStream = stream; - int quality = this.Quality.Clamp(1, 100); + int quality = this.quality.Clamp(1, 100); // Convert from a quality rating to a scaling factor. int scale; - if (this.Quality < 50) + if (this.quality < 50) { scale = 5000 / quality; } @@ -785,7 +796,7 @@ namespace ImageSharp.Formats private void WriteProfiles(Image image) where TPixel : struct, IPixel { - if (this.IgnoreMetadata) + if (this.ignoreMetadata) { return; } @@ -807,7 +818,7 @@ namespace ImageSharp.Formats byte[] subsamples = { 0x22, 0x11, 0x11 }; byte[] chroma = { 0x00, 0x01, 0x01 }; - switch (this.Subsample) + switch (this.subsample) { case JpegSubsample.Ratio444: subsamples = new byte[] { 0x11, 0x11, 0x11 }; @@ -863,7 +874,7 @@ namespace ImageSharp.Formats // TODO: We should allow grayscale writing. this.outputStream.Write(SosHeaderYCbCr, 0, SosHeaderYCbCr.Length); - switch (this.Subsample) + switch (this.subsample) { case JpegSubsample.Ratio444: this.Encode444(pixels); diff --git a/src/ImageSharp/Formats/Png/IPngDecoderOptions.cs b/src/ImageSharp/Formats/Png/IPngDecoderOptions.cs new file mode 100644 index 0000000000..de163ba7e5 --- /dev/null +++ b/src/ImageSharp/Formats/Png/IPngDecoderOptions.cs @@ -0,0 +1,29 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Text; + using ImageSharp.PixelFormats; + + /// + /// The optioas for decoding png images + /// + internal interface IPngDecoderOptions + { + /// + /// Gets a value indicating whether the metadata should be ignored when the image is being decoded. + /// + bool IgnoreMetadata { get; } + + /// + /// Gets the encoding that should be used when reading text chunks. + /// + Encoding TextEncoding { get; } + } +} diff --git a/src/ImageSharp/Formats/Png/IPngEncoderOptions.cs b/src/ImageSharp/Formats/Png/IPngEncoderOptions.cs new file mode 100644 index 0000000000..8f0a4cd829 --- /dev/null +++ b/src/ImageSharp/Formats/Png/IPngEncoderOptions.cs @@ -0,0 +1,64 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using System.Collections.Generic; + using System.IO; + + using ImageSharp.PixelFormats; + using ImageSharp.Quantizers; + + /// + /// The options availible for manipulating the encoder pipeline + /// + internal interface IPngEncoderOptions + { + /// + /// Gets a value indicating whether the metadata should be ignored when the image is being encoded. + /// + bool IgnoreMetadata { get; } + + /// + /// Gets the size of the color palette to use. Set to zero to leav png encoding to use pixel data. + /// + int PaletteSize { get; } + + /// + /// Gets the png color type + /// + PngColorType PngColorType { get; } + + /// + /// Gets the compression level 1-9. + /// Defaults to 6. + /// + int CompressionLevel { get; } + + /// + /// Gets the gamma value, that will be written + /// the the stream, when the property + /// is set to true. The default value is 2.2F. + /// + /// The gamma value of the image. + float Gamma { get; } + + /// + /// Gets quantizer for reducing the color count. + /// + IQuantizer Quantizer { get; } + + /// + /// Gets the transparency threshold. + /// + byte Threshold { get; } + + /// + /// Gets a value indicating whether this instance should write + /// gamma information to the stream. The default value is false. + /// + bool WriteGamma { get; } + } +} diff --git a/src/ImageSharp/Formats/Png/PngDecoder.cs b/src/ImageSharp/Formats/Png/PngDecoder.cs index c9fab8e3fb..2e177bc211 100644 --- a/src/ImageSharp/Formats/Png/PngDecoder.cs +++ b/src/ImageSharp/Formats/Png/PngDecoder.cs @@ -31,7 +31,7 @@ namespace ImageSharp.Formats /// /// /// - public class PngDecoder : IImageDecoder + public class PngDecoder : IImageDecoder, IPngDecoderOptions { /// /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded. @@ -53,8 +53,7 @@ namespace ImageSharp.Formats public Image Decode(Configuration configuration, Stream stream) where TPixel : struct, IPixel { - var decoder = new PngDecoderCore(configuration, this.TextEncoding); - decoder.IgnoreMetadata = this.IgnoreMetadata; + var decoder = new PngDecoderCore(configuration, this); return decoder.Decode(stream); } } diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index b1b98eca57..aef588f256 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -155,25 +155,26 @@ namespace ImageSharp.Formats private PngColorType pngColorType; /// - /// Initializes a new instance of the class. + /// Gets the encoding to use /// - /// The configuration. - /// The text encoding. - public PngDecoderCore(Configuration configuration, Encoding encoding) - { - this.configuration = configuration ?? Configuration.Default; - this.TextEncoding = encoding ?? PngConstants.DefaultEncoding; - } + private Encoding textEncoding; /// - /// Gets the encoding to use + /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded. /// - public Encoding TextEncoding { get; private set; } + private bool ignoreMetadata; /// - /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded. + /// Initializes a new instance of the class. /// - public bool IgnoreMetadata { get; internal set; } + /// The configuration. + /// The decoder options. + public PngDecoderCore(Configuration configuration, IPngDecoderOptions options) + { + this.configuration = configuration ?? Configuration.Default; + this.textEncoding = options.TextEncoding ?? PngConstants.DefaultEncoding; + this.ignoreMetadata = options.IgnoreMetadata; + } /// /// Decodes the stream to the image. @@ -900,7 +901,7 @@ namespace ImageSharp.Formats /// The maximum length to read. private void ReadTextChunk(ImageMetaData metadata, byte[] data, int length) { - if (this.IgnoreMetadata) + if (this.ignoreMetadata) { return; } @@ -916,8 +917,8 @@ namespace ImageSharp.Formats } } - string name = this.TextEncoding.GetString(data, 0, zeroIndex); - string value = this.TextEncoding.GetString(data, zeroIndex + 1, length - zeroIndex - 1); + string name = this.textEncoding.GetString(data, 0, zeroIndex); + string value = this.textEncoding.GetString(data, zeroIndex + 1, length - zeroIndex - 1); metadata.Properties.Add(new ImageProperty(name, value)); } diff --git a/src/ImageSharp/Formats/Png/PngEncoder.cs b/src/ImageSharp/Formats/Png/PngEncoder.cs index 8d5b09d0c3..f0d332fc49 100644 --- a/src/ImageSharp/Formats/Png/PngEncoder.cs +++ b/src/ImageSharp/Formats/Png/PngEncoder.cs @@ -14,7 +14,7 @@ namespace ImageSharp.Formats /// /// Image encoder for writing image data to a stream in png format. /// - public class PngEncoder : IImageEncoder + public class PngEncoder : IImageEncoder, IPngEncoderOptions { /// /// Gets or sets a value indicating whether the metadata should be ignored when the image is being encoded. @@ -70,18 +70,8 @@ namespace ImageSharp.Formats public void Encode(Image image, Stream stream) where TPixel : struct, IPixel { - using (var encoder = new PngEncoderCore()) + using (var encoder = new PngEncoderCore(this)) { - encoder.IgnoreMetadata = this.IgnoreMetadata; - - encoder.PaletteSize = this.PaletteSize > 0 ? this.PaletteSize.Clamp(1, int.MaxValue) : int.MaxValue; - encoder.PngColorType = this.PngColorType; - encoder.CompressionLevel = this.CompressionLevel; - encoder.Gamma = this.Gamma; - encoder.Quantizer = this.Quantizer; - encoder.Threshold = this.Threshold; - encoder.WriteGamma = this.WriteGamma; - encoder.Encode(image, stream); } } diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index c0cd0ffaf1..cfbd0c4493 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -118,52 +118,51 @@ namespace ImageSharp.Formats /// private IQuantizer quantizer; - /// - /// Initializes a new instance of the class. - /// - public PngEncoderCore() - { - } - /// /// Gets or sets a value indicating whether to ignore metadata /// - public bool IgnoreMetadata { get; internal set; } - - /// - /// Gets or sets the Quality value - /// - public int PaletteSize { get; internal set; } + private bool ignoreMetadata; /// /// Gets or sets the Quality value /// - public PngColorType PngColorType { get; internal set; } + private int paletteSize; /// /// Gets or sets the CompressionLevel value /// - public int CompressionLevel { get; internal set; } + private int compressionLevel; /// /// Gets or sets the Gamma value /// - public float Gamma { get; internal set; } + private float gamma; /// - /// Gets or sets the Quantizer value + /// Gets or sets the Threshold value /// - public IQuantizer Quantizer { get; internal set; } + private byte threshold; /// - /// Gets or sets the Threshold value + /// Gets or sets a value indicating whether to Write Gamma /// - public byte Threshold { get; internal set; } + private bool writeGamma; /// - /// Gets or sets a value indicating whether to Write Gamma + /// Initializes a new instance of the class. /// - public bool WriteGamma { get; internal set; } + /// The options for influancing the encoder + public PngEncoderCore(IPngEncoderOptions options) + { + this.ignoreMetadata = options.IgnoreMetadata; + this.paletteSize = options.PaletteSize > 0 ? options.PaletteSize.Clamp(1, int.MaxValue) : int.MaxValue; + this.pngColorType = options.PngColorType; + this.compressionLevel = options.CompressionLevel; + this.gamma = options.Gamma; + this.quantizer = options.Quantizer; + this.threshold = options.Threshold; + this.writeGamma = options.WriteGamma; + } /// /// Encodes the image to the specified stream from the . @@ -192,23 +191,20 @@ namespace ImageSharp.Formats stream.Write(this.chunkDataBuffer, 0, 8); - this.pngColorType = this.PngColorType; - this.quantizer = this.Quantizer; - // Set correct color type if the color count is 256 or less. - if (this.PaletteSize <= 256) + if (this.paletteSize <= 256) { this.pngColorType = PngColorType.Palette; } - if (this.pngColorType == PngColorType.Palette && this.PaletteSize > 256) + if (this.pngColorType == PngColorType.Palette && this.paletteSize > 256) { - this.PaletteSize = 256; + this.paletteSize = 256; } // Set correct bit depth. - this.bitDepth = this.PaletteSize <= 256 - ? (byte)ImageMaths.GetBitsNeededForColorDepth(this.PaletteSize).Clamp(1, 8) + this.bitDepth = this.paletteSize <= 256 + ? (byte)ImageMaths.GetBitsNeededForColorDepth(this.paletteSize).Clamp(1, 8) : (byte)8; // Png only supports in four pixel depths: 1, 2, 4, and 8 bits when using the PLTE chunk @@ -538,7 +534,7 @@ namespace ImageSharp.Formats private QuantizedImage WritePaletteChunk(Stream stream, PngHeader header, ImageBase image) where TPixel : struct, IPixel { - if (this.PaletteSize > 256) + if (this.paletteSize > 256) { return null; } @@ -549,7 +545,7 @@ namespace ImageSharp.Formats } // Quantize the image returning a palette. This boxing is icky. - QuantizedImage quantized = ((IQuantizer)this.quantizer).Quantize(image, this.PaletteSize); + QuantizedImage quantized = ((IQuantizer)this.quantizer).Quantize(image, this.paletteSize); // Grab the palette and write it to the stream. TPixel[] palette = quantized.Palette; @@ -576,7 +572,7 @@ namespace ImageSharp.Formats colorTable[offset + 1] = bytes[1]; colorTable[offset + 2] = bytes[2]; - if (alpha > this.Threshold) + if (alpha > this.threshold) { alpha = 255; } @@ -634,9 +630,9 @@ namespace ImageSharp.Formats /// The containing image data. private void WriteGammaChunk(Stream stream) { - if (this.WriteGamma) + if (this.writeGamma) { - int gammaValue = (int)(this.Gamma * 100000F); + int gammaValue = (int)(this.gamma * 100000F); byte[] size = BitConverter.GetBytes(gammaValue); @@ -679,7 +675,7 @@ namespace ImageSharp.Formats try { memoryStream = new MemoryStream(); - using (var deflateStream = new ZlibDeflateStream(memoryStream, this.CompressionLevel)) + using (var deflateStream = new ZlibDeflateStream(memoryStream, this.compressionLevel)) { for (int y = 0; y < this.height; y++) { diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs index cc7935fbfc..06bfd8990d 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs @@ -18,19 +18,6 @@ namespace ImageSharp.Tests public static readonly string[] TestFiles = { TestImages.Gif.Giphy, TestImages.Gif.Rings, TestImages.Gif.Trans }; - [Fact] - public void SkipDecodingFrames() - { - var file = TestFile.GetPath(TestImages.Gif.Giphy); - - using (Image image = Image.Load(file, new GifDecoder() { IgnoreFrames = true })) - using (Image imageWithFrames = Image.Load(file, new GifDecoder() { IgnoreFrames = false })) - { - Assert.NotEmpty(imageWithFrames.Frames); - Assert.Empty(image.Frames); - } - } - [Theory] [WithFileCollection(nameof(TestFiles), PixelTypes)] public void DecodeAndReSave(TestImageProvider imageProvider) diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs index ab4850ecf4..9401d098de 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs @@ -90,7 +90,7 @@ namespace ImageSharp.Tests image.Save(ms, new JpegEncoder()); ms.Seek(0, SeekOrigin.Begin); - using (JpegDecoderCore decoder = new JpegDecoderCore(null)) + using (JpegDecoderCore decoder = new JpegDecoderCore(null, new JpegDecoder())) { Image mirror = decoder.Decode(ms); From 8bd785af5070776412baee6b680a86c072ff4344 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Sun, 25 Jun 2017 18:03:59 +0100 Subject: [PATCH 09/36] switch from mimetype strings to IImageFormats --- .../ChangeDefaultEncoderOptions/Program.cs | 2 +- src/ImageSharp/Configuration.cs | 183 +++++++++--------- .../Formats/Bmp/BmpConfigurationModule.cs | 27 +++ src/ImageSharp/Formats/Bmp/BmpFormat.cs | 31 +++ ...eDetector.cs => BmpImageFormatDetector.cs} | 8 +- .../Formats/Bmp/BmpImageFormatProvider.cs | 45 ----- .../Formats/Gif/GifConfigurationModule.cs | 28 +++ src/ImageSharp/Formats/Gif/GifFormat.cs | 31 +++ ...eDetector.cs => GifImageFormatDetector.cs} | 8 +- .../Formats/Gif/GifImageFormatProvider.cs | 45 ----- src/ImageSharp/Formats/IImageFormat.cs | 39 ++++ ...ypeDetector.cs => IImageFormatDetector.cs} | 6 +- .../Formats/IImageFormatProvider.cs | 56 ------ .../Formats/Jpeg/JpegConfigurationModule.cs | 28 +++ src/ImageSharp/Formats/Jpeg/JpegFormat.cs | 31 +++ ...Detector.cs => JpegImageFormatDetector.cs} | 14 +- .../Formats/Jpeg/JpegImageFormatProvider.cs | 45 ----- .../Formats/Png/PngConfigurationModule.cs | 27 +++ src/ImageSharp/Formats/Png/PngFormat.cs | 31 +++ ...eDetector.cs => PngImageFormatDetector.cs} | 10 +- .../Formats/Png/PngImageFormatProvider.cs | 45 ----- src/ImageSharp/IConfigurationModule.cs | 23 +++ src/ImageSharp/Image/Image.Decode.cs | 31 ++- src/ImageSharp/Image/Image.FromBytes.cs | 34 ++-- src/ImageSharp/Image/Image.FromFile.cs | 28 +-- src/ImageSharp/Image/Image.FromStream.cs | 36 ++-- src/ImageSharp/Image/Image{TPixel}.cs | 52 ++--- src/ImageSharp/ImageFormats.cs | 39 ++++ src/ImageSharp/Memory/Buffer.cs | 10 + tests/ImageSharp.Tests/ConfigurationTests.cs | 76 +++----- .../Formats/GeneralFormatTests.cs | 6 +- .../Image/ImageDiscoverMimeType.cs | 56 +++--- .../ImageSharp.Tests/Image/ImageLoadTests.cs | 14 +- .../ImageSharp.Tests/Image/ImageSaveTests.cs | 19 +- tests/ImageSharp.Tests/Image/ImageTests.cs | 4 +- tests/ImageSharp.Tests/TestFormat.cs | 33 ++-- .../TestUtilities/ImagingTestCaseUtility.cs | 4 +- 37 files changed, 647 insertions(+), 558 deletions(-) create mode 100644 src/ImageSharp/Formats/Bmp/BmpConfigurationModule.cs create mode 100644 src/ImageSharp/Formats/Bmp/BmpFormat.cs rename src/ImageSharp/Formats/Bmp/{BmpMimeTypeDetector.cs => BmpImageFormatDetector.cs} (77%) delete mode 100644 src/ImageSharp/Formats/Bmp/BmpImageFormatProvider.cs create mode 100644 src/ImageSharp/Formats/Gif/GifConfigurationModule.cs create mode 100644 src/ImageSharp/Formats/Gif/GifFormat.cs rename src/ImageSharp/Formats/Gif/{GifMimeTypeDetector.cs => GifImageFormatDetector.cs} (81%) delete mode 100644 src/ImageSharp/Formats/Gif/GifImageFormatProvider.cs create mode 100644 src/ImageSharp/Formats/IImageFormat.cs rename src/ImageSharp/Formats/{IMimeTypeDetector.cs => IImageFormatDetector.cs} (81%) delete mode 100644 src/ImageSharp/Formats/IImageFormatProvider.cs create mode 100644 src/ImageSharp/Formats/Jpeg/JpegConfigurationModule.cs create mode 100644 src/ImageSharp/Formats/Jpeg/JpegFormat.cs rename src/ImageSharp/Formats/Jpeg/{JpegMimeTypeDetector.cs => JpegImageFormatDetector.cs} (85%) delete mode 100644 src/ImageSharp/Formats/Jpeg/JpegImageFormatProvider.cs create mode 100644 src/ImageSharp/Formats/Png/PngConfigurationModule.cs create mode 100644 src/ImageSharp/Formats/Png/PngFormat.cs rename src/ImageSharp/Formats/Png/{PngMimeTypeDetector.cs => PngImageFormatDetector.cs} (76%) delete mode 100644 src/ImageSharp/Formats/Png/PngImageFormatProvider.cs create mode 100644 src/ImageSharp/IConfigurationModule.cs create mode 100644 src/ImageSharp/ImageFormats.cs diff --git a/samples/ChangeDefaultEncoderOptions/Program.cs b/samples/ChangeDefaultEncoderOptions/Program.cs index b085e468a1..dab8d445ca 100644 --- a/samples/ChangeDefaultEncoderOptions/Program.cs +++ b/samples/ChangeDefaultEncoderOptions/Program.cs @@ -10,7 +10,7 @@ namespace ChangeDefaultEncoderOptions { // lets switch out the default encoder for jpeg to one // that saves at 90 quality and ignores the matadata - Configuration.Default.SetMimeTypeEncoder("image/jpeg", new ImageSharp.Formats.JpegEncoder() + Configuration.Default.SetEncoder(ImageFormats.Jpeg, new ImageSharp.Formats.JpegEncoder() { Quality = 90, IgnoreMetadata = true diff --git a/src/ImageSharp/Configuration.cs b/src/ImageSharp/Configuration.cs index f48aefbcd8..90fd3a0e66 100644 --- a/src/ImageSharp/Configuration.cs +++ b/src/ImageSharp/Configuration.cs @@ -18,7 +18,7 @@ namespace ImageSharp /// /// Provides initialization code which allows extending the library. /// - public class Configuration : IImageFormatHost + public class Configuration { /// /// A lazily initialized configuration default instance. @@ -33,22 +33,22 @@ namespace ImageSharp /// /// The list of supported keyed to mime types. /// - private readonly ConcurrentDictionary mimeTypeEncoders = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + private readonly ConcurrentDictionary mimeTypeEncoders = new ConcurrentDictionary(); /// - /// The list of supported mime types keyed to file extensions. + /// The list of supported keyed to mime types. /// - private readonly ConcurrentDictionary extensionsMap = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + private readonly ConcurrentDictionary mimeTypeDecoders = new ConcurrentDictionary(); /// - /// The list of supported keyed to mime types. + /// The list of supported s. /// - private readonly ConcurrentDictionary mimeTypeDecoders = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + private readonly List imageFormatDetectors = new List(); /// - /// The list of supported s. + /// The list of supported s. /// - private readonly List mimeTypeDetectors = new List(); + private readonly HashSet imageFormats = new HashSet(); /// /// Initializes a new instance of the class. @@ -61,11 +61,11 @@ namespace ImageSharp /// Initializes a new instance of the class. /// /// A collection of providers to configure - public Configuration(params IImageFormatProvider[] providers) + public Configuration(params IConfigurationModule[] providers) { if (providers != null) { - foreach (IImageFormatProvider p in providers) + foreach (IConfigurationModule p in providers) { p.Configure(this); } @@ -88,24 +88,24 @@ namespace ImageSharp internal int MaxHeaderSize { get; private set; } /// - /// Gets the currently registered s. + /// Gets the currently registered s. /// - internal IEnumerable MimeTypeDetectors => this.mimeTypeDetectors; + internal IEnumerable FormatDetectors => this.imageFormatDetectors; /// /// Gets the currently registered s. /// - internal IEnumerable> ImageDecoders => this.mimeTypeDecoders; + internal IEnumerable> ImageDecoders => this.mimeTypeDecoders; /// /// Gets the currently registered s. /// - internal IEnumerable> ImageEncoders => this.mimeTypeEncoders; + internal IEnumerable> ImageEncoders => this.mimeTypeEncoders; /// - /// Gets the currently registered file extensions. + /// Gets the currently registered s. /// - internal IEnumerable> ImageExtensionToMimeTypeMapping => this.extensionsMap; + internal IEnumerable ImageFormats => this.imageFormats; #if !NETSTANDARD1_1 /// @@ -117,35 +117,69 @@ namespace ImageSharp /// /// Registers a new format provider. /// - /// The format providers to call configure on. - public void AddImageFormat(IImageFormatProvider formatProvider) + /// The configuration provider to call configure on. + public void Configure(IConfigurationModule configuration) { - Guard.NotNull(formatProvider, nameof(formatProvider)); - formatProvider.Configure(this); + Guard.NotNull(configuration, nameof(configuration)); + configuration.Configure(this); } - /// - public void SetMimeTypeEncoder(string mimeType, IImageEncoder encoder) + /// + /// Registers a new format provider. + /// + /// The format to register as a well know format. + public void AddImageFormat(IImageFormat format) { - Guard.NotNullOrEmpty(mimeType, nameof(mimeType)); - Guard.NotNull(encoder, nameof(encoder)); - this.mimeTypeEncoders.AddOrUpdate(mimeType?.Trim(), encoder, (s, e) => encoder); + Guard.NotNull(format, nameof(format)); + Guard.NotNull(format.MimeTypes, nameof(format.MimeTypes)); + Guard.NotNull(format.FileExtensions, nameof(format.FileExtensions)); + this.imageFormats.Add(format); + } + + /// + /// For the specified file extensions type find the e . + /// + /// The extension to discover + /// The if found otherwise null + public IImageFormat FindFormatByFileExtensions(string extension) + { + return this.imageFormats.FirstOrDefault(x => x.FileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase)); } - /// - public void SetFileExtensionToMimeTypeMapping(string extension, string mimeType) + /// + /// For the specified mime type find the . + /// + /// The mime-type to discover + /// The if found otherwise null + public IImageFormat FindFormatByMimeType(string mimeType) { - Guard.NotNullOrEmpty(extension, nameof(extension)); - Guard.NotNullOrEmpty(mimeType, nameof(mimeType)); - this.extensionsMap.AddOrUpdate(extension?.Trim(), mimeType, (s, e) => mimeType); + return this.imageFormats.FirstOrDefault(x => x.MimeTypes.Contains(mimeType, StringComparer.OrdinalIgnoreCase)); } - /// - public void SetMimeTypeDecoder(string mimeType, IImageDecoder decoder) + /// + /// Sets a specific image encoder as the encoder for a specific image format. + /// + /// The image format to register the encoder for. + /// The encoder to use, + public void SetEncoder(IImageFormat imageFormat, IImageEncoder encoder) { - Guard.NotNullOrEmpty(mimeType, nameof(mimeType)); + Guard.NotNull(imageFormat, nameof(imageFormat)); + Guard.NotNull(encoder, nameof(encoder)); + this.AddImageFormat(imageFormat); + this.mimeTypeEncoders.AddOrUpdate(imageFormat, encoder, (s, e) => encoder); + } + + /// + /// Sets a specific image decoder as the decoder for a specific image format. + /// + /// The image format to register the encoder for. + /// The decoder to use, + public void SetDecoder(IImageFormat imageFormat, IImageDecoder decoder) + { + Guard.NotNull(imageFormat, nameof(imageFormat)); Guard.NotNull(decoder, nameof(decoder)); - this.mimeTypeDecoders.AddOrUpdate(mimeType, decoder, (s, e) => decoder); + this.AddImageFormat(imageFormat); + this.mimeTypeDecoders.AddOrUpdate(imageFormat, decoder, (s, e) => decoder); } /// @@ -153,43 +187,46 @@ namespace ImageSharp /// public void ClearMimeTypeDetectors() { - this.mimeTypeDetectors.Clear(); + this.imageFormatDetectors.Clear(); } - /// - public void AddMimeTypeDetector(IMimeTypeDetector detector) + /// + /// Adds a new detector for detecting mime types. + /// + /// The detector to add + public void AddImageFormatDetector(IImageFormatDetector detector) { Guard.NotNull(detector, nameof(detector)); - this.mimeTypeDetectors.Add(detector); + this.imageFormatDetectors.Add(detector); this.SetMaxHeaderSize(); } /// - /// Creates the default instance with the following s preregistered: - /// - /// - /// - /// + /// Creates the default instance with the following s preregistered: + /// + /// + /// + /// /// /// The default configuration of internal static Configuration CreateDefaultInstance() { return new Configuration( - new PngImageFormatProvider(), - new JpegImageFormatProvider(), - new GifImageFormatProvider(), - new BmpImageFormatProvider()); + new PngConfigurationModule(), + new JpegConfigurationModule(), + new GifConfigurationModule(), + new BmpConfigurationModule()); } /// /// For the specified mime type find the decoder. /// - /// The mime type to discover + /// The format to discover /// The if found otherwise null - internal IImageDecoder FindMimeTypeDecoder(string mimeType) + internal IImageDecoder FindDecoder(IImageFormat format) { - Guard.NotNullOrEmpty(mimeType, nameof(mimeType)); - if (this.mimeTypeDecoders.TryGetValue(mimeType, out IImageDecoder decoder)) + Guard.NotNull(format, nameof(format)); + if (this.mimeTypeDecoders.TryGetValue(format, out IImageDecoder decoder)) { return decoder; } @@ -200,12 +237,12 @@ namespace ImageSharp /// /// For the specified mime type find the encoder. /// - /// The mime type to discover + /// The format to discover /// The if found otherwise null - internal IImageEncoder FindMimeTypeEncoder(string mimeType) + internal IImageEncoder FindEncoder(IImageFormat format) { - Guard.NotNullOrEmpty(mimeType, nameof(mimeType)); - if (this.mimeTypeEncoders.TryGetValue(mimeType, out IImageEncoder encoder)) + Guard.NotNull(format, nameof(format)); + if (this.mimeTypeEncoders.TryGetValue(format, out IImageEncoder encoder)) { return encoder; } @@ -213,46 +250,12 @@ namespace ImageSharp return null; } - /// - /// For the specified mime type find the encoder. - /// - /// The extensions to discover - /// The if found otherwise null - internal IImageEncoder FindFileExtensionsEncoder(string extensions) - { - extensions = extensions?.TrimStart('.'); - Guard.NotNullOrEmpty(extensions, nameof(extensions)); - if (this.extensionsMap.TryGetValue(extensions, out string mimeType)) - { - return this.FindMimeTypeEncoder(mimeType); - } - - return null; - } - - /// - /// For the specified extension find the mime type. - /// - /// the extensions to discover - /// The mime type if found otherwise null - internal string FindFileExtensionsMimeType(string extensions) - { - extensions = extensions?.TrimStart('.'); - Guard.NotNullOrEmpty(extensions, nameof(extensions)); - if (this.extensionsMap.TryGetValue(extensions, out string mimeType)) - { - return mimeType; - } - - return null; - } - /// /// Sets the max header size. /// private void SetMaxHeaderSize() { - this.MaxHeaderSize = this.mimeTypeDetectors.Max(x => x.HeaderSize); + this.MaxHeaderSize = this.imageFormatDetectors.Max(x => x.HeaderSize); } } } diff --git a/src/ImageSharp/Formats/Bmp/BmpConfigurationModule.cs b/src/ImageSharp/Formats/Bmp/BmpConfigurationModule.cs new file mode 100644 index 0000000000..2553f14542 --- /dev/null +++ b/src/ImageSharp/Formats/Bmp/BmpConfigurationModule.cs @@ -0,0 +1,27 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Text; + using ImageSharp.PixelFormats; + + /// + /// Registers the image encoders, decoders and mime type detectors for the bmp format. + /// + public class BmpConfigurationModule : IConfigurationModule + { + /// + public void Configure(Configuration config) + { + config.SetEncoder(ImageFormats.Bitmap, new BmpEncoder()); + config.SetDecoder(ImageFormats.Bitmap, new BmpDecoder()); + config.AddImageFormatDetector(new BmpImageFormatDetector()); + } + } +} diff --git a/src/ImageSharp/Formats/Bmp/BmpFormat.cs b/src/ImageSharp/Formats/Bmp/BmpFormat.cs new file mode 100644 index 0000000000..4f32e2875e --- /dev/null +++ b/src/ImageSharp/Formats/Bmp/BmpFormat.cs @@ -0,0 +1,31 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Text; + using ImageSharp.PixelFormats; + + /// + /// Registers the image encoders, decoders and mime type detectors for the jpeg format. + /// + internal sealed class BmpFormat : IImageFormat + { + /// + public string Name => "BMP"; + + /// + public string DefaultMimeType => "image/bmp"; + + /// + public IEnumerable MimeTypes => BmpConstants.MimeTypes; + + /// + public IEnumerable FileExtensions => BmpConstants.FileExtensions; + } +} diff --git a/src/ImageSharp/Formats/Bmp/BmpMimeTypeDetector.cs b/src/ImageSharp/Formats/Bmp/BmpImageFormatDetector.cs similarity index 77% rename from src/ImageSharp/Formats/Bmp/BmpMimeTypeDetector.cs rename to src/ImageSharp/Formats/Bmp/BmpImageFormatDetector.cs index c13181d6a2..4f1054b097 100644 --- a/src/ImageSharp/Formats/Bmp/BmpMimeTypeDetector.cs +++ b/src/ImageSharp/Formats/Bmp/BmpImageFormatDetector.cs @@ -14,23 +14,23 @@ namespace ImageSharp.Formats /// /// Detects bmp file headers /// - internal class BmpMimeTypeDetector : IMimeTypeDetector + internal class BmpImageFormatDetector : IImageFormatDetector { /// public int HeaderSize => 2; /// - public string DetectMimeType(Span header) + public IImageFormat DetectFormat(ReadOnlySpan header) { if (this.IsSupportedFileFormat(header)) { - return "image/bmp"; + return ImageFormats.Bitmap; } return null; } - private bool IsSupportedFileFormat(Span header) + private bool IsSupportedFileFormat(ReadOnlySpan header) { return header.Length >= this.HeaderSize && header[0] == 0x42 && // B diff --git a/src/ImageSharp/Formats/Bmp/BmpImageFormatProvider.cs b/src/ImageSharp/Formats/Bmp/BmpImageFormatProvider.cs deleted file mode 100644 index b532ccfb51..0000000000 --- a/src/ImageSharp/Formats/Bmp/BmpImageFormatProvider.cs +++ /dev/null @@ -1,45 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Formats -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Text; - using ImageSharp.PixelFormats; - - /// - /// Registers the image encoders, decoders and mime type detectors for the bmp format. - /// - public class BmpImageFormatProvider : IImageFormatProvider - { - /// - public void Configure(IImageFormatHost host) - { - var encoder = new BmpEncoder(); - foreach (string mimeType in BmpConstants.MimeTypes) - { - host.SetMimeTypeEncoder(mimeType, encoder); - } - - foreach (string ext in BmpConstants.FileExtensions) - { - foreach (string mimeType in BmpConstants.MimeTypes) - { - host.SetFileExtensionToMimeTypeMapping(ext, mimeType); - } - } - - var decoder = new BmpDecoder(); - foreach (string mimeType in BmpConstants.MimeTypes) - { - host.SetMimeTypeDecoder(mimeType, decoder); - } - - host.AddMimeTypeDetector(new BmpMimeTypeDetector()); - } - } -} diff --git a/src/ImageSharp/Formats/Gif/GifConfigurationModule.cs b/src/ImageSharp/Formats/Gif/GifConfigurationModule.cs new file mode 100644 index 0000000000..0640cb2347 --- /dev/null +++ b/src/ImageSharp/Formats/Gif/GifConfigurationModule.cs @@ -0,0 +1,28 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Text; + using ImageSharp.PixelFormats; + + /// + /// Registers the image encoders, decoders and mime type detectors for the gif format. + /// + public class GifConfigurationModule : IConfigurationModule + { + /// + public void Configure(Configuration config) + { + config.SetEncoder(ImageFormats.Gif, new GifEncoder()); + config.SetDecoder(ImageFormats.Gif, new GifDecoder()); + + config.AddImageFormatDetector(new GifImageFormatDetector()); + } + } +} diff --git a/src/ImageSharp/Formats/Gif/GifFormat.cs b/src/ImageSharp/Formats/Gif/GifFormat.cs new file mode 100644 index 0000000000..5aa667ea91 --- /dev/null +++ b/src/ImageSharp/Formats/Gif/GifFormat.cs @@ -0,0 +1,31 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Text; + using ImageSharp.PixelFormats; + + /// + /// Registers the image encoders, decoders and mime type detectors for the jpeg format. + /// + internal sealed class GifFormat : IImageFormat + { + /// + public string Name => "GIF"; + + /// + public string DefaultMimeType => "image/gif"; + + /// + public IEnumerable MimeTypes => GifConstants.MimeTypes; + + /// + public IEnumerable FileExtensions => GifConstants.FileExtensions; + } +} diff --git a/src/ImageSharp/Formats/Gif/GifMimeTypeDetector.cs b/src/ImageSharp/Formats/Gif/GifImageFormatDetector.cs similarity index 81% rename from src/ImageSharp/Formats/Gif/GifMimeTypeDetector.cs rename to src/ImageSharp/Formats/Gif/GifImageFormatDetector.cs index f4ad8fa8e1..2aee867953 100644 --- a/src/ImageSharp/Formats/Gif/GifMimeTypeDetector.cs +++ b/src/ImageSharp/Formats/Gif/GifImageFormatDetector.cs @@ -14,23 +14,23 @@ namespace ImageSharp.Formats /// /// Detects gif file headers /// - public class GifMimeTypeDetector : IMimeTypeDetector + public class GifImageFormatDetector : IImageFormatDetector { /// public int HeaderSize => 6; /// - public string DetectMimeType(Span header) + public IImageFormat DetectFormat(ReadOnlySpan header) { if (this.IsSupportedFileFormat(header)) { - return "image/gif"; + return ImageFormats.Gif; } return null; } - private bool IsSupportedFileFormat(Span header) + private bool IsSupportedFileFormat(ReadOnlySpan header) { return header.Length >= this.HeaderSize && header[0] == 0x47 && // G diff --git a/src/ImageSharp/Formats/Gif/GifImageFormatProvider.cs b/src/ImageSharp/Formats/Gif/GifImageFormatProvider.cs deleted file mode 100644 index 7e16bff720..0000000000 --- a/src/ImageSharp/Formats/Gif/GifImageFormatProvider.cs +++ /dev/null @@ -1,45 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Formats -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Text; - using ImageSharp.PixelFormats; - - /// - /// Registers the image encoders, decoders and mime type detectors for the gif format. - /// - public class GifImageFormatProvider : IImageFormatProvider - { - /// - public void Configure(IImageFormatHost host) - { - var encoder = new GifEncoder(); - foreach (string mimeType in GifConstants.MimeTypes) - { - host.SetMimeTypeEncoder(mimeType, encoder); - } - - foreach (string ext in GifConstants.FileExtensions) - { - foreach (string mimeType in GifConstants.MimeTypes) - { - host.SetFileExtensionToMimeTypeMapping(ext, mimeType); - } - } - - var decoder = new GifDecoder(); - foreach (string mimeType in GifConstants.MimeTypes) - { - host.SetMimeTypeDecoder(mimeType, decoder); - } - - host.AddMimeTypeDetector(new GifMimeTypeDetector()); - } - } -} diff --git a/src/ImageSharp/Formats/IImageFormat.cs b/src/ImageSharp/Formats/IImageFormat.cs new file mode 100644 index 0000000000..8643e2f4c9 --- /dev/null +++ b/src/ImageSharp/Formats/IImageFormat.cs @@ -0,0 +1,39 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using System; + using System.Collections.Generic; + using System.IO; + + using ImageSharp.PixelFormats; + + /// + /// Describes an image format. + /// + public interface IImageFormat + { + /// + /// Gets the name that describes this image format. + /// + string Name { get; } + + /// + /// Gets the default mimetype that the image foramt uses + /// + string DefaultMimeType { get; } + + /// + /// Gets all the mimetypes that have been used by this image foramt. + /// + IEnumerable MimeTypes { get; } + + /// + /// Gets the file extensions this image format commonly uses. + /// + IEnumerable FileExtensions { get; } + } +} diff --git a/src/ImageSharp/Formats/IMimeTypeDetector.cs b/src/ImageSharp/Formats/IImageFormatDetector.cs similarity index 81% rename from src/ImageSharp/Formats/IMimeTypeDetector.cs rename to src/ImageSharp/Formats/IImageFormatDetector.cs index f55be71516..a53da07e8b 100644 --- a/src/ImageSharp/Formats/IMimeTypeDetector.cs +++ b/src/ImageSharp/Formats/IImageFormatDetector.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // @@ -12,7 +12,7 @@ namespace ImageSharp.Formats /// /// Used for detecting mime types from a file header /// - public interface IMimeTypeDetector + public interface IImageFormatDetector { /// /// Gets the size of the header for this image type. @@ -25,6 +25,6 @@ namespace ImageSharp.Formats /// /// The containing the file header. /// returns the mime type of detected othersie returns null - string DetectMimeType(Span header); + IImageFormat DetectFormat(ReadOnlySpan header); } } diff --git a/src/ImageSharp/Formats/IImageFormatProvider.cs b/src/ImageSharp/Formats/IImageFormatProvider.cs deleted file mode 100644 index 1e5ea7f93e..0000000000 --- a/src/ImageSharp/Formats/IImageFormatProvider.cs +++ /dev/null @@ -1,56 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Formats -{ - using System; - using System.Collections.Generic; - using System.Text; - - /// - /// Represents an interface that can register image encoders, decoders and mime type detectors. - /// - public interface IImageFormatProvider - { - /// - /// Called when loaded so the provider and register its encoders, decoders and mime type detectors into an IImageFormatHost. - /// - /// The host that will retain the encoders, decodes and mime type detectors. - void Configure(IImageFormatHost host); - } - - /// - /// Represents an interface that can have encoders, decoders and mime type detectors loaded into. - /// - public interface IImageFormatHost - { - /// - /// Sets a specific image encoder as the encoder for a specific mime type. - /// - /// the target mimetype - /// the encoder to use - void SetMimeTypeEncoder(string mimeType, IImageEncoder encoder); // could/should this be an Action??? - - /// - /// Sets a mapping value between a file extension and a mime type. - /// - /// The target mime type - /// The mime type this extension equates to - void SetFileExtensionToMimeTypeMapping(string extension, string mimetype); - - /// - /// Sets a specific image decoder as the decoder for a specific mime type. - /// - /// The target mime type - /// The decoder to use - void SetMimeTypeDecoder(string mimeType, IImageDecoder decoder); - - /// - /// Adds a new detector for detecting mime types. - /// - /// The detector to add - void AddMimeTypeDetector(IMimeTypeDetector detector); - } -} diff --git a/src/ImageSharp/Formats/Jpeg/JpegConfigurationModule.cs b/src/ImageSharp/Formats/Jpeg/JpegConfigurationModule.cs new file mode 100644 index 0000000000..64c7af3579 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/JpegConfigurationModule.cs @@ -0,0 +1,28 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Text; + using ImageSharp.PixelFormats; + + /// + /// Registers the image encoders, decoders and mime type detectors for the jpeg format. + /// + public class JpegConfigurationModule : IConfigurationModule + { + /// + public void Configure(Configuration config) + { + config.SetEncoder(ImageFormats.Jpeg, new JpegEncoder()); + config.SetDecoder(ImageFormats.Jpeg, new JpegDecoder()); + + config.AddImageFormatDetector(new JpegImageFormatDetector()); + } + } +} diff --git a/src/ImageSharp/Formats/Jpeg/JpegFormat.cs b/src/ImageSharp/Formats/Jpeg/JpegFormat.cs new file mode 100644 index 0000000000..86b624fc85 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/JpegFormat.cs @@ -0,0 +1,31 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Text; + using ImageSharp.PixelFormats; + + /// + /// Registers the image encoders, decoders and mime type detectors for the jpeg format. + /// + internal sealed class JpegFormat : IImageFormat + { + /// + public string Name => "JPEG"; + + /// + public string DefaultMimeType => "image/jpeg"; + + /// + public IEnumerable MimeTypes => JpegConstants.MimeTypes; + + /// + public IEnumerable FileExtensions => JpegConstants.FileExtensions; + } +} diff --git a/src/ImageSharp/Formats/Jpeg/JpegMimeTypeDetector.cs b/src/ImageSharp/Formats/Jpeg/JpegImageFormatDetector.cs similarity index 85% rename from src/ImageSharp/Formats/Jpeg/JpegMimeTypeDetector.cs rename to src/ImageSharp/Formats/Jpeg/JpegImageFormatDetector.cs index f84a91f70c..82f810625f 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegMimeTypeDetector.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegImageFormatDetector.cs @@ -14,23 +14,23 @@ namespace ImageSharp.Formats /// /// Detects Jpeg file headers /// - public class JpegMimeTypeDetector : IMimeTypeDetector + public class JpegImageFormatDetector : IImageFormatDetector { /// public int HeaderSize => 11; /// - public string DetectMimeType(Span header) + public IImageFormat DetectFormat(ReadOnlySpan header) { if (this.IsSupportedFileFormat(header)) { - return "image/jpeg"; + return ImageFormats.Jpeg; } return null; } - private bool IsSupportedFileFormat(Span header) + private bool IsSupportedFileFormat(ReadOnlySpan header) { return header.Length >= this.HeaderSize && (this.IsJfif(header) || this.IsExif(header) || this.IsJpeg(header)); @@ -41,7 +41,7 @@ namespace ImageSharp.Formats /// /// The bytes representing the file header. /// The - private bool IsJfif(Span header) + private bool IsJfif(ReadOnlySpan header) { bool isJfif = header[6] == 0x4A && // J @@ -58,7 +58,7 @@ namespace ImageSharp.Formats /// /// The bytes representing the file header. /// The - private bool IsExif(Span header) + private bool IsExif(ReadOnlySpan header) { bool isExif = header[6] == 0x45 && // E @@ -76,7 +76,7 @@ namespace ImageSharp.Formats /// /// The bytes representing the file header. /// The - private bool IsJpeg(Span header) + private bool IsJpeg(ReadOnlySpan header) { bool isJpg = header[0] == 0xFF && // 255 diff --git a/src/ImageSharp/Formats/Jpeg/JpegImageFormatProvider.cs b/src/ImageSharp/Formats/Jpeg/JpegImageFormatProvider.cs deleted file mode 100644 index 5eefa5db16..0000000000 --- a/src/ImageSharp/Formats/Jpeg/JpegImageFormatProvider.cs +++ /dev/null @@ -1,45 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Formats -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Text; - using ImageSharp.PixelFormats; - - /// - /// Registers the image encoders, decoders and mime type detectors for the jpeg format. - /// - public class JpegImageFormatProvider : IImageFormatProvider - { - /// - public void Configure(IImageFormatHost host) - { - var pngEncoder = new JpegEncoder(); - foreach (string mimeType in JpegConstants.MimeTypes) - { - host.SetMimeTypeEncoder(mimeType, pngEncoder); - } - - foreach (string ext in JpegConstants.FileExtensions) - { - foreach (string mimeType in JpegConstants.MimeTypes) - { - host.SetFileExtensionToMimeTypeMapping(ext, mimeType); - } - } - - var pngDecoder = new JpegDecoder(); - foreach (string mimeType in JpegConstants.MimeTypes) - { - host.SetMimeTypeDecoder(mimeType, pngDecoder); - } - - host.AddMimeTypeDetector(new JpegMimeTypeDetector()); - } - } -} diff --git a/src/ImageSharp/Formats/Png/PngConfigurationModule.cs b/src/ImageSharp/Formats/Png/PngConfigurationModule.cs new file mode 100644 index 0000000000..47fe8b136a --- /dev/null +++ b/src/ImageSharp/Formats/Png/PngConfigurationModule.cs @@ -0,0 +1,27 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Text; + using ImageSharp.PixelFormats; + + /// + /// Registers the image encoders, decoders and mime type detectors for the png format. + /// + public class PngConfigurationModule : IConfigurationModule + { + /// + public void Configure(Configuration host) + { + host.SetEncoder(ImageFormats.Png, new PngEncoder()); + host.SetDecoder(ImageFormats.Png, new PngDecoder()); + host.AddImageFormatDetector(new PngImageFormatDetector()); + } + } +} diff --git a/src/ImageSharp/Formats/Png/PngFormat.cs b/src/ImageSharp/Formats/Png/PngFormat.cs new file mode 100644 index 0000000000..60ba306056 --- /dev/null +++ b/src/ImageSharp/Formats/Png/PngFormat.cs @@ -0,0 +1,31 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Text; + using ImageSharp.PixelFormats; + + /// + /// Registers the image encoders, decoders and mime type detectors for the jpeg format. + /// + internal sealed class PngFormat : IImageFormat + { + /// + public string Name => "PNG"; + + /// + public string DefaultMimeType => "image/png"; + + /// + public IEnumerable MimeTypes => PngConstants.MimeTypes; + + /// + public IEnumerable FileExtensions => PngConstants.FileExtensions; + } +} diff --git a/src/ImageSharp/Formats/Png/PngMimeTypeDetector.cs b/src/ImageSharp/Formats/Png/PngImageFormatDetector.cs similarity index 76% rename from src/ImageSharp/Formats/Png/PngMimeTypeDetector.cs rename to src/ImageSharp/Formats/Png/PngImageFormatDetector.cs index 6b5d43fbdd..6183ef4496 100644 --- a/src/ImageSharp/Formats/Png/PngMimeTypeDetector.cs +++ b/src/ImageSharp/Formats/Png/PngImageFormatDetector.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // @@ -14,23 +14,23 @@ namespace ImageSharp.Formats /// /// Detects png file headers /// - public class PngMimeTypeDetector : IMimeTypeDetector + public class PngImageFormatDetector : IImageFormatDetector { /// public int HeaderSize => 8; /// - public string DetectMimeType(Span header) + public IImageFormat DetectFormat(ReadOnlySpan header) { if (this.IsSupportedFileFormat(header)) { - return "image/png"; + return ImageFormats.Png; } return null; } - private bool IsSupportedFileFormat(Span header) + private bool IsSupportedFileFormat(ReadOnlySpan header) { return header.Length >= this.HeaderSize && header[0] == 0x89 && diff --git a/src/ImageSharp/Formats/Png/PngImageFormatProvider.cs b/src/ImageSharp/Formats/Png/PngImageFormatProvider.cs deleted file mode 100644 index abbbaa6d57..0000000000 --- a/src/ImageSharp/Formats/Png/PngImageFormatProvider.cs +++ /dev/null @@ -1,45 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Formats -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Text; - using ImageSharp.PixelFormats; - - /// - /// Registers the image encoders, decoders and mime type detectors for the png format. - /// - public class PngImageFormatProvider : IImageFormatProvider - { - /// - public void Configure(IImageFormatHost host) - { - var pngEncoder = new PngEncoder(); - foreach (string mimeType in PngConstants.MimeTypes) - { - host.SetMimeTypeEncoder(mimeType, pngEncoder); - } - - foreach (string ext in PngConstants.FileExtensions) - { - foreach (string mimeType in PngConstants.MimeTypes) - { - host.SetFileExtensionToMimeTypeMapping(ext, mimeType); - } - } - - var pngDecoder = new PngDecoder(); - foreach (string mimeType in PngConstants.MimeTypes) - { - host.SetMimeTypeDecoder(mimeType, pngDecoder); - } - - host.AddMimeTypeDetector(new PngMimeTypeDetector()); - } - } -} diff --git a/src/ImageSharp/IConfigurationModule.cs b/src/ImageSharp/IConfigurationModule.cs new file mode 100644 index 0000000000..d37e0042b5 --- /dev/null +++ b/src/ImageSharp/IConfigurationModule.cs @@ -0,0 +1,23 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Collections.Generic; + using System.Text; + + /// + /// Represents an interface that can register image encoders, decoders and image format detectors. + /// + public interface IConfigurationModule + { + /// + /// Called when loaded into a configuration object so the module can register items into the configuration. + /// + /// The configuration that will retain the encoders, decodes and mime type detectors. + void Configure(Configuration configuration); + } +} diff --git a/src/ImageSharp/Image/Image.Decode.cs b/src/ImageSharp/Image/Image.Decode.cs index 436cde466d..2408ec3aa2 100644 --- a/src/ImageSharp/Image/Image.Decode.cs +++ b/src/ImageSharp/Image/Image.Decode.cs @@ -9,7 +9,7 @@ namespace ImageSharp using System.IO; using System.Linq; using Formats; - + using ImageSharp.Memory; using ImageSharp.PixelFormats; /// @@ -23,7 +23,7 @@ namespace ImageSharp /// The image stream to read the header from. /// The configuration. /// The mime type or null if none found. - private static string InternalDiscoverMimeType(Stream stream, Configuration config) + private static IImageFormat InternalDetectFormat(Stream stream, Configuration config) { // This is probably a candidate for making into a public API in the future! int maxHeaderSize = config.MaxHeaderSize; @@ -32,17 +32,12 @@ namespace ImageSharp return null; } - byte[] header = ArrayPool.Shared.Rent(maxHeaderSize); - try + using (var buffer = new Buffer(maxHeaderSize)) { long startPosition = stream.Position; - stream.Read(header, 0, maxHeaderSize); + stream.Read(buffer.Array, 0, maxHeaderSize); stream.Position = startPosition; - return config.MimeTypeDetectors.Select(x => x.DetectMimeType(header)).LastOrDefault(x => x != null); - } - finally - { - ArrayPool.Shared.Return(header); + return config.FormatDetectors.Select(x => x.DetectFormat(buffer)).LastOrDefault(x => x != null); } } @@ -51,14 +46,14 @@ namespace ImageSharp /// /// The image stream to read the header from. /// The configuration. - /// The mimeType. + /// The IImageFormat. /// The image format or null if none found. - private static IImageDecoder DiscoverDecoder(Stream stream, Configuration config, out string mimeType) + private static IImageDecoder DiscoverDecoder(Stream stream, Configuration config, out IImageFormat format) { - mimeType = InternalDiscoverMimeType(stream, config); - if (mimeType != null) + format = InternalDetectFormat(stream, config); + if (format != null) { - return config.FindMimeTypeDecoder(mimeType); + return config.FindDecoder(format); } return null; @@ -74,18 +69,18 @@ namespace ImageSharp /// /// A new . /// - private static (Image img, string mimeType) Decode(Stream stream, Configuration config) + private static (Image img, IImageFormat format) Decode(Stream stream, Configuration config) #pragma warning restore SA1008 // Opening parenthesis must be spaced correctly where TPixel : struct, IPixel { - IImageDecoder decoder = DiscoverDecoder(stream, config, out string mimeType); + IImageDecoder decoder = DiscoverDecoder(stream, config, out IImageFormat format); if (decoder == null) { return (null, null); } Image img = decoder.Decode(config, stream); - return (img, mimeType); + return (img, format); } } } \ No newline at end of file diff --git a/src/ImageSharp/Image/Image.FromBytes.cs b/src/ImageSharp/Image/Image.FromBytes.cs index 3b95211023..c7023b860b 100644 --- a/src/ImageSharp/Image/Image.FromBytes.cs +++ b/src/ImageSharp/Image/Image.FromBytes.cs @@ -16,26 +16,26 @@ namespace ImageSharp public static partial class Image { /// - /// By reading the header on the provided byte array this calculates the images mime type. + /// By reading the header on the provided byte array this calculates the images format. /// /// The byte array containing image data to read the header from. - /// The mime type or null if none found. - public static string DiscoverMimeType(byte[] data) + /// The format or null if none found. + public static IImageFormat DetectFormat(byte[] data) { - return DiscoverMimeType(null, data); + return DetectFormat(null, data); } /// - /// By reading the header on the provided byte array this calculates the images mime type. + /// By reading the header on the provided byte array this calculates the images format. /// /// The configuration. /// The byte array containing image data to read the header from. /// The mime type or null if none found. - public static string DiscoverMimeType(Configuration config, byte[] data) + public static IImageFormat DetectFormat(Configuration config, byte[] data) { using (Stream stream = new MemoryStream(data)) { - return DiscoverMimeType(config, stream); + return DetectFormat(config, stream); } } @@ -50,9 +50,9 @@ namespace ImageSharp /// Create a new instance of the class from the given byte array. /// /// The byte array containing image data. - /// The mime type of the decoded image. + /// The mime type of the decoded image. /// A new . - public static Image Load(byte[] data, out string mimeType) => Load(null, data, out mimeType); + public static Image Load(byte[] data, out IImageFormat format) => Load(null, data, out format); /// /// Create a new instance of the class from the given byte array. @@ -67,9 +67,9 @@ namespace ImageSharp /// /// The config for the decoder. /// The byte array containing image data. - /// The mime type of the decoded image. + /// The mime type of the decoded image. /// A new . - public static Image Load(Configuration config, byte[] data, out string mimeType) => Load(config, data, out mimeType); + public static Image Load(Configuration config, byte[] data, out IImageFormat format) => Load(config, data, out format); /// /// Create a new instance of the class from the given byte array. @@ -104,13 +104,13 @@ namespace ImageSharp /// Create a new instance of the class from the given byte array. /// /// The byte array containing image data. - /// The mime type of the decoded image. + /// The mime type of the decoded image. /// The pixel format. /// A new . - public static Image Load(byte[] data, out string mimeType) + public static Image Load(byte[] data, out IImageFormat format) where TPixel : struct, IPixel { - return Load(null, data, out mimeType); + return Load(null, data, out format); } /// @@ -134,15 +134,15 @@ namespace ImageSharp /// /// The configuration options. /// The byte array containing image data. - /// The mime type of the decoded image. + /// The mime type of the decoded image. /// The pixel format. /// A new . - public static Image Load(Configuration config, byte[] data, out string mimeType) + public static Image Load(Configuration config, byte[] data, out IImageFormat format) where TPixel : struct, IPixel { using (var memoryStream = new MemoryStream(data)) { - return Load(config, memoryStream, out mimeType); + return Load(config, memoryStream, out format); } } diff --git a/src/ImageSharp/Image/Image.FromFile.cs b/src/ImageSharp/Image/Image.FromFile.cs index c44a5da5e6..ca395ce088 100644 --- a/src/ImageSharp/Image/Image.FromFile.cs +++ b/src/ImageSharp/Image/Image.FromFile.cs @@ -21,9 +21,9 @@ namespace ImageSharp /// /// The image file to open and to read the header from. /// The mime type or null if none found. - public static string DiscoverMimeType(string filePath) + public static IImageFormat DetectFormat(string filePath) { - return DiscoverMimeType(null, filePath); + return DetectFormat(null, filePath); } /// @@ -32,12 +32,12 @@ namespace ImageSharp /// The configuration. /// The image file to open and to read the header from. /// The mime type or null if none found. - public static string DiscoverMimeType(Configuration config, string filePath) + public static IImageFormat DetectFormat(Configuration config, string filePath) { config = config ?? Configuration.Default; using (Stream file = config.FileSystem.OpenRead(filePath)) { - return DiscoverMimeType(config, file); + return DetectFormat(config, file); } } @@ -55,12 +55,12 @@ namespace ImageSharp /// Create a new instance of the class from the given file. /// /// The file path to the image. - /// The mime type of the decoded image. + /// The mime type of the decoded image. /// /// Thrown if the stream is not readable nor seekable. /// /// A new . - public static Image Load(string path, out string mimeType) => Load(path, out mimeType); + public static Image Load(string path, out IImageFormat format) => Load(path, out format); /// /// Create a new instance of the class from the given file. @@ -78,12 +78,12 @@ namespace ImageSharp /// /// The config for the decoder. /// The file path to the image. - /// The mime type of the decoded image. + /// The mime type of the decoded image. /// /// Thrown if the stream is not readable nor seekable. /// /// A new . - public static Image Load(Configuration config, string path, out string mimeType) => Load(config, path, out mimeType); + public static Image Load(Configuration config, string path, out IImageFormat format) => Load(config, path, out format); /// /// Create a new instance of the class from the given file. @@ -127,16 +127,16 @@ namespace ImageSharp /// Create a new instance of the class from the given file. /// /// The file path to the image. - /// The mime type of the decoded image. + /// The mime type of the decoded image. /// /// Thrown if the stream is not readable nor seekable. /// /// The pixel format. /// A new . - public static Image Load(string path, out string mimeType) + public static Image Load(string path, out IImageFormat format) where TPixel : struct, IPixel { - return Load(null, path, out mimeType); + return Load(null, path, out format); } /// @@ -164,19 +164,19 @@ namespace ImageSharp /// /// The configuration options. /// The file path to the image. - /// The mime type of the decoded image. + /// The mime type of the decoded image. /// /// Thrown if the stream is not readable nor seekable. /// /// The pixel format. /// A new . - public static Image Load(Configuration config, string path, out string mimeType) + public static Image Load(Configuration config, string path, out IImageFormat format) where TPixel : struct, IPixel { config = config ?? Configuration.Default; using (Stream stream = config.FileSystem.OpenRead(path)) { - return Load(config, stream, out mimeType); + return Load(config, stream, out format); } } diff --git a/src/ImageSharp/Image/Image.FromStream.cs b/src/ImageSharp/Image/Image.FromStream.cs index 19f9304931..7493d66fcc 100644 --- a/src/ImageSharp/Image/Image.FromStream.cs +++ b/src/ImageSharp/Image/Image.FromStream.cs @@ -24,9 +24,9 @@ namespace ImageSharp /// /// The image stream to read the header from. /// The mime type or null if none found. - public static string DiscoverMimeType(Stream stream) + public static IImageFormat DetectFormat(Stream stream) { - return DiscoverMimeType(null, stream); + return DetectFormat(null, stream); } /// @@ -35,21 +35,21 @@ namespace ImageSharp /// The configuration. /// The image stream to read the header from. /// The mime type or null if none found. - public static string DiscoverMimeType(Configuration config, Stream stream) + public static IImageFormat DetectFormat(Configuration config, Stream stream) { - return WithSeekableStream(stream, s => InternalDiscoverMimeType(s, config ?? Configuration.Default)); + return WithSeekableStream(stream, s => InternalDetectFormat(s, config ?? Configuration.Default)); } /// /// Create a new instance of the class from the given stream. /// /// The stream containing image information. - /// the mime type of the decoded image. + /// the mime type of the decoded image. /// /// Thrown if the stream is not readable nor seekable. /// /// A new .> - public static Image Load(Stream stream, out string mimeType) => Load(stream, out mimeType); + public static Image Load(Stream stream, out IImageFormat format) => Load(stream, out format); /// /// Create a new instance of the class from the given stream. @@ -88,12 +88,12 @@ namespace ImageSharp /// /// The config for the decoder. /// The stream containing image information. - /// the mime type of the decoded image. + /// the mime type of the decoded image. /// /// Thrown if the stream is not readable nor seekable. /// /// A new .> - public static Image Load(Configuration config, Stream stream, out string mimeType) => Load(config, stream, out mimeType); + public static Image Load(Configuration config, Stream stream, out IImageFormat format) => Load(config, stream, out format); /// /// Create a new instance of the class from the given stream. @@ -114,16 +114,16 @@ namespace ImageSharp /// Create a new instance of the class from the given stream. /// /// The stream containing image information. - /// the mime type of the decoded image. + /// the mime type of the decoded image. /// /// Thrown if the stream is not readable nor seekable. /// /// The pixel format. /// A new .> - public static Image Load(Stream stream, out string mimeType) + public static Image Load(Stream stream, out IImageFormat format) where TPixel : struct, IPixel { - return Load(null, stream, out mimeType); + return Load(null, stream, out format); } /// @@ -180,20 +180,20 @@ namespace ImageSharp /// /// The configuration options. /// The stream containing image information. - /// the mime type of the decoded image. + /// the mime type of the decoded image. /// /// Thrown if the stream is not readable nor seekable. /// /// The pixel format. /// A new .> - public static Image Load(Configuration config, Stream stream, out string mimeType) + public static Image Load(Configuration config, Stream stream, out IImageFormat format) where TPixel : struct, IPixel { config = config ?? Configuration.Default; - mimeType = null; - (Image img, string mimeType) data = WithSeekableStream(stream, s => Decode(s, config)); + format = null; + (Image img, IImageFormat format) data = WithSeekableStream(stream, s => Decode(s, config)); - mimeType = data.mimeType; + format = data.format; if (data.img != null) { @@ -203,9 +203,9 @@ namespace ImageSharp var stringBuilder = new StringBuilder(); stringBuilder.AppendLine("Image cannot be loaded. Available decoders:"); - foreach (KeyValuePair val in config.ImageDecoders) + foreach (KeyValuePair val in config.ImageDecoders) { - stringBuilder.AppendLine($" - {val.Key} : {val.Value.GetType().Name}"); + stringBuilder.AppendLine($" - {val.Key.Name} : {val.Value.GetType().Name}"); } throw new NotSupportedException(stringBuilder.ToString()); diff --git a/src/ImageSharp/Image/Image{TPixel}.cs b/src/ImageSharp/Image/Image{TPixel}.cs index 5165bc857c..11fea5145b 100644 --- a/src/ImageSharp/Image/Image{TPixel}.cs +++ b/src/ImageSharp/Image/Image{TPixel}.cs @@ -152,22 +152,22 @@ namespace ImageSharp /// Saves the image to the given stream using the currently loaded image format. /// /// The stream to save the image to. - /// The mime type to save the image to. + /// The format to save the image to. /// Thrown if the stream is null. /// The - public Image Save(Stream stream, string mimeType) + public Image Save(Stream stream, IImageFormat format) { - Guard.NotNullOrEmpty(mimeType, nameof(mimeType)); - IImageEncoder encoder = this.Configuration.FindMimeTypeEncoder(mimeType); + Guard.NotNull(format, nameof(format)); + IImageEncoder encoder = this.Configuration.FindEncoder(format); if (encoder == null) { var stringBuilder = new StringBuilder(); stringBuilder.AppendLine("Can't find encoder for provided mime type. Available encoded:"); - foreach (KeyValuePair val in this.Configuration.ImageEncoders) + foreach (KeyValuePair val in this.Configuration.ImageEncoders) { - stringBuilder.AppendLine($" - {val.Key} : {val.Value.GetType().Name}"); + stringBuilder.AppendLine($" - {val.Key.Name} : {val.Value.GetType().Name}"); } throw new NotSupportedException(stringBuilder.ToString()); @@ -207,26 +207,28 @@ namespace ImageSharp Guard.NotNullOrEmpty(filePath, nameof(filePath)); string ext = Path.GetExtension(filePath).Trim('.'); - IImageEncoder encoder = this.Configuration.FindFileExtensionsEncoder(ext); - if (encoder == null) + var format = this.Configuration.FindFormatByFileExtensions(ext); + if (format == null) { var stringBuilder = new StringBuilder(); - string mime = this.Configuration.FindFileExtensionsMimeType(ext); - if (mime == null) + stringBuilder.AppendLine($"Can't find a format that is associated with the file extention '{ext}'. Registered formats with there extensions include:"); + foreach (IImageFormat fmt in this.Configuration.ImageFormats) { - stringBuilder.AppendLine($"Can't find a mime type for the file extention '{ext}'. Registered file extension maps include:"); - foreach (KeyValuePair map in this.Configuration.ImageExtensionToMimeTypeMapping) - { - stringBuilder.AppendLine($" - {map.Key} : {map.Value}"); - } + stringBuilder.AppendLine($" - {fmt.Name} : {string.Join(", ", fmt.FileExtensions)}"); } - else + + throw new NotSupportedException(stringBuilder.ToString()); + } + + IImageEncoder encoder = this.Configuration.FindEncoder(format); + + if (encoder == null) + { + var stringBuilder = new StringBuilder(); + stringBuilder.AppendLine($"Can't find encoder for file extention '{ext}' using image format '{format.Name}'. Registered encoders include:"); + foreach (KeyValuePair enc in this.Configuration.ImageEncoders) { - stringBuilder.AppendLine($"Can't find encoder for file extention '{ext}' using mime type '{mime}'. Registered encoders include:"); - foreach (KeyValuePair enc in this.Configuration.ImageEncoders) - { - stringBuilder.AppendLine($" - {enc.Key} : {enc.Value.GetType().Name}"); - } + stringBuilder.AppendLine($" - {enc.Key} : {enc.Value.GetType().Name}"); } throw new NotSupportedException(stringBuilder.ToString()); @@ -262,15 +264,15 @@ namespace ImageSharp /// Returns a Base64 encoded string from the given image. /// /// - /// The mimeType. + /// The format. /// The - public string ToBase64String(string mimeType) + public string ToBase64String(IImageFormat format) { using (var stream = new MemoryStream()) { - this.Save(stream, mimeType); + this.Save(stream, format); stream.Flush(); - return $"data:{mimeType};base64,{Convert.ToBase64String(stream.ToArray())}"; + return $"data:{format.DefaultMimeType};base64,{Convert.ToBase64String(stream.ToArray())}"; } } diff --git a/src/ImageSharp/ImageFormats.cs b/src/ImageSharp/ImageFormats.cs new file mode 100644 index 0000000000..fb67fb7146 --- /dev/null +++ b/src/ImageSharp/ImageFormats.cs @@ -0,0 +1,39 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Collections.Generic; + using System.IO; + using ImageSharp.Formats; + using ImageSharp.PixelFormats; + + /// + /// The static collection of all the default image formats + /// + public static class ImageFormats + { + /// + /// The format details for the jpegs. + /// + public static readonly IImageFormat Jpeg = new JpegFormat(); + + /// + /// The format details for the pngs. + /// + public static readonly IImageFormat Png = new PngFormat(); + + /// + /// The format details for the gifs. + /// + public static readonly IImageFormat Gif = new GifFormat(); + + /// + /// The format details for the bitmaps. + /// + public static readonly IImageFormat Bitmap = new BmpFormat(); + } +} diff --git a/src/ImageSharp/Memory/Buffer.cs b/src/ImageSharp/Memory/Buffer.cs index a894ea53a4..4b3681c744 100644 --- a/src/ImageSharp/Memory/Buffer.cs +++ b/src/ImageSharp/Memory/Buffer.cs @@ -115,6 +115,16 @@ namespace ImageSharp.Memory } } + /// + /// Converts to an . + /// + /// The to convert. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ReadOnlySpan(Buffer buffer) + { + return new ReadOnlySpan(buffer.Array, 0, buffer.Length); + } + /// /// Converts to an . /// diff --git a/tests/ImageSharp.Tests/ConfigurationTests.cs b/tests/ImageSharp.Tests/ConfigurationTests.cs index aa09cf8166..417588180a 100644 --- a/tests/ImageSharp.Tests/ConfigurationTests.cs +++ b/tests/ImageSharp.Tests/ConfigurationTests.cs @@ -40,8 +40,8 @@ namespace ImageSharp.Tests [Fact] public void IfAutoloadWellknwonFormatesIsTrueAllFormateAreLoaded() { - Assert.Equal(6, DefaultConfiguration.ImageEncoders.Count()); - Assert.Equal(6, DefaultConfiguration.ImageDecoders.Count()); + Assert.Equal(4, DefaultConfiguration.ImageEncoders.Count()); + Assert.Equal(4, DefaultConfiguration.ImageDecoders.Count()); } /// @@ -73,11 +73,11 @@ namespace ImageSharp.Tests } [Fact] - public void AddMimeTypeDetectorNullthrows() + public void AddImageFormatDetectorNullthrows() { Assert.Throws(() => { - DefaultConfiguration.AddMimeTypeDetector(null); + DefaultConfiguration.AddImageFormatDetector(null); }); } @@ -86,49 +86,32 @@ namespace ImageSharp.Tests { Assert.Throws(() => { - DefaultConfiguration.SetMimeTypeEncoder(null, new Mock().Object); + DefaultConfiguration.SetEncoder(null, new Mock().Object); }); Assert.Throws(() => { - DefaultConfiguration.SetMimeTypeEncoder("sdsdsd", null); + DefaultConfiguration.SetEncoder(ImageFormats.Bitmap, null); }); Assert.Throws(() => { - DefaultConfiguration.SetMimeTypeEncoder(null, null); + DefaultConfiguration.SetEncoder(null, null); }); } [Fact] - public void RegisterNullFileExtEncoder() + public void RegisterNullSetDecoder() { Assert.Throws(() => { - DefaultConfiguration.SetFileExtensionToMimeTypeMapping(null, "str"); + DefaultConfiguration.SetDecoder(null, new Mock().Object); }); Assert.Throws(() => { - DefaultConfiguration.SetFileExtensionToMimeTypeMapping("sdsdsd", null); + DefaultConfiguration.SetDecoder(ImageFormats.Bitmap, null); }); Assert.Throws(() => { - DefaultConfiguration.SetFileExtensionToMimeTypeMapping(null, null); - }); - } - - [Fact] - public void RegisterNullMimeTypeDecoder() - { - Assert.Throws(() => - { - DefaultConfiguration.SetMimeTypeDecoder(null, new Mock().Object); - }); - Assert.Throws(() => - { - DefaultConfiguration.SetMimeTypeDecoder("sdsdsd", null); - }); - Assert.Throws(() => - { - DefaultConfiguration.SetMimeTypeDecoder(null, null); + DefaultConfiguration.SetDecoder(null, null); }); } @@ -136,28 +119,13 @@ namespace ImageSharp.Tests public void RegisterMimeTypeEncoderReplacesLast() { var encoder1 = new Mock().Object; - ConfigurationEmpty.SetMimeTypeEncoder("test", encoder1); - var found = ConfigurationEmpty.FindMimeTypeEncoder("TEST"); + ConfigurationEmpty.SetEncoder(TestFormat.GlobalTestFormat, encoder1); + var found = ConfigurationEmpty.FindEncoder(TestFormat.GlobalTestFormat); Assert.Equal(encoder1, found); var encoder2 = new Mock().Object; - ConfigurationEmpty.SetMimeTypeEncoder("TEST", encoder2); - var found2 = ConfigurationEmpty.FindMimeTypeEncoder("test"); - Assert.Equal(encoder2, found2); - Assert.NotEqual(found, found2); - } - - [Fact] - public void RegisterFileExtEnecoderReplacesLast() - { - var encoder1 = "mime1"; - ConfigurationEmpty.SetFileExtensionToMimeTypeMapping("TEST", encoder1); - var found = ConfigurationEmpty.FindFileExtensionsMimeType("test"); - Assert.Equal(encoder1, found); - - var encoder2 = "mime2"; - ConfigurationEmpty.SetFileExtensionToMimeTypeMapping("test", encoder2); - var found2 = ConfigurationEmpty.FindFileExtensionsMimeType("TEST"); + ConfigurationEmpty.SetEncoder(TestFormat.GlobalTestFormat, encoder2); + var found2 = ConfigurationEmpty.FindEncoder(TestFormat.GlobalTestFormat); Assert.Equal(encoder2, found2); Assert.NotEqual(found, found2); } @@ -166,13 +134,13 @@ namespace ImageSharp.Tests public void RegisterMimeTypeDecoderReplacesLast() { var decoder1 = new Mock().Object; - ConfigurationEmpty.SetMimeTypeDecoder("test", decoder1); - var found = ConfigurationEmpty.FindMimeTypeDecoder("TEST"); + ConfigurationEmpty.SetDecoder(TestFormat.GlobalTestFormat, decoder1); + var found = ConfigurationEmpty.FindDecoder(TestFormat.GlobalTestFormat); Assert.Equal(decoder1, found); var decoder2 = new Mock().Object; - ConfigurationEmpty.SetMimeTypeDecoder("TEST", decoder2); - var found2 = ConfigurationEmpty.FindMimeTypeDecoder("test"); + ConfigurationEmpty.SetDecoder(TestFormat.GlobalTestFormat, decoder2); + var found2 = ConfigurationEmpty.FindDecoder(TestFormat.GlobalTestFormat); Assert.Equal(decoder2, found2); Assert.NotEqual(found, found2); } @@ -181,7 +149,7 @@ namespace ImageSharp.Tests [Fact] public void ConstructorCallConfigureOnFormatProvider() { - var provider = new Mock(); + var provider = new Mock(); var config = new Configuration(provider.Object); provider.Verify(x => x.Configure(config)); @@ -190,9 +158,9 @@ namespace ImageSharp.Tests [Fact] public void AddFormatCallsConfig() { - var provider = new Mock(); + var provider = new Mock(); var config = new Configuration(); - config.AddImageFormat(provider.Object); + config.Configure(provider.Object); provider.Verify(x => x.Configure(config)); } diff --git a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs index 659aa317b8..4ef8fe0612 100644 --- a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs +++ b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs @@ -6,7 +6,7 @@ namespace ImageSharp.Tests { using System.IO; - + using ImageSharp.Formats; using ImageSharp.PixelFormats; using Xunit; @@ -36,7 +36,7 @@ namespace ImageSharp.Tests using (Image image = file.CreateImage()) { string filename = path + "/" + file.FileNameWithoutExtension + ".txt"; - File.WriteAllText(filename, image.ToBase64String("image/png")); + File.WriteAllText(filename, image.ToBase64String(ImageFormats.Png)); } } } @@ -135,7 +135,7 @@ namespace ImageSharp.Tests foreach (TestFile file in Files) { byte[] serialized; - using (Image image = Image.Load(file.Bytes, out string mimeType)) + using (Image image = Image.Load(file.Bytes, out IImageFormat mimeType)) using (MemoryStream memoryStream = new MemoryStream()) { image.Save(memoryStream, mimeType); diff --git a/tests/ImageSharp.Tests/Image/ImageDiscoverMimeType.cs b/tests/ImageSharp.Tests/Image/ImageDiscoverMimeType.cs index 80414662b6..59a39c4542 100644 --- a/tests/ImageSharp.Tests/Image/ImageDiscoverMimeType.cs +++ b/tests/ImageSharp.Tests/Image/ImageDiscoverMimeType.cs @@ -17,23 +17,27 @@ namespace ImageSharp.Tests /// /// Tests the class. /// - public class DiscoverMimeTypeTests + public class DiscoverImageFormatTests { private readonly Mock fileSystem; private readonly string FilePath; - private readonly Mock localMimeTypeDetector; + private readonly Mock localMimeTypeDetector; + private readonly Mock localImageFormatMock; + public IImageFormat localImageFormat => localImageFormatMock.Object; public Configuration LocalConfiguration { get; private set; } public byte[] Marker { get; private set; } public MemoryStream DataStream { get; private set; } public byte[] DecodedData { get; private set; } private const string localMimeType = "image/local"; - public DiscoverMimeTypeTests() + public DiscoverImageFormatTests() { - this.localMimeTypeDetector = new Mock(); + this.localImageFormatMock = new Mock(); + + this.localMimeTypeDetector = new Mock(); this.localMimeTypeDetector.Setup(x => x.HeaderSize).Returns(1); - this.localMimeTypeDetector.Setup(x => x.DetectMimeType(It.IsAny>())).Returns(localMimeType); + this.localMimeTypeDetector.Setup(x => x.DetectFormat(It.IsAny>())).Returns(localImageFormatMock.Object); this.fileSystem = new Mock(); @@ -41,7 +45,7 @@ namespace ImageSharp.Tests { FileSystem = this.fileSystem.Object }; - this.LocalConfiguration.AddMimeTypeDetector(this.localMimeTypeDetector.Object); + this.LocalConfiguration.AddImageFormatDetector(this.localMimeTypeDetector.Object); TestFormat.RegisterGloablTestFormat(); this.Marker = Guid.NewGuid().ToByteArray(); @@ -55,52 +59,52 @@ namespace ImageSharp.Tests } [Fact] - public void DiscoverMimeTypeByteArray() + public void DiscoverImageFormatByteArray() { - var type = Image.DiscoverMimeType(DataStream.ToArray()); - Assert.Equal(TestFormat.GlobalTestFormat.MimeType, type); + var type = Image.DetectFormat(DataStream.ToArray()); + Assert.Equal(TestFormat.GlobalTestFormat, type); } [Fact] - public void DiscoverMimeTypeByteArray_WithConfig() + public void DiscoverImageFormatByteArray_WithConfig() { - var type = Image.DiscoverMimeType(this.LocalConfiguration, DataStream.ToArray()); - Assert.Equal(localMimeType, type); + var type = Image.DetectFormat(this.LocalConfiguration, DataStream.ToArray()); + Assert.Equal(localImageFormat, type); } [Fact] - public void DiscoverMimeTypeFile() + public void DiscoverImageFormatFile() { - var type = Image.DiscoverMimeType(this.FilePath); - Assert.Equal(TestFormat.GlobalTestFormat.MimeType, type); + var type = Image.DetectFormat(this.FilePath); + Assert.Equal(TestFormat.GlobalTestFormat, type); } [Fact] - public void DiscoverMimeTypeFilePath_WithConfig() + public void DiscoverImageFormatFilePath_WithConfig() { - var type = Image.DiscoverMimeType(this.LocalConfiguration, FilePath); - Assert.Equal(localMimeType, type); + var type = Image.DetectFormat(this.LocalConfiguration, FilePath); + Assert.Equal(localImageFormat, type); } [Fact] - public void DiscoverMimeTypeStream() + public void DiscoverImageFormatStream() { - var type = Image.DiscoverMimeType(this.DataStream); - Assert.Equal(TestFormat.GlobalTestFormat.MimeType, type); + var type = Image.DetectFormat(this.DataStream); + Assert.Equal(TestFormat.GlobalTestFormat, type); } [Fact] - public void DiscoverMimeTypeFileStream_WithConfig() + public void DiscoverImageFormatFileStream_WithConfig() { - var type = Image.DiscoverMimeType(this.LocalConfiguration, DataStream); - Assert.Equal(localMimeType, type); + var type = Image.DetectFormat(this.LocalConfiguration, DataStream); + Assert.Equal(localImageFormat, type); } [Fact] - public void DiscoverMimeTypeNoDetectorsRegisterdShouldReturnNull() + public void DiscoverImageFormatNoDetectorsRegisterdShouldReturnNull() { - var type = Image.DiscoverMimeType(new Configuration(), DataStream); + var type = Image.DetectFormat(new Configuration(), DataStream); Assert.Null(type); } } diff --git a/tests/ImageSharp.Tests/Image/ImageLoadTests.cs b/tests/ImageSharp.Tests/Image/ImageLoadTests.cs index efd7b69350..bb64ceda34 100644 --- a/tests/ImageSharp.Tests/Image/ImageLoadTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageLoadTests.cs @@ -23,7 +23,8 @@ namespace ImageSharp.Tests private Image returnImage; private Mock localDecoder; private readonly string FilePath; - private readonly Mock localMimeTypeDetector; + private readonly Mock localMimeTypeDetector; + private readonly Mock localImageFormatMock; public Configuration LocalConfiguration { get; private set; } public byte[] Marker { get; private set; } @@ -34,10 +35,12 @@ namespace ImageSharp.Tests { this.returnImage = new Image(1, 1); + this.localImageFormatMock = new Mock(); + this.localDecoder = new Mock(); - this.localMimeTypeDetector = new Mock(); + this.localMimeTypeDetector = new Mock(); this.localMimeTypeDetector.Setup(x => x.HeaderSize).Returns(1); - this.localMimeTypeDetector.Setup(x => x.DetectMimeType(It.IsAny>())).Returns("test"); + this.localMimeTypeDetector.Setup(x => x.DetectFormat(It.IsAny>())).Returns(localImageFormatMock.Object); this.localDecoder.Setup(x => x.Decode(It.IsAny(), It.IsAny())) @@ -56,8 +59,8 @@ namespace ImageSharp.Tests { FileSystem = this.fileSystem.Object }; - this.LocalConfiguration.AddMimeTypeDetector(this.localMimeTypeDetector.Object); - this.LocalConfiguration.SetMimeTypeDecoder("test", this.localDecoder.Object); + this.LocalConfiguration.AddImageFormatDetector(this.localMimeTypeDetector.Object); + this.LocalConfiguration.SetDecoder(localImageFormatMock.Object, this.localDecoder.Object); TestFormat.RegisterGloablTestFormat(); this.Marker = Guid.NewGuid().ToByteArray(); @@ -78,7 +81,6 @@ namespace ImageSharp.Tests Assert.NotNull(img); TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, Configuration.Default); - } [Fact] diff --git a/tests/ImageSharp.Tests/Image/ImageSaveTests.cs b/tests/ImageSharp.Tests/Image/ImageSaveTests.cs index 72461cfc7c..eecb983800 100644 --- a/tests/ImageSharp.Tests/Image/ImageSaveTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageSaveTests.cs @@ -24,13 +24,17 @@ namespace ImageSharp.Tests private readonly Mock fileSystem; private readonly Mock encoder; private readonly Mock encoderNotInFormat; - private Mock localMimeTypeDetector; + private Mock localMimeTypeDetector; + private Mock localImageFormat; public ImageSaveTests() { - this.localMimeTypeDetector = new Mock(); + this.localImageFormat = new Mock(); + this.localImageFormat.Setup(x => x.FileExtensions).Returns(new[] { "png" }); + + this.localMimeTypeDetector = new Mock(); this.localMimeTypeDetector.Setup(x => x.HeaderSize).Returns(1); - this.localMimeTypeDetector.Setup(x => x.DetectMimeType(It.IsAny>())).Returns("img/test"); + this.localMimeTypeDetector.Setup(x => x.DetectFormat(It.IsAny>())).Returns(localImageFormat.Object); this.encoder = new Mock(); @@ -41,9 +45,8 @@ namespace ImageSharp.Tests { FileSystem = this.fileSystem.Object }; - config.AddMimeTypeDetector(this.localMimeTypeDetector.Object); - config.SetMimeTypeEncoder("img/test", this.encoder.Object); - config.SetFileExtensionToMimeTypeMapping("png", "img/test"); + config.AddImageFormatDetector(this.localMimeTypeDetector.Object); + config.SetEncoder(localImageFormat.Object, this.encoder.Object); this.Image = new Image(config, 1, 1); } @@ -72,7 +75,7 @@ namespace ImageSharp.Tests [Fact] public void ToBase64String() { - var str = this.Image.ToBase64String("img/test"); + var str = this.Image.ToBase64String(localImageFormat.Object); this.encoder.Verify(x => x.Encode(this.Image, It.IsAny())); } @@ -81,7 +84,7 @@ namespace ImageSharp.Tests public void SaveStreamWithMime() { Stream stream = new MemoryStream(); - this.Image.Save(stream, "img/test"); + this.Image.Save(stream, localImageFormat.Object); this.encoder.Verify(x => x.Encode(this.Image, stream)); } diff --git a/tests/ImageSharp.Tests/Image/ImageTests.cs b/tests/ImageSharp.Tests/Image/ImageTests.cs index cb8607c091..3157c27d8d 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.cs @@ -76,7 +76,7 @@ namespace ImageSharp.Tests using (Image img = Image.Load(file, out var mime)) { - Assert.Equal("image/png", mime); + Assert.Equal("image/png", mime.DefaultMimeType); } } @@ -105,7 +105,7 @@ namespace ImageSharp.Tests } using (Image img = Image.Load(file, out var mime)) { - Assert.Equal("image/png", mime); + Assert.Equal("image/png", mime.DefaultMimeType); } } } diff --git a/tests/ImageSharp.Tests/TestFormat.cs b/tests/ImageSharp.Tests/TestFormat.cs index 85e11ee876..5a3cd102e7 100644 --- a/tests/ImageSharp.Tests/TestFormat.cs +++ b/tests/ImageSharp.Tests/TestFormat.cs @@ -19,13 +19,13 @@ namespace ImageSharp.Tests /// /// A test image file. /// - public class TestFormat : IImageFormatProvider + public class TestFormat : IConfigurationModule, IImageFormat { public static TestFormat GlobalTestFormat { get; } = new TestFormat(); public static void RegisterGloablTestFormat() { - Configuration.Default.AddImageFormat(GlobalTestFormat); + Configuration.Default.Configure(GlobalTestFormat); } public TestFormat() @@ -93,7 +93,15 @@ namespace ImageSharp.Tests public int HeaderSize => this.header.Length; - public bool IsSupportedFileFormat(Span header) + public string Name => this.Extension; + + public string DefaultMimeType => this.MimeType; + + public IEnumerable MimeTypes => new[] { this.MimeType }; + + public IEnumerable FileExtensions => this.SupportedExtensions; + + public bool IsSupportedFileFormat(ReadOnlySpan header) { if (header.Length < this.header.Length) { @@ -109,16 +117,11 @@ namespace ImageSharp.Tests return true; } - public void Configure(IImageFormatHost host) + public void Configure(Configuration host) { - host.AddMimeTypeDetector(new TestHeader(this)); - foreach (var ext in this.SupportedExtensions) - { - host.SetFileExtensionToMimeTypeMapping(ext, this.MimeType); - } - - host.SetMimeTypeEncoder(this.MimeType, new TestEncoder(this)); - host.SetMimeTypeDecoder(this.MimeType, new TestDecoder(this)); + host.AddImageFormatDetector(new TestHeader(this)); + host.SetEncoder(this, new TestEncoder(this)); + host.SetDecoder(this, new TestDecoder(this)); } public struct DecodeOperation @@ -150,17 +153,17 @@ namespace ImageSharp.Tests } } - public class TestHeader : IMimeTypeDetector + public class TestHeader : IImageFormatDetector { private TestFormat testFormat; public int HeaderSize => testFormat.HeaderSize; - public string DetectMimeType(Span header) + public IImageFormat DetectFormat(ReadOnlySpan header) { if (testFormat.IsSupportedFileFormat(header)) - return testFormat.MimeType; + return testFormat; return null; } diff --git a/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs b/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs index 21b167ca20..47085cf5f4 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs @@ -124,13 +124,13 @@ namespace ImageSharp.Tests private static IImageEncoder GetImageFormatByExtension(string extension) { extension = extension?.TrimStart('.'); - return Configuration.Default.FindFileExtensionsEncoder(extension); + var format = Configuration.Default.FindFormatByFileExtensions(extension); + return Configuration.Default.FindEncoder(format); } private string GetTestOutputDir() { string testGroupName = Path.GetFileNameWithoutExtension(this.TestGroupName); - return CreateOutputDirectory(testGroupName); } } From b61cae24d6281eec68e871877b4d77a871adb5ec Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Tue, 27 Jun 2017 08:12:26 +0100 Subject: [PATCH 10/36] fix paramater names --- src/ImageSharp/Configuration.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/ImageSharp/Configuration.cs b/src/ImageSharp/Configuration.cs index 90fd3a0e66..4c9ba6a411 100644 --- a/src/ImageSharp/Configuration.cs +++ b/src/ImageSharp/Configuration.cs @@ -18,7 +18,7 @@ namespace ImageSharp /// /// Provides initialization code which allows extending the library. /// - public class Configuration + public sealed partial class Configuration { /// /// A lazily initialized configuration default instance. @@ -60,12 +60,12 @@ namespace ImageSharp /// /// Initializes a new instance of the class. /// - /// A collection of providers to configure - public Configuration(params IConfigurationModule[] providers) + /// A collection of configuration modules to register + public Configuration(params IConfigurationModule[] configurationModules) { - if (providers != null) + if (configurationModules != null) { - foreach (IConfigurationModule p in providers) + foreach (IConfigurationModule p in configurationModules) { p.Configure(this); } @@ -183,9 +183,9 @@ namespace ImageSharp } /// - /// Removes all the registered mime type detectors. + /// Removes all the registered image format detectors. /// - public void ClearMimeTypeDetectors() + public void ClearImageFormatDetectors() { this.imageFormatDetectors.Clear(); } From fe346134feae82fe2531529c87f0dff8e990ffef Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 28 Jun 2017 10:14:09 +1000 Subject: [PATCH 11/36] Minor cleanup --- src/ImageSharp/Configuration.cs | 10 ++-------- src/ImageSharp/Formats/Bmp/BmpConfigurationModule.cs | 10 ++-------- src/ImageSharp/Formats/Bmp/BmpFormat.cs | 6 +----- src/ImageSharp/Formats/Bmp/BmpImageFormatDetector.cs | 9 +++------ src/ImageSharp/Formats/Gif/GifConfigurationModule.cs | 10 ++-------- src/ImageSharp/Formats/Gif/GifConstants.cs | 2 +- src/ImageSharp/Formats/Gif/GifFormat.cs | 6 +----- src/ImageSharp/Formats/Gif/GifImageFormatDetector.cs | 9 +++------ src/ImageSharp/Formats/IImageFormat.cs | 6 +----- .../Formats/Jpeg/JpegConfigurationModule.cs | 8 +------- src/ImageSharp/Formats/Jpeg/JpegFormat.cs | 6 +----- .../Formats/Jpeg/JpegImageFormatDetector.cs | 11 +++++------ src/ImageSharp/Formats/Png/PngConfigurationModule.cs | 8 +------- src/ImageSharp/Formats/Png/PngConstants.cs | 3 ++- src/ImageSharp/Formats/Png/PngFormat.cs | 6 +----- src/ImageSharp/Formats/Png/PngImageFormatDetector.cs | 7 ++----- src/ImageSharp/IConfigurationModule.cs | 4 ---- src/ImageSharp/Image/Image.Decode.cs | 1 - src/ImageSharp/Image/Image.FromStream.cs | 2 -- src/ImageSharp/Image/Image{TPixel}.cs | 1 - src/ImageSharp/ImageFormats.cs | 6 +----- 21 files changed, 30 insertions(+), 101 deletions(-) diff --git a/src/ImageSharp/Configuration.cs b/src/ImageSharp/Configuration.cs index 4c9ba6a411..226d451325 100644 --- a/src/ImageSharp/Configuration.cs +++ b/src/ImageSharp/Configuration.cs @@ -8,7 +8,6 @@ namespace ImageSharp using System; using System.Collections.Concurrent; using System.Collections.Generic; - using System.Collections.ObjectModel; using System.Linq; using System.Threading.Tasks; @@ -18,17 +17,12 @@ namespace ImageSharp /// /// Provides initialization code which allows extending the library. /// - public sealed partial class Configuration + public sealed class Configuration { /// /// A lazily initialized configuration default instance. /// - private static readonly Lazy Lazy = new Lazy(() => CreateDefaultInstance()); - - /// - /// An object that can be used to synchronize access to the . - /// - private readonly object syncRoot = new object(); + private static readonly Lazy Lazy = new Lazy(CreateDefaultInstance); /// /// The list of supported keyed to mime types. diff --git a/src/ImageSharp/Formats/Bmp/BmpConfigurationModule.cs b/src/ImageSharp/Formats/Bmp/BmpConfigurationModule.cs index 2553f14542..4534f26361 100644 --- a/src/ImageSharp/Formats/Bmp/BmpConfigurationModule.cs +++ b/src/ImageSharp/Formats/Bmp/BmpConfigurationModule.cs @@ -1,16 +1,10 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // namespace ImageSharp.Formats { - using System; - using System.Collections.Generic; - using System.IO; - using System.Text; - using ImageSharp.PixelFormats; - /// /// Registers the image encoders, decoders and mime type detectors for the bmp format. /// @@ -24,4 +18,4 @@ namespace ImageSharp.Formats config.AddImageFormatDetector(new BmpImageFormatDetector()); } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Bmp/BmpFormat.cs b/src/ImageSharp/Formats/Bmp/BmpFormat.cs index 4f32e2875e..fb65f34d7d 100644 --- a/src/ImageSharp/Formats/Bmp/BmpFormat.cs +++ b/src/ImageSharp/Formats/Bmp/BmpFormat.cs @@ -5,11 +5,7 @@ namespace ImageSharp.Formats { - using System; using System.Collections.Generic; - using System.IO; - using System.Text; - using ImageSharp.PixelFormats; /// /// Registers the image encoders, decoders and mime type detectors for the jpeg format. @@ -28,4 +24,4 @@ namespace ImageSharp.Formats /// public IEnumerable FileExtensions => BmpConstants.FileExtensions; } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Bmp/BmpImageFormatDetector.cs b/src/ImageSharp/Formats/Bmp/BmpImageFormatDetector.cs index 4f1054b097..96ab92f25c 100644 --- a/src/ImageSharp/Formats/Bmp/BmpImageFormatDetector.cs +++ b/src/ImageSharp/Formats/Bmp/BmpImageFormatDetector.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // @@ -6,10 +6,6 @@ namespace ImageSharp.Formats { using System; - using System.Collections.Generic; - using System.IO; - using System.Text; - using ImageSharp.PixelFormats; /// /// Detects bmp file headers @@ -32,9 +28,10 @@ namespace ImageSharp.Formats private bool IsSupportedFileFormat(ReadOnlySpan header) { + // TODO: This should be in constants return header.Length >= this.HeaderSize && header[0] == 0x42 && // B header[1] == 0x4D; // M } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Gif/GifConfigurationModule.cs b/src/ImageSharp/Formats/Gif/GifConfigurationModule.cs index 0640cb2347..4e810ffad1 100644 --- a/src/ImageSharp/Formats/Gif/GifConfigurationModule.cs +++ b/src/ImageSharp/Formats/Gif/GifConfigurationModule.cs @@ -1,16 +1,10 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // namespace ImageSharp.Formats { - using System; - using System.Collections.Generic; - using System.IO; - using System.Text; - using ImageSharp.PixelFormats; - /// /// Registers the image encoders, decoders and mime type detectors for the gif format. /// @@ -25,4 +19,4 @@ namespace ImageSharp.Formats config.AddImageFormatDetector(new GifImageFormatDetector()); } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Gif/GifConstants.cs b/src/ImageSharp/Formats/Gif/GifConstants.cs index 7b215b773b..9bec6c48f9 100644 --- a/src/ImageSharp/Formats/Gif/GifConstants.cs +++ b/src/ImageSharp/Formats/Gif/GifConstants.cs @@ -103,4 +103,4 @@ namespace ImageSharp.Formats /// public static readonly IEnumerable FileExtensions = new[] { "gif" }; } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Gif/GifFormat.cs b/src/ImageSharp/Formats/Gif/GifFormat.cs index 5aa667ea91..ea7b72d327 100644 --- a/src/ImageSharp/Formats/Gif/GifFormat.cs +++ b/src/ImageSharp/Formats/Gif/GifFormat.cs @@ -5,11 +5,7 @@ namespace ImageSharp.Formats { - using System; using System.Collections.Generic; - using System.IO; - using System.Text; - using ImageSharp.PixelFormats; /// /// Registers the image encoders, decoders and mime type detectors for the jpeg format. @@ -28,4 +24,4 @@ namespace ImageSharp.Formats /// public IEnumerable FileExtensions => GifConstants.FileExtensions; } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Gif/GifImageFormatDetector.cs b/src/ImageSharp/Formats/Gif/GifImageFormatDetector.cs index 2aee867953..5a6dfe1988 100644 --- a/src/ImageSharp/Formats/Gif/GifImageFormatDetector.cs +++ b/src/ImageSharp/Formats/Gif/GifImageFormatDetector.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // @@ -6,10 +6,6 @@ namespace ImageSharp.Formats { using System; - using System.Collections.Generic; - using System.IO; - using System.Text; - using ImageSharp.PixelFormats; /// /// Detects gif file headers @@ -32,6 +28,7 @@ namespace ImageSharp.Formats private bool IsSupportedFileFormat(ReadOnlySpan header) { + // TODO: This should be in constants return header.Length >= this.HeaderSize && header[0] == 0x47 && // G header[1] == 0x49 && // I @@ -41,4 +38,4 @@ namespace ImageSharp.Formats header[5] == 0x61; // a } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/IImageFormat.cs b/src/ImageSharp/Formats/IImageFormat.cs index 8643e2f4c9..d6ddc0b0bb 100644 --- a/src/ImageSharp/Formats/IImageFormat.cs +++ b/src/ImageSharp/Formats/IImageFormat.cs @@ -5,11 +5,7 @@ namespace ImageSharp.Formats { - using System; using System.Collections.Generic; - using System.IO; - - using ImageSharp.PixelFormats; /// /// Describes an image format. @@ -36,4 +32,4 @@ namespace ImageSharp.Formats /// IEnumerable FileExtensions { get; } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/JpegConfigurationModule.cs b/src/ImageSharp/Formats/Jpeg/JpegConfigurationModule.cs index 64c7af3579..8fe4a35a0a 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegConfigurationModule.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegConfigurationModule.cs @@ -1,16 +1,10 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // namespace ImageSharp.Formats { - using System; - using System.Collections.Generic; - using System.IO; - using System.Text; - using ImageSharp.PixelFormats; - /// /// Registers the image encoders, decoders and mime type detectors for the jpeg format. /// diff --git a/src/ImageSharp/Formats/Jpeg/JpegFormat.cs b/src/ImageSharp/Formats/Jpeg/JpegFormat.cs index 86b624fc85..23cd5d8752 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegFormat.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegFormat.cs @@ -5,11 +5,7 @@ namespace ImageSharp.Formats { - using System; using System.Collections.Generic; - using System.IO; - using System.Text; - using ImageSharp.PixelFormats; /// /// Registers the image encoders, decoders and mime type detectors for the jpeg format. @@ -28,4 +24,4 @@ namespace ImageSharp.Formats /// public IEnumerable FileExtensions => JpegConstants.FileExtensions; } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/JpegImageFormatDetector.cs b/src/ImageSharp/Formats/Jpeg/JpegImageFormatDetector.cs index 82f810625f..8d6923c8de 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegImageFormatDetector.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegImageFormatDetector.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // @@ -6,10 +6,6 @@ namespace ImageSharp.Formats { using System; - using System.Collections.Generic; - using System.IO; - using System.Text; - using ImageSharp.PixelFormats; /// /// Detects Jpeg file headers @@ -43,6 +39,7 @@ namespace ImageSharp.Formats /// The private bool IsJfif(ReadOnlySpan header) { + // TODO: This should be in constants bool isJfif = header[6] == 0x4A && // J header[7] == 0x46 && // F @@ -60,6 +57,7 @@ namespace ImageSharp.Formats /// The private bool IsExif(ReadOnlySpan header) { + // TODO: This should be in constants bool isExif = header[6] == 0x45 && // E header[7] == 0x78 && // X @@ -78,6 +76,7 @@ namespace ImageSharp.Formats /// The private bool IsJpeg(ReadOnlySpan header) { + // TODO: This should be in constants bool isJpg = header[0] == 0xFF && // 255 header[1] == 0xD8; // 216 @@ -85,4 +84,4 @@ namespace ImageSharp.Formats return isJpg; } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Png/PngConfigurationModule.cs b/src/ImageSharp/Formats/Png/PngConfigurationModule.cs index 47fe8b136a..a3813c5003 100644 --- a/src/ImageSharp/Formats/Png/PngConfigurationModule.cs +++ b/src/ImageSharp/Formats/Png/PngConfigurationModule.cs @@ -5,12 +5,6 @@ namespace ImageSharp.Formats { - using System; - using System.Collections.Generic; - using System.IO; - using System.Text; - using ImageSharp.PixelFormats; - /// /// Registers the image encoders, decoders and mime type detectors for the png format. /// @@ -24,4 +18,4 @@ namespace ImageSharp.Formats host.AddImageFormatDetector(new PngImageFormatDetector()); } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Png/PngConstants.cs b/src/ImageSharp/Formats/Png/PngConstants.cs index 8c10ad9601..84e76a3419 100644 --- a/src/ImageSharp/Formats/Png/PngConstants.cs +++ b/src/ImageSharp/Formats/Png/PngConstants.cs @@ -2,6 +2,7 @@ // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // + namespace ImageSharp.Formats { using System.Collections.Generic; @@ -27,4 +28,4 @@ namespace ImageSharp.Formats /// public static readonly IEnumerable FileExtensions = new[] { "png" }; } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Png/PngFormat.cs b/src/ImageSharp/Formats/Png/PngFormat.cs index 60ba306056..551b4a8c78 100644 --- a/src/ImageSharp/Formats/Png/PngFormat.cs +++ b/src/ImageSharp/Formats/Png/PngFormat.cs @@ -5,11 +5,7 @@ namespace ImageSharp.Formats { - using System; using System.Collections.Generic; - using System.IO; - using System.Text; - using ImageSharp.PixelFormats; /// /// Registers the image encoders, decoders and mime type detectors for the jpeg format. @@ -28,4 +24,4 @@ namespace ImageSharp.Formats /// public IEnumerable FileExtensions => PngConstants.FileExtensions; } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Png/PngImageFormatDetector.cs b/src/ImageSharp/Formats/Png/PngImageFormatDetector.cs index 6183ef4496..a9327983be 100644 --- a/src/ImageSharp/Formats/Png/PngImageFormatDetector.cs +++ b/src/ImageSharp/Formats/Png/PngImageFormatDetector.cs @@ -6,10 +6,6 @@ namespace ImageSharp.Formats { using System; - using System.Collections.Generic; - using System.IO; - using System.Text; - using ImageSharp.PixelFormats; /// /// Detects png file headers @@ -32,6 +28,7 @@ namespace ImageSharp.Formats private bool IsSupportedFileFormat(ReadOnlySpan header) { + // TODO: This should be in constants return header.Length >= this.HeaderSize && header[0] == 0x89 && header[1] == 0x50 && // P @@ -43,4 +40,4 @@ namespace ImageSharp.Formats header[7] == 0x0A; // LF } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/IConfigurationModule.cs b/src/ImageSharp/IConfigurationModule.cs index d37e0042b5..95b92ed056 100644 --- a/src/ImageSharp/IConfigurationModule.cs +++ b/src/ImageSharp/IConfigurationModule.cs @@ -5,10 +5,6 @@ namespace ImageSharp { - using System; - using System.Collections.Generic; - using System.Text; - /// /// Represents an interface that can register image encoders, decoders and image format detectors. /// diff --git a/src/ImageSharp/Image/Image.Decode.cs b/src/ImageSharp/Image/Image.Decode.cs index 2408ec3aa2..1013107062 100644 --- a/src/ImageSharp/Image/Image.Decode.cs +++ b/src/ImageSharp/Image/Image.Decode.cs @@ -5,7 +5,6 @@ namespace ImageSharp { - using System.Buffers; using System.IO; using System.Linq; using Formats; diff --git a/src/ImageSharp/Image/Image.FromStream.cs b/src/ImageSharp/Image/Image.FromStream.cs index 7493d66fcc..29d93ae859 100644 --- a/src/ImageSharp/Image/Image.FromStream.cs +++ b/src/ImageSharp/Image/Image.FromStream.cs @@ -8,7 +8,6 @@ namespace ImageSharp using System; using System.Collections.Generic; using System.IO; - using System.Linq; using System.Text; using Formats; @@ -190,7 +189,6 @@ namespace ImageSharp where TPixel : struct, IPixel { config = config ?? Configuration.Default; - format = null; (Image img, IImageFormat format) data = WithSeekableStream(stream, s => Decode(s, config)); format = data.format; diff --git a/src/ImageSharp/Image/Image{TPixel}.cs b/src/ImageSharp/Image/Image{TPixel}.cs index 9af7883271..5e8bcab31b 100644 --- a/src/ImageSharp/Image/Image{TPixel}.cs +++ b/src/ImageSharp/Image/Image{TPixel}.cs @@ -9,7 +9,6 @@ namespace ImageSharp using System.Collections.Generic; using System.Diagnostics; using System.IO; - using System.Linq; using System.Numerics; using System.Text; using System.Threading.Tasks; diff --git a/src/ImageSharp/ImageFormats.cs b/src/ImageSharp/ImageFormats.cs index fb67fb7146..f79191eae6 100644 --- a/src/ImageSharp/ImageFormats.cs +++ b/src/ImageSharp/ImageFormats.cs @@ -5,11 +5,7 @@ namespace ImageSharp { - using System; - using System.Collections.Generic; - using System.IO; using ImageSharp.Formats; - using ImageSharp.PixelFormats; /// /// The static collection of all the default image formats @@ -36,4 +32,4 @@ namespace ImageSharp /// public static readonly IImageFormat Bitmap = new BmpFormat(); } -} +} \ No newline at end of file From 465c7368411112ac59aec48cc0114158ba517d7b Mon Sep 17 00:00:00 2001 From: Dirk Lemstra Date: Sat, 1 Jul 2017 14:30:34 +0200 Subject: [PATCH 12/36] Made the default IImageFormatDetectors public and sealed. --- src/ImageSharp/Formats/Bmp/BmpImageFormatDetector.cs | 2 +- src/ImageSharp/Formats/Gif/GifImageFormatDetector.cs | 2 +- src/ImageSharp/Formats/Jpeg/JpegImageFormatDetector.cs | 2 +- src/ImageSharp/Formats/Png/PngImageFormatDetector.cs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ImageSharp/Formats/Bmp/BmpImageFormatDetector.cs b/src/ImageSharp/Formats/Bmp/BmpImageFormatDetector.cs index 96ab92f25c..697ee0f981 100644 --- a/src/ImageSharp/Formats/Bmp/BmpImageFormatDetector.cs +++ b/src/ImageSharp/Formats/Bmp/BmpImageFormatDetector.cs @@ -10,7 +10,7 @@ namespace ImageSharp.Formats /// /// Detects bmp file headers /// - internal class BmpImageFormatDetector : IImageFormatDetector + public sealed class BmpImageFormatDetector : IImageFormatDetector { /// public int HeaderSize => 2; diff --git a/src/ImageSharp/Formats/Gif/GifImageFormatDetector.cs b/src/ImageSharp/Formats/Gif/GifImageFormatDetector.cs index 5a6dfe1988..04fcfc516c 100644 --- a/src/ImageSharp/Formats/Gif/GifImageFormatDetector.cs +++ b/src/ImageSharp/Formats/Gif/GifImageFormatDetector.cs @@ -10,7 +10,7 @@ namespace ImageSharp.Formats /// /// Detects gif file headers /// - public class GifImageFormatDetector : IImageFormatDetector + public sealed class GifImageFormatDetector : IImageFormatDetector { /// public int HeaderSize => 6; diff --git a/src/ImageSharp/Formats/Jpeg/JpegImageFormatDetector.cs b/src/ImageSharp/Formats/Jpeg/JpegImageFormatDetector.cs index 8d6923c8de..b72b290c04 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegImageFormatDetector.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegImageFormatDetector.cs @@ -10,7 +10,7 @@ namespace ImageSharp.Formats /// /// Detects Jpeg file headers /// - public class JpegImageFormatDetector : IImageFormatDetector + public sealed class JpegImageFormatDetector : IImageFormatDetector { /// public int HeaderSize => 11; diff --git a/src/ImageSharp/Formats/Png/PngImageFormatDetector.cs b/src/ImageSharp/Formats/Png/PngImageFormatDetector.cs index a9327983be..fdea3eb8ae 100644 --- a/src/ImageSharp/Formats/Png/PngImageFormatDetector.cs +++ b/src/ImageSharp/Formats/Png/PngImageFormatDetector.cs @@ -10,7 +10,7 @@ namespace ImageSharp.Formats /// /// Detects png file headers /// - public class PngImageFormatDetector : IImageFormatDetector + public sealed class PngImageFormatDetector : IImageFormatDetector { /// public int HeaderSize => 8; From 5ce77d127c6302da62b08362b57e325429f12624 Mon Sep 17 00:00:00 2001 From: Dirk Lemstra Date: Sat, 1 Jul 2017 14:56:21 +0200 Subject: [PATCH 13/36] Sealed some classes and made some classes internal. --- src/ImageSharp/Common/Exceptions/ImageFormatException.cs | 2 +- src/ImageSharp/Common/Exceptions/ImageProcessingException.cs | 2 +- src/ImageSharp/Formats/Bmp/BmpConfigurationModule.cs | 2 +- src/ImageSharp/Formats/Bmp/BmpDecoder.cs | 2 +- src/ImageSharp/Formats/Bmp/BmpEncoder.cs | 2 +- src/ImageSharp/Formats/Bmp/BmpFileHeader.cs | 2 +- src/ImageSharp/Formats/Bmp/BmpInfoHeader.cs | 2 +- src/ImageSharp/Formats/Gif/GifConfigurationModule.cs | 2 +- src/ImageSharp/Formats/Gif/GifDecoder.cs | 2 +- src/ImageSharp/Formats/Gif/GifDecoderCore.cs | 2 +- src/ImageSharp/Formats/Gif/GifEncoder.cs | 2 +- src/ImageSharp/Formats/Jpeg/JpegConfigurationModule.cs | 2 +- src/ImageSharp/Formats/Jpeg/JpegDecoder.cs | 2 +- src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs | 2 +- src/ImageSharp/Formats/Jpeg/JpegEncoder.cs | 2 +- src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs | 2 +- src/ImageSharp/Formats/Png/PngConfigurationModule.cs | 2 +- src/ImageSharp/Formats/Png/PngDecoder.cs | 2 +- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 2 +- src/ImageSharp/Formats/Png/PngEncoder.cs | 2 +- src/ImageSharp/Formats/Png/PngHeader.cs | 2 +- src/ImageSharp/Formats/Png/PngInterlaceMode.cs | 2 +- src/ImageSharp/Formats/Png/Zlib/IChecksum.cs | 2 +- src/ImageSharp/Formats/Png/Zlib/ZlibInflateStream.cs | 2 +- 24 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/ImageSharp/Common/Exceptions/ImageFormatException.cs b/src/ImageSharp/Common/Exceptions/ImageFormatException.cs index 70491ba22e..32a0854359 100644 --- a/src/ImageSharp/Common/Exceptions/ImageFormatException.cs +++ b/src/ImageSharp/Common/Exceptions/ImageFormatException.cs @@ -11,7 +11,7 @@ namespace ImageSharp /// The exception that is thrown when the library tries to load /// an image, which has an invalid format. /// - public class ImageFormatException : Exception + public sealed class ImageFormatException : Exception { /// /// Initializes a new instance of the class. diff --git a/src/ImageSharp/Common/Exceptions/ImageProcessingException.cs b/src/ImageSharp/Common/Exceptions/ImageProcessingException.cs index a59be9ca8f..ef84a1e393 100644 --- a/src/ImageSharp/Common/Exceptions/ImageProcessingException.cs +++ b/src/ImageSharp/Common/Exceptions/ImageProcessingException.cs @@ -10,7 +10,7 @@ namespace ImageSharp /// /// The exception that is thrown when an error occurs when applying a process to an image. /// - public class ImageProcessingException : Exception + public sealed class ImageProcessingException : Exception { /// /// Initializes a new instance of the class. diff --git a/src/ImageSharp/Formats/Bmp/BmpConfigurationModule.cs b/src/ImageSharp/Formats/Bmp/BmpConfigurationModule.cs index 4534f26361..f70ff1a56d 100644 --- a/src/ImageSharp/Formats/Bmp/BmpConfigurationModule.cs +++ b/src/ImageSharp/Formats/Bmp/BmpConfigurationModule.cs @@ -8,7 +8,7 @@ namespace ImageSharp.Formats /// /// Registers the image encoders, decoders and mime type detectors for the bmp format. /// - public class BmpConfigurationModule : IConfigurationModule + public sealed class BmpConfigurationModule : IConfigurationModule { /// public void Configure(Configuration config) diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoder.cs b/src/ImageSharp/Formats/Bmp/BmpDecoder.cs index 66af2fa75a..5baf1b1a5a 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoder.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoder.cs @@ -26,7 +26,7 @@ namespace ImageSharp.Formats /// Formats will be supported in a later releases. We advise always /// to use only 24 Bit Windows bitmaps. /// - public class BmpDecoder : IImageDecoder, IBmpDecoderOptions + public sealed class BmpDecoder : IImageDecoder, IBmpDecoderOptions { /// public Image Decode(Configuration configuration, Stream stream) diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoder.cs b/src/ImageSharp/Formats/Bmp/BmpEncoder.cs index b0064a5086..dfba0b41c0 100644 --- a/src/ImageSharp/Formats/Bmp/BmpEncoder.cs +++ b/src/ImageSharp/Formats/Bmp/BmpEncoder.cs @@ -15,7 +15,7 @@ namespace ImageSharp.Formats /// Image encoder for writing an image to a stream as a Windows bitmap. /// /// The encoder can currently only write 24-bit rgb images to streams. - public class BmpEncoder : IImageEncoder, IBmpEncoderOptions + public sealed class BmpEncoder : IImageEncoder, IBmpEncoderOptions { /// /// Gets or sets the number of bits per pixel. diff --git a/src/ImageSharp/Formats/Bmp/BmpFileHeader.cs b/src/ImageSharp/Formats/Bmp/BmpFileHeader.cs index 4be602f4b1..f9b20a48f8 100644 --- a/src/ImageSharp/Formats/Bmp/BmpFileHeader.cs +++ b/src/ImageSharp/Formats/Bmp/BmpFileHeader.cs @@ -15,7 +15,7 @@ namespace ImageSharp.Formats /// All of the other integer values are stored in little-endian format /// (i.e. least-significant byte first). /// - internal class BmpFileHeader + internal sealed class BmpFileHeader { /// /// Defines of the data structure in the bitmap file. diff --git a/src/ImageSharp/Formats/Bmp/BmpInfoHeader.cs b/src/ImageSharp/Formats/Bmp/BmpInfoHeader.cs index e652cb5044..dc6a489d34 100644 --- a/src/ImageSharp/Formats/Bmp/BmpInfoHeader.cs +++ b/src/ImageSharp/Formats/Bmp/BmpInfoHeader.cs @@ -10,7 +10,7 @@ namespace ImageSharp.Formats /// the screen. /// /// - internal class BmpInfoHeader + internal sealed class BmpInfoHeader { /// /// Defines of the data structure in the bitmap file. diff --git a/src/ImageSharp/Formats/Gif/GifConfigurationModule.cs b/src/ImageSharp/Formats/Gif/GifConfigurationModule.cs index 4e810ffad1..ee134d66cd 100644 --- a/src/ImageSharp/Formats/Gif/GifConfigurationModule.cs +++ b/src/ImageSharp/Formats/Gif/GifConfigurationModule.cs @@ -8,7 +8,7 @@ namespace ImageSharp.Formats /// /// Registers the image encoders, decoders and mime type detectors for the gif format. /// - public class GifConfigurationModule : IConfigurationModule + public sealed class GifConfigurationModule : IConfigurationModule { /// public void Configure(Configuration config) diff --git a/src/ImageSharp/Formats/Gif/GifDecoder.cs b/src/ImageSharp/Formats/Gif/GifDecoder.cs index e026cc29bc..927289094f 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoder.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoder.cs @@ -14,7 +14,7 @@ namespace ImageSharp.Formats /// /// Decoder for generating an image out of a gif encoded stream. /// - public class GifDecoder : IImageDecoder, IGifDecoderOptions + public sealed class GifDecoder : IImageDecoder, IGifDecoderOptions { /// /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded. diff --git a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs index 22e5e475b2..bb230beac7 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs @@ -18,7 +18,7 @@ namespace ImageSharp.Formats /// Performs the gif decoding operation. /// /// The pixel format. - internal class GifDecoderCore + internal sealed class GifDecoderCore where TPixel : struct, IPixel { /// diff --git a/src/ImageSharp/Formats/Gif/GifEncoder.cs b/src/ImageSharp/Formats/Gif/GifEncoder.cs index b725f12603..b48db56356 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoder.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoder.cs @@ -15,7 +15,7 @@ namespace ImageSharp.Formats /// /// Image encoder for writing image data to a stream in gif format. /// - public class GifEncoder : IImageEncoder, IGifEncoderOptions + public sealed class GifEncoder : IImageEncoder, IGifEncoderOptions { /// /// Gets or sets a value indicating whether the metadata should be ignored when the image is being encoded. diff --git a/src/ImageSharp/Formats/Jpeg/JpegConfigurationModule.cs b/src/ImageSharp/Formats/Jpeg/JpegConfigurationModule.cs index 8fe4a35a0a..bb8c4e83f2 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegConfigurationModule.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegConfigurationModule.cs @@ -8,7 +8,7 @@ namespace ImageSharp.Formats /// /// Registers the image encoders, decoders and mime type detectors for the jpeg format. /// - public class JpegConfigurationModule : IConfigurationModule + public sealed class JpegConfigurationModule : IConfigurationModule { /// public void Configure(Configuration config) diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs index 0dc186697e..b3caddeca7 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs @@ -14,7 +14,7 @@ namespace ImageSharp.Formats /// /// Image decoder for generating an image out of a jpg stream. /// - public class JpegDecoder : IImageDecoder, IJpegDecoderOptions + public sealed class JpegDecoder : IImageDecoder, IJpegDecoderOptions { /// /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded. diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 1ce78a8b1f..0ce927e516 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -18,7 +18,7 @@ namespace ImageSharp.Formats /// /// Performs the jpeg decoding operation. /// - internal unsafe class JpegDecoderCore : IDisposable + internal sealed unsafe class JpegDecoderCore : IDisposable { /// /// The maximum number of color components diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs index 350856471c..6c6561468f 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs @@ -14,7 +14,7 @@ namespace ImageSharp.Formats /// /// Encoder for writing the data image to a stream in jpeg format. /// - public class JpegEncoder : IImageEncoder, IJpegEncoderOptions + public sealed class JpegEncoder : IImageEncoder, IJpegEncoderOptions { /// /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded. diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs index 2d67f67353..d2b7d2d7c4 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs @@ -17,7 +17,7 @@ namespace ImageSharp.Formats /// /// Image encoder for writing an image to a stream as a jpeg. /// - internal unsafe class JpegEncoderCore + internal sealed unsafe class JpegEncoderCore { /// /// The number of quantization tables. diff --git a/src/ImageSharp/Formats/Png/PngConfigurationModule.cs b/src/ImageSharp/Formats/Png/PngConfigurationModule.cs index a3813c5003..bb1c2086cc 100644 --- a/src/ImageSharp/Formats/Png/PngConfigurationModule.cs +++ b/src/ImageSharp/Formats/Png/PngConfigurationModule.cs @@ -8,7 +8,7 @@ namespace ImageSharp.Formats /// /// Registers the image encoders, decoders and mime type detectors for the png format. /// - public class PngConfigurationModule : IConfigurationModule + public sealed class PngConfigurationModule : IConfigurationModule { /// public void Configure(Configuration host) diff --git a/src/ImageSharp/Formats/Png/PngDecoder.cs b/src/ImageSharp/Formats/Png/PngDecoder.cs index 2e177bc211..61a8cb2127 100644 --- a/src/ImageSharp/Formats/Png/PngDecoder.cs +++ b/src/ImageSharp/Formats/Png/PngDecoder.cs @@ -31,7 +31,7 @@ namespace ImageSharp.Formats /// /// /// - public class PngDecoder : IImageDecoder, IPngDecoderOptions + public sealed class PngDecoder : IImageDecoder, IPngDecoderOptions { /// /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded. diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index aef588f256..e6f19b9157 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -20,7 +20,7 @@ namespace ImageSharp.Formats /// /// Performs the png decoding operation. /// - internal class PngDecoderCore + internal sealed class PngDecoderCore { /// /// The dictionary of available color types. diff --git a/src/ImageSharp/Formats/Png/PngEncoder.cs b/src/ImageSharp/Formats/Png/PngEncoder.cs index f0d332fc49..bfd82a0748 100644 --- a/src/ImageSharp/Formats/Png/PngEncoder.cs +++ b/src/ImageSharp/Formats/Png/PngEncoder.cs @@ -14,7 +14,7 @@ namespace ImageSharp.Formats /// /// Image encoder for writing image data to a stream in png format. /// - public class PngEncoder : IImageEncoder, IPngEncoderOptions + public sealed class PngEncoder : IImageEncoder, IPngEncoderOptions { /// /// Gets or sets a value indicating whether the metadata should be ignored when the image is being encoded. diff --git a/src/ImageSharp/Formats/Png/PngHeader.cs b/src/ImageSharp/Formats/Png/PngHeader.cs index 50d6cc9eca..9cf823fa19 100644 --- a/src/ImageSharp/Formats/Png/PngHeader.cs +++ b/src/ImageSharp/Formats/Png/PngHeader.cs @@ -8,7 +8,7 @@ namespace ImageSharp.Formats /// /// Represents the png header chunk. /// - public sealed class PngHeader + internal sealed class PngHeader { /// /// Gets or sets the dimension in x-direction of the image in pixels. diff --git a/src/ImageSharp/Formats/Png/PngInterlaceMode.cs b/src/ImageSharp/Formats/Png/PngInterlaceMode.cs index ec3b8ebe73..b43aff0b78 100644 --- a/src/ImageSharp/Formats/Png/PngInterlaceMode.cs +++ b/src/ImageSharp/Formats/Png/PngInterlaceMode.cs @@ -8,7 +8,7 @@ namespace ImageSharp.Formats /// /// Provides enumeration of available PNG interlace modes. /// - public enum PngInterlaceMode : byte + internal enum PngInterlaceMode : byte { /// /// Non interlaced diff --git a/src/ImageSharp/Formats/Png/Zlib/IChecksum.cs b/src/ImageSharp/Formats/Png/Zlib/IChecksum.cs index 935cdf9532..cbd292dc4c 100644 --- a/src/ImageSharp/Formats/Png/Zlib/IChecksum.cs +++ b/src/ImageSharp/Formats/Png/Zlib/IChecksum.cs @@ -12,7 +12,7 @@ namespace ImageSharp.Formats /// Value. The complete checksum object can also be reset /// so it can be used again with new data. /// - public interface IChecksum + internal interface IChecksum { /// /// Gets the data checksum computed so far. diff --git a/src/ImageSharp/Formats/Png/Zlib/ZlibInflateStream.cs b/src/ImageSharp/Formats/Png/Zlib/ZlibInflateStream.cs index 0743d8ded3..136f919da7 100644 --- a/src/ImageSharp/Formats/Png/Zlib/ZlibInflateStream.cs +++ b/src/ImageSharp/Formats/Png/Zlib/ZlibInflateStream.cs @@ -9,7 +9,7 @@ /// /// Provides methods and properties for deframing streams from PNGs. /// - internal class ZlibInflateStream : Stream + internal sealed class ZlibInflateStream : Stream { /// /// The inner raw memory stream From 66e08f972be750ea1dc7ff99cb25bb81ee62589b Mon Sep 17 00:00:00 2001 From: Dirk Lemstra Date: Thu, 6 Jul 2017 20:52:11 +0200 Subject: [PATCH 14/36] Also support non ushort values of ExifTag.Orientation (Fixes #271). --- .../Processing/Transforms/AutoOrient.cs | 12 ++++++- .../Processing/Transforms/AutoOrientTests.cs | 35 +++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/src/ImageSharp/Processing/Transforms/AutoOrient.cs b/src/ImageSharp/Processing/Transforms/AutoOrient.cs index 07e5d5bc9a..7e7c66460c 100644 --- a/src/ImageSharp/Processing/Transforms/AutoOrient.cs +++ b/src/ImageSharp/Processing/Transforms/AutoOrient.cs @@ -5,6 +5,7 @@ namespace ImageSharp { + using System; using ImageSharp.PixelFormats; using ImageSharp.Processing; @@ -77,7 +78,16 @@ namespace ImageSharp return Orientation.Unknown; } - var orientation = (Orientation)value.Value; + Orientation orientation; + if (value.DataType == ExifDataType.Short) + { + orientation = (Orientation)value.Value; + } + else + { + orientation = (Orientation)Convert.ToUInt16(value.Value); + source.MetaData.ExifProfile.RemoveValue(ExifTag.Orientation); + } source.MetaData.ExifProfile.SetValue(ExifTag.Orientation, (ushort)Orientation.TopLeft); diff --git a/tests/ImageSharp.Tests/Processing/Transforms/AutoOrientTests.cs b/tests/ImageSharp.Tests/Processing/Transforms/AutoOrientTests.cs index 7bc0c8bb52..0646dc9376 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/AutoOrientTests.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/AutoOrientTests.cs @@ -5,6 +5,7 @@ namespace ImageSharp.Tests.Processing.Transforms { + using System; using ImageSharp.PixelFormats; using ImageSharp.Processing; @@ -28,6 +29,16 @@ namespace ImageSharp.Tests.Processing.Transforms { RotateType.Rotate90, FlipType.None, 8 }, }; + public static readonly TheoryData InvalidOrientationValues + = new TheoryData + { + { ExifDataType.Byte, new byte[] { 1 } }, + { ExifDataType.SignedByte, new byte[] { 2 } }, + { ExifDataType.SignedShort, BitConverter.GetBytes((short) 3) }, + { ExifDataType.Long, BitConverter.GetBytes((uint) 4) }, + { ExifDataType.SignedLong, BitConverter.GetBytes((int) 5) } + }; + [Theory] [WithFileCollection(nameof(FlipFiles), nameof(OrientationValues), DefaultPixelType)] public void ImageShouldAutoRotate(TestImageProvider provider, RotateType rotateType, FlipType flipType, ushort orientation) @@ -44,5 +55,29 @@ namespace ImageSharp.Tests.Processing.Transforms .DebugSave(provider, string.Join("_", rotateType, flipType, orientation, "2_after"), Extensions.Bmp); } } + + [Theory] + [WithFileCollection(nameof(FlipFiles), nameof(InvalidOrientationValues), DefaultPixelType)] + public void ImageShouldAutoRotateInvalidValues(TestImageProvider provider, ExifDataType dataType, byte[] orientation) + where TPixel : struct, IPixel + { + var profile = new ExifProfile(); + profile.SetValue(ExifTag.JPEGTables, orientation); + + byte[] bytes = profile.ToByteArray(); + // Change the tag into ExifTag.Orientation + bytes[16] = 18; + bytes[17] = 1; + // Change the data type + bytes[18] = (byte)dataType; + // Change the number of components + bytes[20] = 1; + + using (Image image = provider.GetImage()) + { + image.MetaData.ExifProfile = new ExifProfile(bytes); + image.AutoOrient(); + } + } } } \ No newline at end of file From a9f1f4a4c3de257f8380c608952578bcd0537ae7 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Sat, 15 Jul 2017 10:02:58 +0100 Subject: [PATCH 15/36] update encrypted api key update encrypted api key for user by the new Organisation build server setup --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 6b7ba946ec..fdbe46b015 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -22,7 +22,7 @@ deploy: server: https://www.myget.org/F/imagesharp/api/v2/package symbol_server: https://www.myget.org/F/imagesharp/symbols/api/v2/package api_key: - secure: fz0rUrt3B1HczUC1ZehwVsrFSWX9WZGDQoueDztLte9/+yQG+BBU7UrO+coE8lUf + secure: P2Fz82nty+itjL+kNRCsMQcqzngmVtkU0R4CZqgST7zgUaE6/1q9ekh5MKKlZLkD artifact: /.*\.nupkg/ on: branch: master From aad4c715abd8050f360a3e42da03783123112b06 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Mon, 17 Jul 2017 02:20:17 +0200 Subject: [PATCH 16/36] made FormatsDirectory a lazy value -> folders are no longer walked in TestFile type initializer. Might be healthy for test discoverers. --- tests/ImageSharp.Tests/TestFile.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/tests/ImageSharp.Tests/TestFile.cs b/tests/ImageSharp.Tests/TestFile.cs index c0f9deebb3..7373b70c52 100644 --- a/tests/ImageSharp.Tests/TestFile.cs +++ b/tests/ImageSharp.Tests/TestFile.cs @@ -25,10 +25,10 @@ namespace ImageSharp.Tests private static readonly ConcurrentDictionary Cache = new ConcurrentDictionary(); /// - /// The formats directory. + /// The formats directory, as lazy value /// - private static readonly string FormatsDirectory = GetFormatsDirectory(); - + private static readonly Lazy formatsDirectory = new Lazy(GetFormatsDirectory); + /// /// The image. /// @@ -71,6 +71,11 @@ namespace ImageSharp.Tests /// public string FileNameWithoutExtension => Path.GetFileNameWithoutExtension(this.file); + /// + /// Gets the "Formats" test file directory. + /// + private static string FormatsDirectory => formatsDirectory.Value; + /// /// Gets the full qualified path to the file. /// From ad4aee89e30eb3103e4726d8b5280492c365c6fd Mon Sep 17 00:00:00 2001 From: ascensio Date: Mon, 17 Jul 2017 16:38:22 +0200 Subject: [PATCH 17/36] closes #269 --- .../Processors/Transforms/RotateProcessor.cs | 2 +- .../Image/ImageRotationTests.cs | 54 +++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 tests/ImageSharp.Tests/Image/ImageRotationTests.cs diff --git a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs index e6b1d180f1..d563d072aa 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs @@ -79,7 +79,7 @@ namespace ImageSharp.Processing.Processors return; } - this.processMatrix = Matrix3x2Extensions.CreateRotation(-this.Angle, new Point(0, 0)); + this.processMatrix = Matrix3x2Extensions.CreateRotationDegrees(-this.Angle, new Point(0, 0)); if (this.Expand) { this.CreateNewCanvas(sourceRectangle, this.processMatrix); diff --git a/tests/ImageSharp.Tests/Image/ImageRotationTests.cs b/tests/ImageSharp.Tests/Image/ImageRotationTests.cs new file mode 100644 index 0000000000..44fa458fae --- /dev/null +++ b/tests/ImageSharp.Tests/Image/ImageRotationTests.cs @@ -0,0 +1,54 @@ +using SixLabors.Primitives; +using Xunit; + +namespace ImageSharp.Tests +{ + public class ImageRotationTests + { + [Fact] + public void RotateImageByMinus90Degrees() + { + (Size original, Size rotated) = Rotate(-90); + Assert.Equal(new Size(original.Height, original.Width), rotated); + } + + [Fact] + public void RotateImageBy90Degrees() + { + (Size original, Size rotated) = Rotate(90); + Assert.Equal(new Size(original.Height, original.Width), rotated); + } + + [Fact] + public void RotateImageBy180Degrees() + { + (Size original, Size rotated) = Rotate(180); + Assert.Equal(original, rotated); + } + + [Fact] + public void RotateImageBy270Degrees() + { + (Size original, Size rotated) = Rotate(270); + Assert.Equal(new Size(original.Height, original.Width), rotated); + } + + [Fact] + public void RotateImageBy360Degrees() + { + (Size original, Size rotated) = Rotate(360); + Assert.Equal(original, rotated); + } + + private static (Size original, Size rotated) Rotate(int angle) + { + TestFile file = TestFile.Create(TestImages.Bmp.Car); + using (Image image = Image.Load(file.FilePath)) + { + Size expected = image.Bounds.Size; + image.Rotate(angle); + return (expected, image.Bounds.Size); + } + } + } +} From c4fa14d2cdfc51cec0d184c1979f0d49274a0f5c Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Tue, 18 Jul 2017 18:35:41 +0100 Subject: [PATCH 18/36] temp work around for appveyor nuget restore issue https://appveyor.statuspage.io/incidents/m2vdvw39kdk8 --- appveyor.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/appveyor.yml b/appveyor.yml index fdbe46b015..0799133118 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -7,6 +7,10 @@ skip_branch_with_pr: true init: - ps: iex ((new-object net.webclient).DownloadString('https://gist.githubusercontent.com/PureKrome/0f79e25693d574807939/raw/8cf3160c9516ef1f4effc825c0a44acc918a0b5a/appveyor-build-info.ps')) +# temp work around - https://appveyor.statuspage.io/incidents/m2vdvw39kdk8 +hosts: + api.nuget.org: 93.184.221.200 + build_script: - cmd: build.cmd From 82661d3958dfc80387601316a1762c7963736530 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Tue, 18 Jul 2017 20:42:00 +0100 Subject: [PATCH 19/36] fix concurrency issues in config --- src/ImageSharp/Configuration.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/ImageSharp/Configuration.cs b/src/ImageSharp/Configuration.cs index 226d451325..a9322467cb 100644 --- a/src/ImageSharp/Configuration.cs +++ b/src/ImageSharp/Configuration.cs @@ -35,14 +35,14 @@ namespace ImageSharp private readonly ConcurrentDictionary mimeTypeDecoders = new ConcurrentDictionary(); /// - /// The list of supported s. + /// The list of supported s. /// - private readonly List imageFormatDetectors = new List(); + private readonly ConcurrentBag imageFormats = new ConcurrentBag(); /// - /// The list of supported s. + /// The list of supported s. /// - private readonly HashSet imageFormats = new HashSet(); + private ConcurrentBag imageFormatDetectors = new ConcurrentBag(); /// /// Initializes a new instance of the class. @@ -181,7 +181,7 @@ namespace ImageSharp /// public void ClearImageFormatDetectors() { - this.imageFormatDetectors.Clear(); + this.imageFormatDetectors = new ConcurrentBag(); } /// From ba6e00499c77fdbf530b4049c41dc3d763ea0e3e Mon Sep 17 00:00:00 2001 From: Nikita Balabaev Date: Fri, 28 Jul 2017 18:31:10 +0700 Subject: [PATCH 20/36] fix #277 --- src/ImageSharp/Formats/Bmp/BmpFormat.cs | 2 +- src/ImageSharp/Formats/Gif/GifFormat.cs | 2 +- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 20 +++++++++--------- src/ImageSharp/Formats/Png/PngFormat.cs | 2 +- .../Formats/Png/PngDecoderTests.cs | 2 +- tests/ImageSharp.Tests/TestImages.cs | 1 + .../Formats/Png/rgb-48bpp-interlaced.png | Bin 0 -> 69952 bytes 7 files changed, 15 insertions(+), 14 deletions(-) create mode 100644 tests/ImageSharp.Tests/TestImages/Formats/Png/rgb-48bpp-interlaced.png diff --git a/src/ImageSharp/Formats/Bmp/BmpFormat.cs b/src/ImageSharp/Formats/Bmp/BmpFormat.cs index fb65f34d7d..bd25eb9b75 100644 --- a/src/ImageSharp/Formats/Bmp/BmpFormat.cs +++ b/src/ImageSharp/Formats/Bmp/BmpFormat.cs @@ -8,7 +8,7 @@ namespace ImageSharp.Formats using System.Collections.Generic; /// - /// Registers the image encoders, decoders and mime type detectors for the jpeg format. + /// Registers the image encoders, decoders and mime type detectors for the bmp format. /// internal sealed class BmpFormat : IImageFormat { diff --git a/src/ImageSharp/Formats/Gif/GifFormat.cs b/src/ImageSharp/Formats/Gif/GifFormat.cs index ea7b72d327..744aadff9a 100644 --- a/src/ImageSharp/Formats/Gif/GifFormat.cs +++ b/src/ImageSharp/Formats/Gif/GifFormat.cs @@ -8,7 +8,7 @@ namespace ImageSharp.Formats using System.Collections.Generic; /// - /// Registers the image encoders, decoders and mime type detectors for the jpeg format. + /// Registers the image encoders, decoders and mime type detectors for the gif format. /// internal sealed class GifFormat : IImageFormat { diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index e6f19b9157..467d41ff41 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -825,14 +825,14 @@ namespace ImageSharp.Formats using (var compressed = new Buffer(length)) { // TODO: Should we use pack from vector here instead? - this.From16BitTo8Bit(new Span(defilteredScanline), compressed, length); - for (int x = pixelOffset, o = 1; + this.From16BitTo8Bit(new Span(defilteredScanline, 1), compressed, length); + for (int x = pixelOffset, o = 0; x < this.header.Width; - x += increment, o += this.bytesPerPixel) + x += increment, o += 3) { rgba.R = compressed[o]; - rgba.G = compressed[o + this.bytesPerSample]; - rgba.B = compressed[o + (2 * this.bytesPerSample)]; + rgba.G = compressed[o + 1]; + rgba.B = compressed[o + 2]; color.PackFromRgba32(rgba); rowSpan[x] = color; @@ -862,13 +862,13 @@ namespace ImageSharp.Formats using (var compressed = new Buffer(length)) { // TODO: Should we use pack from vector here instead? - this.From16BitTo8Bit(new Span(defilteredScanline), compressed, length); - for (int x = pixelOffset, o = 1; x < this.header.Width; x += increment, o += this.bytesPerPixel) + this.From16BitTo8Bit(new Span(defilteredScanline, 1), compressed, length); + for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += 4) { rgba.R = compressed[o]; - rgba.G = compressed[o + this.bytesPerSample]; - rgba.B = compressed[o + (2 * this.bytesPerSample)]; - rgba.A = compressed[o + (3 * this.bytesPerSample)]; + rgba.G = compressed[o + 1]; + rgba.B = compressed[o + 2]; + rgba.A = compressed[o + 3]; color.PackFromRgba32(rgba); rowSpan[x] = color; diff --git a/src/ImageSharp/Formats/Png/PngFormat.cs b/src/ImageSharp/Formats/Png/PngFormat.cs index 551b4a8c78..6df2aa7c58 100644 --- a/src/ImageSharp/Formats/Png/PngFormat.cs +++ b/src/ImageSharp/Formats/Png/PngFormat.cs @@ -8,7 +8,7 @@ namespace ImageSharp.Formats using System.Collections.Generic; /// - /// Registers the image encoders, decoders and mime type detectors for the jpeg format. + /// Registers the image encoders, decoders and mime type detectors for the png format. /// internal sealed class PngFormat : IImageFormat { diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs index 6d82b53746..ee8a2ee55b 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs @@ -18,7 +18,7 @@ namespace ImageSharp.Tests public static readonly string[] TestFiles = { TestImages.Png.Splash, TestImages.Png.Indexed, TestImages.Png.Interlaced, TestImages.Png.FilterVar, - TestImages.Png.Bad.ChunkLength1, TestImages.Png.Bad.ChunkLength2, TestImages.Png.Rgb48Bpp + TestImages.Png.Bad.ChunkLength1, TestImages.Png.Bad.ChunkLength2, TestImages.Png.Rgb48Bpp, TestImages.Png.Rgb48BppInterlaced }; [Theory] diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index f35720f199..3479457cf8 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -26,6 +26,7 @@ namespace ImageSharp.Tests public const string SplashInterlaced = "Png/splash-interlaced.png"; public const string Interlaced = "Png/interlaced.png"; public const string Rgb48Bpp = "Png/rgb-48bpp.png"; + public const string Rgb48BppInterlaced = "Png/rgb-48bpp-interlaced.png"; // Filtered test images from http://www.schaik.com/pngsuite/pngsuite_fil_png.html public const string Filter0 = "Png/filter0.png"; diff --git a/tests/ImageSharp.Tests/TestImages/Formats/Png/rgb-48bpp-interlaced.png b/tests/ImageSharp.Tests/TestImages/Formats/Png/rgb-48bpp-interlaced.png new file mode 100644 index 0000000000000000000000000000000000000000..9202eeae4cf9ab5f52e5d598137338559d30238d GIT binary patch literal 69952 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 Zg9>9`e_kqn`z6V=_w``Edf%9={{x|$O_2Zq literal 0 HcmV?d00001 From a209a8210d02457dc0e7c687018f9cc18aca1e90 Mon Sep 17 00:00:00 2001 From: Devedse Date: Sun, 6 Aug 2017 02:31:28 +0200 Subject: [PATCH 21/36] Added failing test for issue 288 --- .../ImageSharp.Tests/Image/ImageLoadTests.cs | 60 +++++++++++------- tests/ImageSharp.Tests/TestImages.cs | 5 +- .../TestImages/Formats/Png/vim16x16_1.png | Bin 0 -> 226 bytes .../TestImages/Formats/Png/vim16x16_2.png | Bin 0 -> 292 bytes 4 files changed, 41 insertions(+), 24 deletions(-) create mode 100644 tests/ImageSharp.Tests/TestImages/Formats/Png/vim16x16_1.png create mode 100644 tests/ImageSharp.Tests/TestImages/Formats/Png/vim16x16_2.png diff --git a/tests/ImageSharp.Tests/Image/ImageLoadTests.cs b/tests/ImageSharp.Tests/Image/ImageLoadTests.cs index bb64ceda34..d821f4bc76 100644 --- a/tests/ImageSharp.Tests/Image/ImageLoadTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageLoadTests.cs @@ -10,7 +10,6 @@ namespace ImageSharp.Tests using ImageSharp.Formats; using ImageSharp.IO; - using ImageSharp.PixelFormats; using Moq; using Xunit; @@ -44,7 +43,8 @@ namespace ImageSharp.Tests this.localDecoder.Setup(x => x.Decode(It.IsAny(), It.IsAny())) - .Callback((c, s) => { + .Callback((c, s) => + { using (var ms = new MemoryStream()) { s.CopyTo(ms); @@ -76,7 +76,7 @@ namespace ImageSharp.Tests [Fact] public void LoadFromStream() { - Image img = Image.Load(this.DataStream); + Image img = Image.Load(this.DataStream); Assert.NotNull(img); @@ -87,12 +87,11 @@ namespace ImageSharp.Tests public void LoadFromNoneSeekableStream() { NoneSeekableStream stream = new NoneSeekableStream(this.DataStream); - Image img = Image.Load(stream); + Image img = Image.Load(stream); Assert.NotNull(img); TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, Configuration.Default); - } [Fact] @@ -104,20 +103,18 @@ namespace ImageSharp.Tests Assert.Equal(TestFormat.GlobalTestFormat.Sample(), img); TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, Configuration.Default); - } - + [Fact] public void LoadFromStreamWithConfig() { Stream stream = new MemoryStream(); - Image img = Image.Load(this.LocalConfiguration, stream); + Image img = Image.Load(this.LocalConfiguration, stream); Assert.NotNull(img); this.localDecoder.Verify(x => x.Decode(this.LocalConfiguration, stream)); - } [Fact] @@ -130,7 +127,6 @@ namespace ImageSharp.Tests Assert.Equal(this.returnImage, img); this.localDecoder.Verify(x => x.Decode(this.LocalConfiguration, stream)); - } @@ -138,7 +134,7 @@ namespace ImageSharp.Tests public void LoadFromStreamWithDecoder() { Stream stream = new MemoryStream(); - Image img = Image.Load(stream, this.localDecoder.Object); + Image img = Image.Load(stream, this.localDecoder.Object); Assert.NotNull(img); this.localDecoder.Verify(x => x.Decode(Configuration.Default, stream)); @@ -158,13 +154,11 @@ namespace ImageSharp.Tests [Fact] public void LoadFromBytes() { - Image img = Image.Load(this.DataStream.ToArray()); + Image img = Image.Load(this.DataStream.ToArray()); Assert.NotNull(img); - TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, Configuration.Default); - } [Fact] @@ -182,7 +176,7 @@ namespace ImageSharp.Tests [Fact] public void LoadFromBytesWithConfig() { - Image img = Image.Load(this.LocalConfiguration, this.DataStream.ToArray()); + Image img = Image.Load(this.LocalConfiguration, this.DataStream.ToArray()); Assert.NotNull(img); @@ -207,7 +201,7 @@ namespace ImageSharp.Tests [Fact] public void LoadFromBytesWithDecoder() { - Image img = Image.Load(this.DataStream.ToArray(), this.localDecoder.Object); + Image img = Image.Load(this.DataStream.ToArray(), this.localDecoder.Object); Assert.NotNull(img); this.localDecoder.Verify(x => x.Decode(Configuration.Default, It.IsAny())); @@ -228,13 +222,11 @@ namespace ImageSharp.Tests [Fact] public void LoadFromFile() { - Image img = Image.Load(this.DataStream); + Image img = Image.Load(this.DataStream); Assert.NotNull(img); - TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, Configuration.Default); - } [Fact] @@ -251,12 +243,11 @@ namespace ImageSharp.Tests [Fact] public void LoadFromFileWithConfig() { - Image img = Image.Load(this.LocalConfiguration, this.FilePath); + Image img = Image.Load(this.LocalConfiguration, this.FilePath); Assert.NotNull(img); this.localDecoder.Verify(x => x.Decode(this.LocalConfiguration, this.DataStream)); - } [Fact] @@ -273,7 +264,7 @@ namespace ImageSharp.Tests [Fact] public void LoadFromFileWithDecoder() { - Image img = Image.Load(this.FilePath, this.localDecoder.Object); + Image img = Image.Load(this.FilePath, this.localDecoder.Object); Assert.NotNull(img); this.localDecoder.Verify(x => x.Decode(Configuration.Default, this.DataStream)); @@ -305,7 +296,6 @@ namespace ImageSharp.Tests Assert.Equal(Rgba32.White, px[1, 0]); Assert.Equal(Rgba32.Black, px[1, 1]); - } } @@ -327,7 +317,31 @@ namespace ImageSharp.Tests Assert.Equal(Rgba32.White, px[1, 0]); Assert.Equal(Rgba32.Black, px[1, 1]); + } + } + [Fact] + public void TestThatTwoImagesAreEqual() + { + var image1Provider = TestImageProvider.File(TestImages.Png.VimImage1); + var image2Provider = TestImageProvider.File(TestImages.Png.VimImage2); + + using (Image img1 = image1Provider.GetImage()) + using (Image img2 = image2Provider.GetImage()) + { + Assert.Equal(img1.Width, img2.Width); + Assert.Equal(img1.Height, img2.Height); + + for (int y = 0; y < img1.Height; y++) + { + for (int x = 0; x < img1.Width; x++) + { + Rgba32 pixel1 = img1[x, y]; + Rgba32 pixel2 = img2[x, y]; + + Assert.Equal(pixel1, pixel2); + } + } } } diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 3479457cf8..7d552d1094 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -38,6 +38,9 @@ namespace ImageSharp.Tests // Filter changing per scanline public const string FilterVar = "Png/filterVar.png"; + public const string VimImage1 = "Png/vim16x16_1.png"; + public const string VimImage2 = "Png/vim16x16_2.png"; + public static class Bad { // Odd chunk lengths @@ -50,7 +53,7 @@ namespace ImageSharp.Tests P1, Pd, Blur, Splash, Cross, Powerpoint, SplashInterlaced, Interlaced, Filter0, Filter1, Filter2, Filter3, Filter4, - FilterVar + FilterVar, VimImage1, VimImage2 }; } diff --git a/tests/ImageSharp.Tests/TestImages/Formats/Png/vim16x16_1.png b/tests/ImageSharp.Tests/TestImages/Formats/Png/vim16x16_1.png new file mode 100644 index 0000000000000000000000000000000000000000..fb45d22a0560d23c20b65ea1922099a8ae56683e GIT binary patch literal 226 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!73?$#)eFPFNHZ`PcHfZ)Qj8@*e!&b5&u*jvIT@ZVjv*CuR8Mc@ zWj5qtc3Ad_O|n7D#PNZdv4p{<#;aOPpQ7wH&A-~H(0FEjI#ces{!lvI6;>1s;*b z3=DjSL74G){)!Z!pk#?_L`iUdT1k0gQ7S`0VrE{6US4X6f{C7io}syM-uz^sqJ^F= zjv*HQQztU=9Z}$5`CVd{_UQb(hFvnPSHI+#Z8^XfwcfDJ`}*>ea=mNh>_l#UO54d> z$|%%enLfGW$dO##Z9%gZDZX8BEs5Rzz`;LU2hQ*=(DV;uFugtT+QxSW)8aaLEM^J2 zeEofbA;VN~v(5U6j1!#iZf%>X&(L7NFr~P|oxwOXavsyC(uIZM(hoi!?@Z)oI5MTN jo^jP4$JXz1$G@_QMjE`zc;f8?bS#6XtDnm{r-UW|K_qIq literal 0 HcmV?d00001 From fec2d6fe9a6310a0af8a36671dd7e49d378ea424 Mon Sep 17 00:00:00 2001 From: JimBobSquarePants Date: Tue, 8 Aug 2017 15:37:56 +1000 Subject: [PATCH 22/36] Fix failing test --- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index 467d41ff41..cdbd3a6893 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -688,19 +688,11 @@ namespace ImageSharp.Formats // channel and we should try to read it. for (int x = 0; x < this.header.Width; x++) { - int index = newScanline[x + 1]; + int index = newScanline[x]; int pixelOffset = index * 3; rgba.A = this.paletteAlpha.Length > index ? this.paletteAlpha[index] : (byte)255; - - if (rgba.A > 0) - { - rgba.Rgb = pal.GetRgb24(pixelOffset); - } - else - { - rgba = default(Rgba32); - } + rgba.Rgb = pal.GetRgb24(pixelOffset); color.PackFromRgba32(rgba); row[x] = color; @@ -712,7 +704,7 @@ namespace ImageSharp.Formats for (int x = 0; x < this.header.Width; x++) { - int index = newScanline[x + 1]; + int index = newScanline[x]; int pixelOffset = index * 3; rgba.Rgb = pal.GetRgb24(pixelOffset); From 51eca28eb6177d96c437e3da05bc731d4e1eaa3d Mon Sep 17 00:00:00 2001 From: JimBobSquarePants Date: Tue, 8 Aug 2017 16:22:30 +1000 Subject: [PATCH 23/36] Disable smoke test --- .../Formats/Png/PngSmokeTests.cs | 40 ++++++++++--------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs index bac340a71c..c977f5fb55 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs @@ -40,25 +40,27 @@ namespace ImageSharp.Tests.Formats.Png } } - [Theory] - [WithTestPatternImages(100, 100, PixelTypes.Rgba32)] - public void CanSaveIndexedPng(TestImageProvider provider) - where TPixel : struct, IPixel - { - // does saving a file then repoening mean both files are identical??? - using (Image image = provider.GetImage()) - using (MemoryStream ms = new MemoryStream()) - { - // image.Save(provider.Utility.GetTestOutputFileName("bmp")); - image.Save(ms, new PngEncoder() { PaletteSize = 256 }); - ms.Position = 0; - using (Image img2 = Image.Load(ms, new PngDecoder())) - { - // img2.Save(provider.Utility.GetTestOutputFileName("bmp", "_loaded"), new BmpEncoder()); - ImageComparer.CheckSimilarity(image, img2, 0.03f); - } - } - } + // JJS: Disabled for now as the decoder now correctly decodes the full pixel components if the + // paletted image has alpha of 0 + //[Theory] + //[WithTestPatternImages(100, 100, PixelTypes.Rgba32)] + //public void CanSaveIndexedPng(TestImageProvider provider) + // where TPixel : struct, IPixel + //{ + // // does saving a file then repoening mean both files are identical??? + // using (Image image = provider.GetImage()) + // using (MemoryStream ms = new MemoryStream()) + // { + // // image.Save(provider.Utility.GetTestOutputFileName("bmp")); + // image.Save(ms, new PngEncoder() { PaletteSize = 256 }); + // ms.Position = 0; + // using (Image img2 = Image.Load(ms, new PngDecoder())) + // { + // // img2.Save(provider.Utility.GetTestOutputFileName("bmp", "_loaded"), new BmpEncoder()); + // ImageComparer.CheckSimilarity(image, img2, 0.03f); + // } + // } + //} // JJS: Commented out for now since the test does not take into lossy nature of indexing. //[Theory] From 852ae7b777988e816827d629ec3d6c99f547b8d8 Mon Sep 17 00:00:00 2001 From: Dirk Lemstra Date: Thu, 10 Aug 2017 09:10:22 +0200 Subject: [PATCH 24/36] Only throw exceptions when the CRC of a critical chunk is incorrect. --- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 13 ++- .../Formats/Png/PngDecoderTests.cs | 87 ++++++++++++++++--- 2 files changed, 86 insertions(+), 14 deletions(-) diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index cdbd3a6893..95211c5f3d 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -306,6 +306,15 @@ namespace ImageSharp.Formats return result; } + private static bool IsCriticalChunk(PngChunk chunk) + { + return + chunk.Type == PngChunkTypes.Header || + chunk.Type == PngChunkTypes.Palette || + chunk.Type == PngChunkTypes.Data || + chunk.Type == PngChunkTypes.End; + } + /// /// Reads the data chunk containing physical dimension data. /// @@ -1017,9 +1026,9 @@ namespace ImageSharp.Formats this.crc.Update(this.chunkTypeBuffer); this.crc.Update(chunk.Data, 0, chunk.Length); - if (this.crc.Value != chunk.Crc) + if (this.crc.Value != chunk.Crc && IsCriticalChunk(chunk)) { - throw new ImageFormatException("CRC Error. PNG Image chunk is corrupt!"); + throw new ImageFormatException($"CRC Error. PNG {chunk.Type} chunk is corrupt!"); } } diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs index ee8a2ee55b..3c335441ab 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs @@ -3,14 +3,15 @@ // Licensed under the Apache License, Version 2.0. // +using System.IO; +using System.IO.Compression; +using System.Text; +using ImageSharp.Formats; +using ImageSharp.PixelFormats; +using Xunit; + namespace ImageSharp.Tests { - using System.Text; - using Xunit; - - using ImageSharp.Formats; - using ImageSharp.PixelFormats; - public class PngDecoderTests { private const PixelTypes PixelTypes = Tests.PixelTypes.Rgba32 | Tests.PixelTypes.RgbaVector | Tests.PixelTypes.Argb32; @@ -35,12 +36,12 @@ namespace ImageSharp.Tests [Fact] public void Decode_IgnoreMetadataIsFalse_TextChunckIsRead() { - PngDecoder options = new PngDecoder() + var options = new PngDecoder() { IgnoreMetadata = false }; - TestFile testFile = TestFile.Create(TestImages.Png.Blur); + var testFile = TestFile.Create(TestImages.Png.Blur); using (Image image = testFile.CreateImage(options)) { @@ -53,12 +54,12 @@ namespace ImageSharp.Tests [Fact] public void Decode_IgnoreMetadataIsTrue_TextChunksAreIgnored() { - PngDecoder options = new PngDecoder() + var options = new PngDecoder() { IgnoreMetadata = true }; - TestFile testFile = TestFile.Create(TestImages.Png.Blur); + var testFile = TestFile.Create(TestImages.Png.Blur); using (Image image = testFile.CreateImage(options)) { @@ -69,12 +70,12 @@ namespace ImageSharp.Tests [Fact] public void Decode_TextEncodingSetToUnicode_TextIsReadWithCorrectEncoding() { - PngDecoder options = new PngDecoder() + var options = new PngDecoder() { TextEncoding = Encoding.Unicode }; - TestFile testFile = TestFile.Create(TestImages.Png.Blur); + var testFile = TestFile.Create(TestImages.Png.Blur); using (Image image = testFile.CreateImage(options)) { @@ -82,5 +83,67 @@ namespace ImageSharp.Tests Assert.Equal("潓瑦慷敲", image.MetaData.Properties[0].Name); } } + + [Theory] + [InlineData(PngChunkTypes.Header)] + [InlineData(PngChunkTypes.Palette)] + // [InlineData(PngChunkTypes.Data)] //TODO: Figure out how to test this + [InlineData(PngChunkTypes.End)] + public void Decode_IncorrectCRCForCriticalChunk_ExceptionIsThrown(string chunkName) + { + using (var memStream = new MemoryStream()) + { + memStream.Skip(8); + + WriteChunk(memStream, chunkName); + + CompressStream(memStream); + + var decoder = new PngDecoder(); + + ImageFormatException exception = Assert.Throws(() => + { + decoder.Decode(null, memStream); + }); + + Assert.Equal($"CRC Error. PNG {chunkName} chunk is corrupt!", exception.Message); + } + } + + [Theory] + [InlineData(PngChunkTypes.Gamma)] + [InlineData(PngChunkTypes.PaletteAlpha)] + [InlineData(PngChunkTypes.Physical)] + //[InlineData(PngChunkTypes.Text)] //TODO: Figure out how to test this + public void Decode_IncorrectCRCForNonCriticalChunk_ExceptionIsThrown(string chunkName) + { + using (var memStream = new MemoryStream()) + { + memStream.Skip(8); + + WriteChunk(memStream, chunkName); + + CompressStream(memStream); + + var decoder = new PngDecoder(); + decoder.Decode(null, memStream); + } + } + + private static void WriteChunk(MemoryStream memStream, string chunkName) + { + memStream.Write(new byte[] { 0, 0, 0, 1 }, 0, 4); + memStream.Write(Encoding.GetEncoding("ASCII").GetBytes(chunkName), 0, 4); + memStream.Write(new byte[] { 0, 0, 0, 0, 0 }, 0, 5); + } + + private static void CompressStream(Stream stream) + { + stream.Position = 0; + using (var deflateStream = new DeflateStream(stream, CompressionLevel.NoCompression, true)) + { + } + stream.Position = 0; + } } } \ No newline at end of file From 4b2fbcd4538f4bbfaf977dc44397cbcd4cae5225 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 12 Aug 2017 19:01:05 +1000 Subject: [PATCH 25/36] Correctly read Adam7 palette colors --- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index 95211c5f3d..372ecdc2b0 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -768,10 +768,7 @@ namespace ImageSharp.Formats case PngColorType.Palette: - byte[] newScanline = ToArrayByBitsLength( - defilteredScanline, - this.bytesPerScanline, - this.header.BitDepth); + byte[] newScanline = ToArrayByBitsLength(defilteredScanline, this.bytesPerScanline, this.header.BitDepth); var rgba = default(Rgba32); if (this.paletteAlpha != null && this.paletteAlpha.Length > 0) @@ -784,15 +781,7 @@ namespace ImageSharp.Formats int offset = index * 3; rgba.A = this.paletteAlpha.Length > index ? this.paletteAlpha[index] : (byte)255; - - if (rgba.A > 0) - { - rgba.Rgb = this.palette.GetRgb24(offset); - } - else - { - rgba = default(Rgba32); - } + rgba.Rgb = this.palette.GetRgb24(offset); color.PackFromRgba32(rgba); rowSpan[x] = color; From 07c0af934054c1e885073d628eae34b3373a3694 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 12 Aug 2017 19:08:58 +1000 Subject: [PATCH 26/36] Update AppVeyor key --- appveyor.yml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 0799133118..6b7ba946ec 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -7,10 +7,6 @@ skip_branch_with_pr: true init: - ps: iex ((new-object net.webclient).DownloadString('https://gist.githubusercontent.com/PureKrome/0f79e25693d574807939/raw/8cf3160c9516ef1f4effc825c0a44acc918a0b5a/appveyor-build-info.ps')) -# temp work around - https://appveyor.statuspage.io/incidents/m2vdvw39kdk8 -hosts: - api.nuget.org: 93.184.221.200 - build_script: - cmd: build.cmd @@ -26,7 +22,7 @@ deploy: server: https://www.myget.org/F/imagesharp/api/v2/package symbol_server: https://www.myget.org/F/imagesharp/symbols/api/v2/package api_key: - secure: P2Fz82nty+itjL+kNRCsMQcqzngmVtkU0R4CZqgST7zgUaE6/1q9ekh5MKKlZLkD + secure: fz0rUrt3B1HczUC1ZehwVsrFSWX9WZGDQoueDztLte9/+yQG+BBU7UrO+coE8lUf artifact: /.*\.nupkg/ on: branch: master From 94fbdf554821f0bd745db47b8e2ac00309c9cd04 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 12 Aug 2017 19:40:28 +1000 Subject: [PATCH 27/36] Revert key change, we have a new CI! --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 6b7ba946ec..fdbe46b015 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -22,7 +22,7 @@ deploy: server: https://www.myget.org/F/imagesharp/api/v2/package symbol_server: https://www.myget.org/F/imagesharp/symbols/api/v2/package api_key: - secure: fz0rUrt3B1HczUC1ZehwVsrFSWX9WZGDQoueDztLte9/+yQG+BBU7UrO+coE8lUf + secure: P2Fz82nty+itjL+kNRCsMQcqzngmVtkU0R4CZqgST7zgUaE6/1q9ekh5MKKlZLkD artifact: /.*\.nupkg/ on: branch: master From 50467456a72e1b6a03ed7c630fb359f8648b0779 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 12 Aug 2017 19:47:04 +1000 Subject: [PATCH 28/36] Fix #281 --- src/ImageSharp/Formats/Gif/GifDecoderCore.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs index bb230beac7..948103fed2 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs @@ -157,6 +157,10 @@ namespace ImageSharp.Formats } nextFlag = stream.ReadByte(); + if (nextFlag == -1) + { + break; + } } } finally From 68df16b43865592b3165efe84a7da22e419d9522 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 12 Aug 2017 20:00:27 +1000 Subject: [PATCH 29/36] Update readme [skip ci] --- README.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index e46b113793..70e48e52e6 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -# ImageSharp ImageSharp +# ImageSharp ImageSharp **ImageSharp** is a new, fully featured, fully managed, cross-platform, 2D graphics API designed to allow the processing of images without the use of `System.Drawing`. @@ -9,12 +9,12 @@ Built against .Net Standard 1.1 ImageSharp can be used in device, cloud, and emb > > Pre-release downloads are available from the [MyGet package repository](https://www.myget.org/gallery/imagesharp). -[![GitHub license](https://img.shields.io/badge/license-Apache%202-blue.svg)](https://raw.githubusercontent.com/JimBobSquarePants/ImageSharp/master/APACHE-2.0-LICENSE.txt) -[![GitHub issues](https://img.shields.io/github/issues/JimBobSquarePants/ImageSharp.svg)](https://github.com/JimBobSquarePants/ImageSharp/issues) -[![GitHub stars](https://img.shields.io/github/stars/JimBobSquarePants/ImageSharp.svg)](https://github.com/JimBobSquarePants/ImageSharp/stargazers) -[![GitHub forks](https://img.shields.io/github/forks/JimBobSquarePants/ImageSharp.svg)](https://github.com/JimBobSquarePants/ImageSharp/network) +[![GitHub license](https://img.shields.io/badge/license-Apache%202-blue.svg)](https://raw.githubusercontent.com/SixLabors/ImageSharp/master/APACHE-2.0-LICENSE.txt) +[![GitHub issues](https://img.shields.io/github/issues/SixLabors/ImageSharp.svg)](https://github.com/SixLabors/ImageSharp/issues) +[![GitHub stars](https://img.shields.io/github/stars/SixLabors/ImageSharp.svg)](https://github.com/SixLabors/ImageSharp/stargazers) +[![GitHub forks](https://img.shields.io/github/forks/SixLabors/ImageSharp.svg)](https://github.com/SixLabors/ImageSharp/network) [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/ImageSharp/General?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -[![Twitter](https://img.shields.io/twitter/url/https/github.com/JimBobSquarePants/ImageSharp.svg?style=social)](https://twitter.com/intent/tweet?hashtags=imagesharp,dotnet,oss&text=ImageSharp.+A+new+cross-platform+2D+graphics+API+in+C%23&url=https%3a%2f%2fgithub.com%2fJimBobSquarePants%2fImageSharp&via=james_m_south) +[![Twitter](https://img.shields.io/twitter/url/https/github.com/SixLabors/ImageSharp.svg?style=social)](https://twitter.com/intent/tweet?hashtags=imagesharp,dotnet,oss&text=ImageSharp.+A+new+cross-platform+2D+graphics+API+in+C%23&url=https%3a%2f%2fgithub.com%2fSixLabors%2fImageSharp&via=sixlabors) [![OpenCollective](https://opencollective.com/imagesharp/backers/badge.svg)](#backers) [![OpenCollective](https://opencollective.com/imagesharp/sponsors/badge.svg)](#sponsors) @@ -22,8 +22,8 @@ Built against .Net Standard 1.1 ImageSharp can be used in device, cloud, and emb | |Build Status|Code Coverage| |-------------|:----------:|:-----------:| -|**Linux/Mac**|[![Build Status](https://travis-ci.org/JimBobSquarePants/ImageSharp.svg)](https://travis-ci.org/JimBobSquarePants/ImageSharp)|[![Code coverage](https://codecov.io/gh/JimBobSquarePants/ImageSharp/branch/master/graph/badge.svg)](https://codecov.io/gh/JimBobSquarePants/ImageSharp)| -|**Windows** |[![Build Status](https://ci.appveyor.com/api/projects/status/hu6d1gdpxdw0q360/branch/master?svg=true)](https://ci.appveyor.com/project/JamesSouth/imagesharp/branch/master)|[![Code coverage](https://codecov.io/gh/JimBobSquarePants/ImageSharp/branch/master/graph/badge.svg)](https://codecov.io/gh/JimBobSquarePants/ImageSharp)| +|**Linux/Mac**|[![Build Status](https://travis-ci.org/SixLabors/ImageSharp.svg)](https://travis-ci.org/SixLabors/ImageSharp)|[![Code coverage](https://codecov.io/gh/SixLabors/ImageSharp/branch/master/graph/badge.svg)](https://codecov.io/gh/SixLabors/ImageSharp)| +|**Windows** |[![Build Status](https://ci.appveyor.com/api/projects/status/m9pn907xdah3ca39/branch/master?svg=true)](https://ci.appveyor.com/project/SixLabors/imagesharp/branch/master)|[![Code coverage](https://codecov.io/gh/SixLabors/ImageSharp/branch/master/graph/badge.svg)](https://codecov.io/gh/SixLabors/ImageSharp)| ### Installation @@ -64,7 +64,7 @@ Alternatively on Linux you can use: To clone it locally click the "Clone in Windows" button above or run the following git commands. ```bash -git clone https://github.com/JimBobSquarePants/ImageSharp +git clone https://github.com/SixLabors/ImageSharp ``` ### Features @@ -121,7 +121,7 @@ For optimized access within a loop it is recommended that the following methods 1. `image.GetRowSpan(y)` 2. `image.GetRowSpan(x, y)` -For advanced pixel format usage there are multiple [PixelFormat implementations](https://github.com/JimBobSquarePants/ImageSharp/tree/master/src/ImageSharp/PixelFormats) available allowing developers to implement their own color models in the same manner as Microsoft XNA Game Studio and MonoGame. +For advanced pixel format usage there are multiple [PixelFormat implementations](https://github.com/SixLabors/ImageSharp/tree/master/src/ImageSharp/PixelFormats) available allowing developers to implement their own color models in the same manner as Microsoft XNA Game Studio and MonoGame. All in all this should allow image processing to be much more accessible to developers which has always been my goal from the start. From c69d2efae82bfdf8f399dd49dbf1f949b103fcaf Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 12 Aug 2017 21:03:46 +1000 Subject: [PATCH 30/36] Fix Appveyor link [skip ci] --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 70e48e52e6..7113d6ba1a 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ Built against .Net Standard 1.1 ImageSharp can be used in device, cloud, and emb | |Build Status|Code Coverage| |-------------|:----------:|:-----------:| |**Linux/Mac**|[![Build Status](https://travis-ci.org/SixLabors/ImageSharp.svg)](https://travis-ci.org/SixLabors/ImageSharp)|[![Code coverage](https://codecov.io/gh/SixLabors/ImageSharp/branch/master/graph/badge.svg)](https://codecov.io/gh/SixLabors/ImageSharp)| -|**Windows** |[![Build Status](https://ci.appveyor.com/api/projects/status/m9pn907xdah3ca39/branch/master?svg=true)](https://ci.appveyor.com/project/SixLabors/imagesharp/branch/master)|[![Code coverage](https://codecov.io/gh/SixLabors/ImageSharp/branch/master/graph/badge.svg)](https://codecov.io/gh/SixLabors/ImageSharp)| +|**Windows** |[![Build Status](https://ci.appveyor.com/api/projects/status/m9pn907xdah3ca39/branch/master?svg=true)](https://ci.appveyor.com/project/six-labors/imagesharp/branch/master)|[![Code coverage](https://codecov.io/gh/SixLabors/ImageSharp/branch/master/graph/badge.svg)](https://codecov.io/gh/SixLabors/ImageSharp)| ### Installation From 03ccebb0df60d81688b9d9977b0e59f5e555c9e1 Mon Sep 17 00:00:00 2001 From: Dirk Lemstra Date: Sat, 12 Aug 2017 13:19:44 +0200 Subject: [PATCH 31/36] Renamed variable. --- tests/ImageSharp.Tests/Image/ImageRotationTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/ImageSharp.Tests/Image/ImageRotationTests.cs b/tests/ImageSharp.Tests/Image/ImageRotationTests.cs index 44fa458fae..56cec42192 100644 --- a/tests/ImageSharp.Tests/Image/ImageRotationTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageRotationTests.cs @@ -45,9 +45,9 @@ namespace ImageSharp.Tests TestFile file = TestFile.Create(TestImages.Bmp.Car); using (Image image = Image.Load(file.FilePath)) { - Size expected = image.Bounds.Size; + Size original = image.Bounds.Size; image.Rotate(angle); - return (expected, image.Bounds.Size); + return (original, image.Bounds.Size); } } } From aabf8a2cbe0163a7cb12a7993a56e88ac0bc8ba6 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 12 Aug 2017 21:36:01 +1000 Subject: [PATCH 32/36] Update Travis build coverity details [skip ci] --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index af8d4ad9de..4ae163530e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,7 +26,7 @@ env: global: # The next declaration is the encrypted COVERITY_SCAN_TOKEN, created # via the "travis encrypt" command using the project repo's public key - - secure: "aim+fUyx7kDQQcAYV1mX16cvyFEYsxiW3y26xjmeuKzsOf6DIUK328pE8KnO50bMWhfVPjuW7jWc43jI+nbUeIW5018aFcjoOrEK2F8JvJ0UKtEo+ONchypJSXA2TSdL0iIlufMBepsmlBsSLkCwHCJYohYcueZV0u9NVPc3n282KLL8ItRZeSFG/cL/a2yrkFnTFhq9OtkUtP4CcVE7BOtzjfftNcn4Rup73e5JkLT7L9AZS7eCYkIYV0KRlT2pOa/FuOHlfP9NP+NVtd83GXUY2FKBsmN3EmrQgGDTfwfwcJjN5dqIqzkIXmLV8IKQ3aiW2//02pIe5VrdqHQG+EVMRcdpCWyKUkMj0g4rGYkqKCtVJojKtOR93ycOGUDc6+cMMoyn3J2qFydkp278dGWeLuwtGfD25fHXorqK1aL9/bGPcwdinrBmcwnuy1IECtuTkEfAPsb6O4nArnDsTEzeQxwa/MAicmpux//TNKgkQGqzCPeHKbl4vOfyyI6kCsf8edWv8fOSPvJUGvL14+/TZ6lY8S+30fosOmwMCe7xlbtcVlBVtOsKx/XUufrP2Vuptlc8INaq6++XtgpCoMLL0SJfBFQKZRmBGavv1Ztyf0aL6Qp303HKGTyXOEq2k18iJmukB6JcnEGVsaAyteGlruQIbPgHWbxhZSoJZPw=" + - secure: "rjMvEMN9rpvIXqXqCAAKzbHyABzr7E4wPU/dYJ/mHBqlCccFpQrEXVVM1MfRFXYuWZSaIioknhLATZjT5xvIYpTNM6D57z4OTmqeRHhYm80=" before_install: - echo -n | openssl s_client -connect scan.coverity.com:443 | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' | sudo tee -a /etc/ssl/certs/ca- @@ -34,7 +34,7 @@ before_install: addons: coverity_scan: project: - name: "JimBobSquarePants/ImageSharp" + name: "SixLabors/ImageSharp" description: "Build submitted via Travis CI" notification_email: james_south@hotmail.com build_command_prepend: "dotnet restore" From f8c2727c14b1168afc816f13bbdb62c11fb19a8f Mon Sep 17 00:00:00 2001 From: Devedse Date: Sat, 12 Aug 2017 15:31:55 +0200 Subject: [PATCH 33/36] Added tests for loading image with CRC Added tests for comparing Versioning image 1 and 2 Moved equality tests for images to seperate file --- .../Image/ImageDiscoverMimeType.cs | 2 +- .../ImageSharp.Tests/Image/ImageEqualTests.cs | 124 ++++++++++++++++++ .../ImageSharp.Tests/Image/ImageLoadTests.cs | 24 +--- .../ImageSharp.Tests/Image/ImageSaveTests.cs | 2 +- tests/ImageSharp.Tests/Image/ImageTests.cs | 2 +- tests/ImageSharp.Tests/TestImages.cs | 6 +- .../TestImages/Formats/Png/versioning-1_1.png | Bin 0 -> 22781 bytes .../TestImages/Formats/Png/versioning-1_2.png | Bin 0 -> 8165 bytes 8 files changed, 137 insertions(+), 23 deletions(-) create mode 100644 tests/ImageSharp.Tests/Image/ImageEqualTests.cs create mode 100644 tests/ImageSharp.Tests/TestImages/Formats/Png/versioning-1_1.png create mode 100644 tests/ImageSharp.Tests/TestImages/Formats/Png/versioning-1_2.png diff --git a/tests/ImageSharp.Tests/Image/ImageDiscoverMimeType.cs b/tests/ImageSharp.Tests/Image/ImageDiscoverMimeType.cs index 59a39c4542..d34fa22e25 100644 --- a/tests/ImageSharp.Tests/Image/ImageDiscoverMimeType.cs +++ b/tests/ImageSharp.Tests/Image/ImageDiscoverMimeType.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // diff --git a/tests/ImageSharp.Tests/Image/ImageEqualTests.cs b/tests/ImageSharp.Tests/Image/ImageEqualTests.cs new file mode 100644 index 0000000000..5e156f5436 --- /dev/null +++ b/tests/ImageSharp.Tests/Image/ImageEqualTests.cs @@ -0,0 +1,124 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System; + using System.IO; + + using ImageSharp.Formats; + using ImageSharp.IO; + using ImageSharp.Tests; + using Moq; + using Xunit; + + public class ImageEqualTests + { + //private readonly Mock fileSystem; + //private Image returnImage; + //private Mock localDecoder; + //private readonly string FilePath; + //private readonly Mock localMimeTypeDetector; + //private readonly Mock localImageFormatMock; + + //public Configuration LocalConfiguration { get; private set; } + //public byte[] Marker { get; private set; } + //public MemoryStream DataStream { get; private set; } + //public byte[] DecodedData { get; private set; } + + public ImageEqualTests() + { + //this.returnImage = new Image(1, 1); + + //this.localImageFormatMock = new Mock(); + + //this.localDecoder = new Mock(); + //this.localMimeTypeDetector = new Mock(); + //this.localMimeTypeDetector.Setup(x => x.HeaderSize).Returns(1); + //this.localMimeTypeDetector.Setup(x => x.DetectFormat(It.IsAny>())).Returns(localImageFormatMock.Object); + + //this.localDecoder.Setup(x => x.Decode(It.IsAny(), It.IsAny())) + + // .Callback((c, s) => + // { + // using (var ms = new MemoryStream()) + // { + // s.CopyTo(ms); + // this.DecodedData = ms.ToArray(); + // } + // }) + // .Returns(this.returnImage); + + //this.fileSystem = new Mock(); + + //this.LocalConfiguration = new Configuration() + //{ + // FileSystem = this.fileSystem.Object + //}; + //this.LocalConfiguration.AddImageFormatDetector(this.localMimeTypeDetector.Object); + //this.LocalConfiguration.SetDecoder(localImageFormatMock.Object, this.localDecoder.Object); + + //TestFormat.RegisterGloablTestFormat(); + //this.Marker = Guid.NewGuid().ToByteArray(); + //this.DataStream = TestFormat.GlobalTestFormat.CreateStream(this.Marker); + + //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] + public void TestsThatVimImagesAreEqual() + { + var image1Provider = TestImageProvider.File(TestImages.Png.VimImage1); + var image2Provider = TestImageProvider.File(TestImages.Png.VimImage2); + + using (Image img1 = image1Provider.GetImage()) + using (Image img2 = image2Provider.GetImage()) + { + bool imagesEqual = AreImagesEqual(img1, img2); + Assert.True(imagesEqual); + } + } + + [Fact] + public void TestsThatVersioningImagesAreEqual() + { + var image1Provider = TestImageProvider.File(TestImages.Png.VersioningImage1); + var image2Provider = TestImageProvider.File(TestImages.Png.VersioningImage2); + + using (Image img1 = image1Provider.GetImage()) + using (Image img2 = image2Provider.GetImage()) + { + bool imagesEqual = AreImagesEqual(img1, img2); + //Assert.True(imagesEqual); + } + } + + private bool AreImagesEqual(Image img1, Image img2) + { + Assert.Equal(img1.Width, img2.Width); + Assert.Equal(img1.Height, img2.Height); + + for (int y = 0; y < img1.Height; y++) + { + for (int x = 0; x < img1.Width; x++) + { + Rgba32 pixel1 = img1[x, y]; + Rgba32 pixel2 = img2[x, y]; + + if (pixel1 != pixel2) + { + return false; + } + } + } + + return true; + } + } +} diff --git a/tests/ImageSharp.Tests/Image/ImageLoadTests.cs b/tests/ImageSharp.Tests/Image/ImageLoadTests.cs index d821f4bc76..4573d0d9de 100644 --- a/tests/ImageSharp.Tests/Image/ImageLoadTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageLoadTests.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // @@ -321,27 +321,13 @@ namespace ImageSharp.Tests } [Fact] - public void TestThatTwoImagesAreEqual() + public void LoadsImageWithoutThrowingCrcException() { - var image1Provider = TestImageProvider.File(TestImages.Png.VimImage1); - var image2Provider = TestImageProvider.File(TestImages.Png.VimImage2); + var image1Provider = TestImageProvider.File(TestImages.Png.VersioningImage1); - using (Image img1 = image1Provider.GetImage()) - using (Image img2 = image2Provider.GetImage()) + using (Image img = image1Provider.GetImage()) { - Assert.Equal(img1.Width, img2.Width); - Assert.Equal(img1.Height, img2.Height); - - for (int y = 0; y < img1.Height; y++) - { - for (int x = 0; x < img1.Width; x++) - { - Rgba32 pixel1 = img1[x, y]; - Rgba32 pixel2 = img2[x, y]; - - Assert.Equal(pixel1, pixel2); - } - } + Assert.Equal(166036, img.Pixels.Length); } } diff --git a/tests/ImageSharp.Tests/Image/ImageSaveTests.cs b/tests/ImageSharp.Tests/Image/ImageSaveTests.cs index eecb983800..ad8a5cc7d5 100644 --- a/tests/ImageSharp.Tests/Image/ImageSaveTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageSaveTests.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // diff --git a/tests/ImageSharp.Tests/Image/ImageTests.cs b/tests/ImageSharp.Tests/Image/ImageTests.cs index 3157c27d8d..217bf37fe8 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 7d552d1094..46887d721d 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -41,6 +41,9 @@ namespace ImageSharp.Tests public const string VimImage1 = "Png/vim16x16_1.png"; public const string VimImage2 = "Png/vim16x16_2.png"; + public const string VersioningImage1 = "Png/versioning-1_1.png"; + public const string VersioningImage2 = "Png/versioning-1_2.png"; + public static class Bad { // Odd chunk lengths @@ -53,7 +56,8 @@ namespace ImageSharp.Tests P1, Pd, Blur, Splash, Cross, Powerpoint, SplashInterlaced, Interlaced, Filter0, Filter1, Filter2, Filter3, Filter4, - FilterVar, VimImage1, VimImage2 + FilterVar, VimImage1, VimImage2, VersioningImage1, + VersioningImage2 }; } diff --git a/tests/ImageSharp.Tests/TestImages/Formats/Png/versioning-1_1.png b/tests/ImageSharp.Tests/TestImages/Formats/Png/versioning-1_1.png new file mode 100644 index 0000000000000000000000000000000000000000..c13f98fd16ed8318e88c2e550f31cca566aabdf9 GIT binary patch literal 22781 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

dm@8bx=l{7XaO`ql1=#NSsHpvzv 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 S+^K(l)=P`ae<~L<@ckcgU$2w^ literal 0 HcmV?d00001 From 43c92a1389c9d0adcf9275b87539813c3f3a9254 Mon Sep 17 00:00:00 2001 From: Devedse Date: Sat, 12 Aug 2017 15:33:12 +0200 Subject: [PATCH 34/36] Removed commented out code --- .../ImageSharp.Tests/Image/ImageEqualTests.cs | 62 ------------------- 1 file changed, 62 deletions(-) diff --git a/tests/ImageSharp.Tests/Image/ImageEqualTests.cs b/tests/ImageSharp.Tests/Image/ImageEqualTests.cs index 5e156f5436..422e61d6d0 100644 --- a/tests/ImageSharp.Tests/Image/ImageEqualTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageEqualTests.cs @@ -5,72 +5,10 @@ namespace ImageSharp.Tests { - using System; - using System.IO; - - using ImageSharp.Formats; - using ImageSharp.IO; - using ImageSharp.Tests; - using Moq; using Xunit; public class ImageEqualTests { - //private readonly Mock fileSystem; - //private Image returnImage; - //private Mock localDecoder; - //private readonly string FilePath; - //private readonly Mock localMimeTypeDetector; - //private readonly Mock localImageFormatMock; - - //public Configuration LocalConfiguration { get; private set; } - //public byte[] Marker { get; private set; } - //public MemoryStream DataStream { get; private set; } - //public byte[] DecodedData { get; private set; } - - public ImageEqualTests() - { - //this.returnImage = new Image(1, 1); - - //this.localImageFormatMock = new Mock(); - - //this.localDecoder = new Mock(); - //this.localMimeTypeDetector = new Mock(); - //this.localMimeTypeDetector.Setup(x => x.HeaderSize).Returns(1); - //this.localMimeTypeDetector.Setup(x => x.DetectFormat(It.IsAny>())).Returns(localImageFormatMock.Object); - - //this.localDecoder.Setup(x => x.Decode(It.IsAny(), It.IsAny())) - - // .Callback((c, s) => - // { - // using (var ms = new MemoryStream()) - // { - // s.CopyTo(ms); - // this.DecodedData = ms.ToArray(); - // } - // }) - // .Returns(this.returnImage); - - //this.fileSystem = new Mock(); - - //this.LocalConfiguration = new Configuration() - //{ - // FileSystem = this.fileSystem.Object - //}; - //this.LocalConfiguration.AddImageFormatDetector(this.localMimeTypeDetector.Object); - //this.LocalConfiguration.SetDecoder(localImageFormatMock.Object, this.localDecoder.Object); - - //TestFormat.RegisterGloablTestFormat(); - //this.Marker = Guid.NewGuid().ToByteArray(); - //this.DataStream = TestFormat.GlobalTestFormat.CreateStream(this.Marker); - - //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] public void TestsThatVimImagesAreEqual() { From 5cf62d2421a4675d3217049751839128a2a7ec8e Mon Sep 17 00:00:00 2001 From: Devedse Date: Sat, 12 Aug 2017 17:10:35 +0200 Subject: [PATCH 35/36] Commented in the Assert.True() to make the test fail. --- tests/ImageSharp.Tests/Image/ImageEqualTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ImageSharp.Tests/Image/ImageEqualTests.cs b/tests/ImageSharp.Tests/Image/ImageEqualTests.cs index 422e61d6d0..3990060127 100644 --- a/tests/ImageSharp.Tests/Image/ImageEqualTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageEqualTests.cs @@ -33,7 +33,7 @@ namespace ImageSharp.Tests using (Image img2 = image2Provider.GetImage()) { bool imagesEqual = AreImagesEqual(img1, img2); - //Assert.True(imagesEqual); + Assert.True(imagesEqual); } } From a710ccca3a2eb993bdc591b701660a4d6c66c92f Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 13 Aug 2017 19:01:06 +1000 Subject: [PATCH 36/36] Fix marker skipping bug when decoding certain images --- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 102 +++++++++---------- tests/ImageSharp.Tests/FileTestBase.cs | 4 +- 2 files changed, 50 insertions(+), 56 deletions(-) diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index 372ecdc2b0..d9df44a091 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -267,41 +267,35 @@ namespace ImageSharp.Formats /// The bytes to convert from. Cannot be null. /// The number of bytes per scanline /// The number of bits per value. - /// The resulting array. Is never null. + /// The resulting array. Is never null. /// is null. /// is less than or equals than zero. - private static byte[] ToArrayByBitsLength(byte[] source, int bytesPerScanline, int bits) + private static Span ToArrayByBitsLength(Span source, int bytesPerScanline, int bits) { Guard.NotNull(source, nameof(source)); Guard.MustBeGreaterThan(bits, 0, nameof(bits)); - byte[] result; - - if (bits < 8) + if (bits >= 8) { - result = new byte[bytesPerScanline * 8 / bits]; - int mask = 0xFF >> (8 - bits); - int resultOffset = 0; + return source; + } - // ReSharper disable once ForCanBeConvertedToForeach - // First byte is the marker so skip. - for (int i = 1; i < bytesPerScanline; i++) + byte[] result = new byte[bytesPerScanline * 8 / bits]; + int mask = 0xFF >> (8 - bits); + int resultOffset = 0; + + for (int i = 0; i < bytesPerScanline; i++) + { + byte b = source[i]; + for (int shift = 0; shift < 8; shift += bits) { - byte b = source[i]; - for (int shift = 0; shift < 8; shift += bits) - { - int colorIndex = (b >> (8 - bits - shift)) & mask; + int colorIndex = (b >> (8 - bits - shift)) & mask; - result[resultOffset] = (byte)colorIndex; + result[resultOffset] = (byte)colorIndex; - resultOffset++; - } + resultOffset++; } } - else - { - result = source; - } return result; } @@ -584,13 +578,15 @@ namespace ImageSharp.Formats { var color = default(TPixel); Span rowSpan = pixels.GetRowSpan(this.currentRow); + + // Trim the first marker byte from the buffer var scanlineBuffer = new Span(defilteredScanline, 1); switch (this.pngColorType) { case PngColorType.Grayscale: int factor = 255 / ((int)Math.Pow(2, this.header.BitDepth) - 1); - byte[] newScanline1 = ToArrayByBitsLength(defilteredScanline, this.bytesPerScanline, this.header.BitDepth); + Span newScanline1 = ToArrayByBitsLength(scanlineBuffer, this.bytesPerScanline, this.header.BitDepth); for (int x = 0; x < this.header.Width; x++) { byte intensity = (byte)(newScanline1[x] * factor); @@ -604,10 +600,10 @@ namespace ImageSharp.Formats for (int x = 0; x < this.header.Width; x++) { - int offset = 1 + (x * this.bytesPerPixel); + int offset = x * this.bytesPerPixel; - byte intensity = defilteredScanline[offset]; - byte alpha = defilteredScanline[offset + this.bytesPerSample]; + byte intensity = scanlineBuffer[offset]; + byte alpha = scanlineBuffer[offset + this.bytesPerSample]; color.PackFromRgba32(new Rgba32(intensity, intensity, intensity, alpha)); rowSpan[x] = color; @@ -617,7 +613,7 @@ namespace ImageSharp.Formats case PngColorType.Palette: - this.ProcessScanlineFromPalette(defilteredScanline, rowSpan); + this.ProcessScanlineFromPalette(scanlineBuffer, rowSpan); break; @@ -682,10 +678,10 @@ namespace ImageSharp.Formats /// The type of pixel we are expanding to /// The scanline /// Thecurrent output image row - private void ProcessScanlineFromPalette(byte[] defilteredScanline, Span row) + private void ProcessScanlineFromPalette(Span defilteredScanline, Span row) where TPixel : struct, IPixel { - byte[] newScanline = ToArrayByBitsLength(defilteredScanline, this.bytesPerScanline, this.header.BitDepth); + Span newScanline = ToArrayByBitsLength(defilteredScanline, this.bytesPerScanline, this.header.BitDepth); byte[] pal = this.palette; var color = default(TPixel); @@ -737,15 +733,15 @@ namespace ImageSharp.Formats { var color = default(TPixel); + // Trim the first marker byte from the buffer + var scanlineBuffer = new Span(defilteredScanline, 1); + switch (this.pngColorType) { case PngColorType.Grayscale: int factor = 255 / ((int)Math.Pow(2, this.header.BitDepth) - 1); - byte[] newScanline1 = ToArrayByBitsLength( - defilteredScanline, - this.bytesPerScanline, - this.header.BitDepth); - for (int x = pixelOffset, o = 1; x < this.header.Width; x += increment, o++) + Span newScanline1 = ToArrayByBitsLength(scanlineBuffer, this.bytesPerScanline, this.header.BitDepth); + for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o++) { byte intensity = (byte)(newScanline1[o] * factor); color.PackFromRgba32(new Rgba32(intensity, intensity, intensity)); @@ -756,10 +752,10 @@ namespace ImageSharp.Formats case PngColorType.GrayscaleWithAlpha: - for (int x = pixelOffset, o = 1; x < this.header.Width; x += increment, o += this.bytesPerPixel) + for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += this.bytesPerPixel) { - byte intensity = defilteredScanline[o]; - byte alpha = defilteredScanline[o + this.bytesPerSample]; + byte intensity = scanlineBuffer[o]; + byte alpha = scanlineBuffer[o + this.bytesPerSample]; color.PackFromRgba32(new Rgba32(intensity, intensity, intensity, alpha)); rowSpan[x] = color; } @@ -768,14 +764,14 @@ namespace ImageSharp.Formats case PngColorType.Palette: - byte[] newScanline = ToArrayByBitsLength(defilteredScanline, this.bytesPerScanline, this.header.BitDepth); + Span newScanline = ToArrayByBitsLength(scanlineBuffer, this.bytesPerScanline, this.header.BitDepth); var rgba = default(Rgba32); if (this.paletteAlpha != null && this.paletteAlpha.Length > 0) { // If the alpha palette is not null and has one or more entries, this means, that the image contains an alpha // channel and we should try to read it. - for (int x = pixelOffset, o = 1; x < this.header.Width; x += increment, o++) + for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o++) { int index = newScanline[o]; int offset = index * 3; @@ -791,7 +787,7 @@ namespace ImageSharp.Formats { rgba.A = 255; - for (int x = pixelOffset, o = 1; x < this.header.Width; x += increment, o++) + for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o++) { int index = newScanline[o]; int offset = index * 3; @@ -815,10 +811,8 @@ namespace ImageSharp.Formats using (var compressed = new Buffer(length)) { // TODO: Should we use pack from vector here instead? - this.From16BitTo8Bit(new Span(defilteredScanline, 1), compressed, length); - for (int x = pixelOffset, o = 0; - x < this.header.Width; - x += increment, o += 3) + this.From16BitTo8Bit(scanlineBuffer, compressed, length); + for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += 3) { rgba.R = compressed[o]; rgba.G = compressed[o + 1]; @@ -831,11 +825,11 @@ namespace ImageSharp.Formats } else { - for (int x = pixelOffset, o = 1; x < this.header.Width; x += increment, o += this.bytesPerPixel) + for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += this.bytesPerPixel) { - rgba.R = defilteredScanline[o]; - rgba.G = defilteredScanline[o + this.bytesPerSample]; - rgba.B = defilteredScanline[o + (2 * this.bytesPerSample)]; + rgba.R = scanlineBuffer[o]; + rgba.G = scanlineBuffer[o + this.bytesPerSample]; + rgba.B = scanlineBuffer[o + (2 * this.bytesPerSample)]; color.PackFromRgba32(rgba); rowSpan[x] = color; @@ -852,7 +846,7 @@ namespace ImageSharp.Formats using (var compressed = new Buffer(length)) { // TODO: Should we use pack from vector here instead? - this.From16BitTo8Bit(new Span(defilteredScanline, 1), compressed, length); + this.From16BitTo8Bit(scanlineBuffer, compressed, length); for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += 4) { rgba.R = compressed[o]; @@ -867,12 +861,12 @@ namespace ImageSharp.Formats } else { - for (int x = pixelOffset, o = 1; x < this.header.Width; x += increment, o += this.bytesPerPixel) + for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += this.bytesPerPixel) { - rgba.R = defilteredScanline[o]; - rgba.G = defilteredScanline[o + this.bytesPerSample]; - rgba.B = defilteredScanline[o + (2 * this.bytesPerSample)]; - rgba.A = defilteredScanline[o + (3 * this.bytesPerSample)]; + rgba.R = scanlineBuffer[o]; + rgba.G = scanlineBuffer[o + this.bytesPerSample]; + rgba.B = scanlineBuffer[o + (2 * this.bytesPerSample)]; + rgba.A = scanlineBuffer[o + (3 * this.bytesPerSample)]; color.PackFromRgba32(rgba); rowSpan[x] = color; diff --git a/tests/ImageSharp.Tests/FileTestBase.cs b/tests/ImageSharp.Tests/FileTestBase.cs index 51a1562f53..08ed69f3e2 100644 --- a/tests/ImageSharp.Tests/FileTestBase.cs +++ b/tests/ImageSharp.Tests/FileTestBase.cs @@ -79,8 +79,8 @@ namespace ImageSharp.Tests // TestFile.Create(TestImages.Bmp.NegHeight), // Perf: Enable for local testing only TestFile.Create(TestImages.Png.Splash), // TestFile.Create(TestImages.Png.Cross), // Perf: Enable for local testing only - // TestFile.Create(TestImages.Png.ChunkLength1), // Perf: Enable for local testing only - // TestFile.Create(TestImages.Png.ChunkLength2), // Perf: Enable for local testing only + // TestFile.Create(TestImages.Png.Bad.ChunkLength1), // Perf: Enable for local testing only + // TestFile.Create(TestImages.Png.Bad.ChunkLength2), // Perf: Enable for local testing only // TestFile.Create(TestImages.Png.Powerpoint), // Perf: Enable for local testing only // TestFile.Create(TestImages.Png.Blur), // Perf: Enable for local testing only // TestFile.Create(TestImages.Png.Indexed), // Perf: Enable for local testing only