diff --git a/ImageSharp.sln b/ImageSharp.sln index e546420ef..a584c5686 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 @@ -49,6 +49,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{7CC6 EndProject 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 Debug|Any CPU = Debug|Any CPU @@ -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 000000000..5797be0f5 --- /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 000000000..dab8d445c --- /dev/null +++ b/samples/ChangeDefaultEncoderOptions/Program.cs @@ -0,0 +1,20 @@ +using System; +using ImageSharp; +using ImageSharp.Formats; + +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.SetEncoder(ImageFormats.Jpeg, new ImageSharp.Formats.JpegEncoder() + { + Quality = 90, + IgnoreMetadata = true + }); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Common/Exceptions/ImageFormatException.cs b/src/ImageSharp/Common/Exceptions/ImageFormatException.cs index 70491ba22..32a085435 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 a59be9ca8..ef84a1e39 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/Configuration.cs b/src/ImageSharp/Configuration.cs index fa983d355..226d45132 100644 --- a/src/ImageSharp/Configuration.cs +++ b/src/ImageSharp/Configuration.cs @@ -6,8 +6,8 @@ namespace ImageSharp { using System; + using System.Collections.Concurrent; using System.Collections.Generic; - using System.Collections.ObjectModel; using System.Linq; using System.Threading.Tasks; @@ -17,22 +17,32 @@ namespace ImageSharp /// /// Provides initialization code which allows extending the library. /// - public class Configuration + public sealed class Configuration { /// /// A lazily initialized configuration default instance. /// - private static readonly Lazy Lazy = new Lazy(() => CreateDefaultInstance()); + private static readonly Lazy Lazy = new Lazy(CreateDefaultInstance); /// - /// An object that can be used to synchronize access to the . + /// The list of supported keyed to mime types. /// - private readonly object syncRoot = new object(); + private readonly ConcurrentDictionary mimeTypeEncoders = new ConcurrentDictionary(); /// - /// The list of supported . + /// The list of supported keyed to mime types. /// - private readonly List imageFormatsList = new List(); + private readonly ConcurrentDictionary mimeTypeDecoders = new ConcurrentDictionary(); + + /// + /// The list of supported s. + /// + private readonly List imageFormatDetectors = new List(); + + /// + /// The list of supported s. + /// + private readonly HashSet imageFormats = new HashSet(); /// /// Initializes a new instance of the class. @@ -44,12 +54,15 @@ namespace ImageSharp /// /// Initializes a new instance of the class. /// - /// The inital set of image formats. - public Configuration(params IImageFormat[] providers) + /// A collection of configuration modules to register + public Configuration(params IConfigurationModule[] configurationModules) { - foreach (IImageFormat p in providers) + if (configurationModules != null) { - this.AddImageFormat(p); + foreach (IConfigurationModule p in configurationModules) + { + p.Configure(this); + } } } @@ -58,21 +71,36 @@ namespace ImageSharp /// public static Configuration Default { get; } = Lazy.Value; - /// - /// Gets the collection of supported - /// - public IReadOnlyCollection ImageFormats => new ReadOnlyCollection(this.imageFormatsList); - /// /// Gets the global parallel options for processing tasks in parallel. /// 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 registered s. + /// + internal IEnumerable FormatDetectors => this.imageFormatDetectors; + + /// + /// Gets the currently registered s. + /// + internal IEnumerable> ImageDecoders => this.mimeTypeDecoders; + + /// + /// Gets the currently registered s. + /// + internal IEnumerable> ImageEncoders => this.mimeTypeEncoders; + + /// + /// Gets the currently registered s. + /// + internal IEnumerable ImageFormats => this.imageFormats; + #if !NETSTANDARD1_1 /// /// Gets or sets the fielsystem helper for accessing the local file system. @@ -81,126 +109,147 @@ namespace ImageSharp #endif /// - /// Adds a new to the collection of supported image formats. + /// Registers a new format provider. /// - /// The new format to add. + /// The configuration provider to call configure on. + public void Configure(IConfigurationModule configuration) + { + Guard.NotNull(configuration, nameof(configuration)); + configuration.Configure(this); + } + + /// + /// Registers a new format provider. + /// + /// The format to register as a well know format. 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."); + Guard.NotNull(format.MimeTypes, nameof(format.MimeTypes)); + Guard.NotNull(format.FileExtensions, nameof(format.FileExtensions)); + this.imageFormats.Add(format); + } - this.AddImageFormatLocked(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)); } /// - /// Creates the default instance, with Png, Jpeg, Gif and Bmp preregisterd (if they have been referenced) + /// For the specified mime type find the . /// - /// The default configuration of - internal static Configuration CreateDefaultInstance() + /// The mime-type to discover + /// The if found otherwise null + public IImageFormat FindFormatByMimeType(string mimeType) { - 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; + return this.imageFormats.FirstOrDefault(x => x.MimeTypes.Contains(mimeType, StringComparer.OrdinalIgnoreCase)); } /// - /// Tries the add image format. + /// Sets a specific image encoder as the encoder for a specific image format. /// - /// Name of the type. - /// True if type discoverd and is a valid - internal bool TryAddImageFormat(string typeName) + /// The image format to register the encoder for. + /// The encoder to use, + public void SetEncoder(IImageFormat imageFormat, IImageEncoder encoder) { - Type type = Type.GetType(typeName, false); - if (type != null) - { - 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; - } - } + Guard.NotNull(imageFormat, nameof(imageFormat)); + Guard.NotNull(encoder, nameof(encoder)); + this.AddImageFormat(imageFormat); + this.mimeTypeEncoders.AddOrUpdate(imageFormat, encoder, (s, e) => encoder); + } - return false; + /// + /// 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.AddImageFormat(imageFormat); + this.mimeTypeDecoders.AddOrUpdate(imageFormat, decoder, (s, e) => decoder); } /// - /// Adds image format. The class is locked to make it thread safe. + /// Removes all the registered image format detectors. /// - /// The image format. - private void AddImageFormatLocked(IImageFormat format) + public void ClearImageFormatDetectors() { - lock (this.syncRoot) - { - if (this.GuardDuplicate(format)) - { - this.imageFormatsList.Add(format); + this.imageFormatDetectors.Clear(); + } - this.SetMaxHeaderSize(); - } - } + /// + /// Adds a new detector for detecting mime types. + /// + /// The detector to add + public void AddImageFormatDetector(IImageFormatDetector detector) + { + Guard.NotNull(detector, nameof(detector)); + this.imageFormatDetectors.Add(detector); + this.SetMaxHeaderSize(); } /// - /// Checks to ensure duplicate image formats are not added. + /// Creates the default instance with the following s preregistered: + /// + /// + /// + /// /// - /// 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)); - } + return new Configuration( + new PngConfigurationModule(), + new JpegConfigurationModule(), + new GifConfigurationModule(), + new BmpConfigurationModule()); + } - // ReSharper disable once ConvertClosureToMethodGroup - // Prevents method group allocation - if (format.SupportedExtensions.Any(e => string.IsNullOrWhiteSpace(e))) + /// + /// For the specified mime type find the decoder. + /// + /// The format to discover + /// The if found otherwise null + internal IImageDecoder FindDecoder(IImageFormat format) + { + Guard.NotNull(format, nameof(format)); + if (this.mimeTypeDecoders.TryGetValue(format, out IImageDecoder decoder)) { - throw new ArgumentException("The supported extensions should not contain empty values.", nameof(format)); + return decoder; } - // 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; - } + return null; + } - if (imageFormat.SupportedExtensions.Intersect(format.SupportedExtensions, StringComparer.OrdinalIgnoreCase).Any()) - { - return false; - } + /// + /// For the specified mime type find the encoder. + /// + /// The format to discover + /// The if found otherwise null + internal IImageEncoder FindEncoder(IImageFormat format) + { + Guard.NotNull(format, nameof(format)); + if (this.mimeTypeEncoders.TryGetValue(format, out IImageEncoder encoder)) + { + return encoder; } - return true; + return null; } /// - /// Sets max header size. + /// Sets the max header size. /// private void SetMaxHeaderSize() { - this.MaxHeaderSize = this.imageFormatsList.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 000000000..f70ff1a56 --- /dev/null +++ b/src/ImageSharp/Formats/Bmp/BmpConfigurationModule.cs @@ -0,0 +1,21 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + /// + /// Registers the image encoders, decoders and mime type detectors for the bmp format. + /// + public sealed class BmpConfigurationModule : IConfigurationModule + { + /// + public void Configure(Configuration config) + { + config.SetEncoder(ImageFormats.Bitmap, new BmpEncoder()); + config.SetDecoder(ImageFormats.Bitmap, new BmpDecoder()); + config.AddImageFormatDetector(new BmpImageFormatDetector()); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Bmp/BmpConstants.cs b/src/ImageSharp/Formats/Bmp/BmpConstants.cs new file mode 100644 index 000000000..d394b61f6 --- /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 file extensions 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 9090e9a8c..5baf1b1a5 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; @@ -25,16 +26,16 @@ 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 sealed class BmpDecoder : IImageDecoder, IBmpDecoderOptions { /// - public Image Decode(Configuration configuration, Stream stream, IDecoderOptions options) + public Image Decode(Configuration configuration, Stream stream) where TPixel : struct, IPixel { 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 997a77d6c..817d00f7e 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 dc2bc0e97..dfba0b41c 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; @@ -14,28 +15,18 @@ 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 sealed class BmpEncoder : IImageEncoder, IBmpEncoderOptions { - /// - public void Encode(Image image, Stream stream, IEncoderOptions options) - where TPixel : struct, IPixel - { - IBmpEncoderOptions bmpOptions = BmpEncoderOptions.Create(options); - - this.Encode(image, stream, bmpOptions); - } - /// - /// Encodes the image to the specified stream from the . + /// Gets or sets the number of bits per pixel. /// - /// 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 BmpBitsPerPixel BitsPerPixel { get; set; } = BmpBitsPerPixel.Pixel24; + + /// + public void Encode(Image image, Stream stream) where TPixel : struct, IPixel { - BmpEncoderCore encoder = new BmpEncoderCore(options); + 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 617edde8e..e41c29501 100644 --- a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs @@ -18,22 +18,22 @@ namespace ImageSharp.Formats internal sealed class BmpEncoderCore { /// - /// The options for the encoder. + /// The amount to pad each row by. /// - private readonly IBmpEncoderOptions options; + private int padding; /// - /// The amount to pad each row by. + /// Gets or sets the number of bits per pixel. /// - private int padding; + private BmpBitsPerPixel bitsPerPixel; /// /// Initializes a new instance of the class. /// - /// The options for the encoder. + /// The encoder options public BmpEncoderCore(IBmpEncoderOptions options) { - this.options = options ?? new BmpEncoderOptions(); + this.bitsPerPixel = options.BitsPerPixel; } /// @@ -49,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.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 +136,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 a0f9ff8e0..000000000 --- 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/BmpFileHeader.cs b/src/ImageSharp/Formats/Bmp/BmpFileHeader.cs index 4be602f4b..f9b20a48f 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/BmpFormat.cs b/src/ImageSharp/Formats/Bmp/BmpFormat.cs index bf73d3162..fb65f34d7 100644 --- a/src/ImageSharp/Formats/Bmp/BmpFormat.cs +++ b/src/ImageSharp/Formats/Bmp/BmpFormat.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // @@ -8,34 +8,20 @@ namespace ImageSharp.Formats using System.Collections.Generic; /// - /// Encapsulates the means to encode and decode bitmap images. + /// Registers the image encoders, decoders and mime type detectors for the jpeg format. /// - public class BmpFormat : IImageFormat + internal sealed class BmpFormat : IImageFormat { /// - public string MimeType => "image/bmp"; + public string Name => "BMP"; /// - public string Extension => "bmp"; + public string DefaultMimeType => "image/bmp"; /// - public IEnumerable SupportedExtensions => new string[] { "bmp", "dip" }; + public IEnumerable MimeTypes => BmpConstants.MimeTypes; /// - 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 - } + 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 new file mode 100644 index 000000000..697ee0f98 --- /dev/null +++ b/src/ImageSharp/Formats/Bmp/BmpImageFormatDetector.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; + + /// + /// Detects bmp file headers + /// + public sealed class BmpImageFormatDetector : IImageFormatDetector + { + /// + public int HeaderSize => 2; + + /// + public IImageFormat DetectFormat(ReadOnlySpan header) + { + if (this.IsSupportedFileFormat(header)) + { + return ImageFormats.Bitmap; + } + + return null; + } + + 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/Bmp/BmpInfoHeader.cs b/src/ImageSharp/Formats/Bmp/BmpInfoHeader.cs index e652cb504..dc6a489d3 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/Bmp/IBmpDecoderOptions.cs b/src/ImageSharp/Formats/Bmp/IBmpDecoderOptions.cs new file mode 100644 index 000000000..9285b9cf7 --- /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 index 6cf37cbae..dd17043fa 100644 --- a/src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs +++ b/src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs @@ -1,14 +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; + /// - /// Encapsulates the options for the . + /// Configuration options for use during bmp encoding /// - public interface IBmpEncoderOptions : IEncoderOptions + /// The encoder can currently only write 24-bit rgb images to streams. + internal interface IBmpEncoderOptions { /// /// Gets the number of bits per pixel. diff --git a/src/ImageSharp/Formats/DecoderOptions.cs b/src/ImageSharp/Formats/DecoderOptions.cs deleted file mode 100644 index 5257b07b3..000000000 --- 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 27a7e9781..000000000 --- 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/GifConfigurationModule.cs b/src/ImageSharp/Formats/Gif/GifConfigurationModule.cs new file mode 100644 index 000000000..ee134d66c --- /dev/null +++ b/src/ImageSharp/Formats/Gif/GifConfigurationModule.cs @@ -0,0 +1,22 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + /// + /// Registers the image encoders, decoders and mime type detectors for the gif format. + /// + public sealed class GifConfigurationModule : IConfigurationModule + { + /// + public void Configure(Configuration config) + { + config.SetEncoder(ImageFormats.Gif, new GifEncoder()); + config.SetDecoder(ImageFormats.Gif, new GifDecoder()); + + 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 4af291c2b..9bec6c48f 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 gif. + /// + public static readonly IEnumerable MimeTypes = new[] { "image/gif" }; + + /// + /// The list of file extensions that equate to a gif. + /// + public static readonly IEnumerable FileExtensions = new[] { "gif" }; } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Gif/GifDecoder.cs b/src/ImageSharp/Formats/Gif/GifDecoder.cs index 88aaccf6a..927289094 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoder.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoder.cs @@ -6,37 +6,32 @@ 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. /// - public class GifDecoder : IImageDecoder + public sealed class GifDecoder : IImageDecoder, IGifDecoderOptions { - /// - public Image Decode(Configuration configuration, Stream stream, IDecoderOptions options) - - where TPixel : struct, IPixel - { - IGifDecoderOptions gifOptions = GifDecoderOptions.Create(options); - - 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 Image Decode(Configuration configuration, Stream stream) where TPixel : struct, IPixel { - return new GifDecoderCore(options, configuration).Decode(stream); + 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 ef0331c0d..bb230beac 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 { /// @@ -26,11 +26,6 @@ namespace ImageSharp.Formats /// private readonly byte[] buffer = new byte[16]; - /// - /// The decoder options. - /// - private readonly IGifDecoderOptions options; - /// /// The global configuration. /// @@ -84,14 +79,25 @@ namespace ImageSharp.Formats /// /// Initializes a new instance of the class. /// - /// The decoder options. /// The configuration. - public GifDecoderCore(IGifDecoderOptions options, Configuration configuration) + /// The decoder options. + public GifDecoderCore(Configuration configuration, IGifDecoderOptions options) { - this.options = options ?? new GifDecoderOptions(); + this.TextEncoding = options.TextEncoding ?? GifConstants.DefaultEncoding; + this.IgnoreMetadata = options.IgnoreMetadata; 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. /// @@ -269,7 +275,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; @@ -280,7 +286,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 @@ -364,8 +370,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 bc7709f75..000000000 --- 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 b5cadd834..b48db5635 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoder.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoder.cs @@ -6,35 +6,47 @@ 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. /// - public class GifEncoder : IImageEncoder + public sealed class GifEncoder : IImageEncoder, IGifEncoderOptions { - /// - public void Encode(Image image, Stream stream, IEncoderOptions options) - where TPixel : struct, IPixel - { - IGifEncoderOptions gifOptions = GifEncoderOptions.Create(options); + /// + /// Gets or sets a value indicating whether the metadata should be ignored when the image is being encoded. + /// + public bool IgnoreMetadata { get; set; } = false; - this.Encode(image, stream, gifOptions); - } + /// + /// Gets or sets the encoding that should be used when writing comments. + /// + public Encoding TextEncoding { get; set; } = GifConstants.DefaultEncoding; /// - /// Encodes the image to the specified stream from the . + /// 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. /// - /// 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 int PaletteSize { get; set; } = 0; + + /// + /// 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); encoder.Encode(image, stream); } } diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs index 5ef7ca165..81b3cfba4 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. /// @@ -40,19 +35,44 @@ namespace ImageSharp.Formats /// private bool hasFrames; + /// + /// Gets the TextEncoding + /// + private Encoding textEncoding; + + /// + /// Gets or sets the quantizer for reducing the color count. + /// + private IQuantizer quantizer; + + /// + /// Gets or sets the threshold. + /// + private byte threshold; + + /// + /// Gets or sets the size of the color palette to use. + /// + private int paletteSize; + + /// + /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded. + /// + private bool ignoreMetadata; + /// /// Initializes a new instance of the class. /// /// The options for the encoder. public GifEncoderCore(IGifEncoderOptions options) { - this.options = options ?? new GifEncoderOptions(); - } + this.textEncoding = options.TextEncoding ?? GifConstants.DefaultEncoding; - /// - /// Gets or sets the quantizer for reducing the color count. - /// - public IQuantizer Quantizer { get; set; } + 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 . @@ -66,26 +86,26 @@ 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; - quality = quality > 0 ? quality.Clamp(1, 256) : 256; + // Ensure that pallete size can be set but has a fallback. + 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(); // 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; - QuantizedImage quantized = ditheredQuantizer.Quantize(image, quality); + // Quantize the image returning a palette. + QuantizedImage quantized = ditheredQuantizer.Quantize(image, paletteSize); int index = this.GetTransparentIndex(quantized); @@ -111,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); @@ -240,7 +260,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 +271,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 5d7c6e40b..000000000 --- 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 index 2851b0b6b..ea7b72d32 100644 --- a/src/ImageSharp/Formats/Gif/GifFormat.cs +++ b/src/ImageSharp/Formats/Gif/GifFormat.cs @@ -8,38 +8,20 @@ namespace ImageSharp.Formats using System.Collections.Generic; /// - /// Encapsulates the means to encode and decode gif images. + /// Registers the image encoders, decoders and mime type detectors for the jpeg format. /// - public class GifFormat : IImageFormat + internal sealed class GifFormat : IImageFormat { /// - public string Extension => "gif"; + public string Name => "GIF"; /// - public string MimeType => "image/gif"; + public string DefaultMimeType => "image/gif"; /// - public IEnumerable SupportedExtensions => new string[] { "gif" }; + public IEnumerable MimeTypes => GifConstants.MimeTypes; /// - 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 - } + 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 new file mode 100644 index 000000000..04fcfc516 --- /dev/null +++ b/src/ImageSharp/Formats/Gif/GifImageFormatDetector.cs @@ -0,0 +1,41 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using System; + + /// + /// Detects gif file headers + /// + public sealed class GifImageFormatDetector : IImageFormatDetector + { + /// + public int HeaderSize => 6; + + /// + public IImageFormat DetectFormat(ReadOnlySpan header) + { + if (this.IsSupportedFileFormat(header)) + { + return ImageFormats.Gif; + } + + return null; + } + + private bool IsSupportedFileFormat(ReadOnlySpan header) + { + // TODO: This should be in constants + 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 + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Gif/IGifDecoderOptions.cs b/src/ImageSharp/Formats/Gif/IGifDecoderOptions.cs index 729bf1d11..caaa8932b 100644 --- a/src/ImageSharp/Formats/Gif/IGifDecoderOptions.cs +++ b/src/ImageSharp/Formats/Gif/IGifDecoderOptions.cs @@ -1,17 +1,26 @@ -// +// // 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; /// - /// Encapsulates the options for the . + /// Decoder for generating an image out of a gif encoded stream. /// - public interface IGifDecoderOptions : IDecoderOptions + 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. /// diff --git a/src/ImageSharp/Formats/Gif/IGifEncoderOptions.cs b/src/ImageSharp/Formats/Gif/IGifEncoderOptions.cs index c1d6b7ad8..c38ec7e45 100644 --- a/src/ImageSharp/Formats/Gif/IGifEncoderOptions.cs +++ b/src/ImageSharp/Formats/Gif/IGifEncoderOptions.cs @@ -1,29 +1,36 @@ -// +// // 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 Quantizers; + using ImageSharp.PixelFormats; + using ImageSharp.Quantizers; /// - /// Encapsulates the options for the . + /// The configuration options used for encoding gifs /// - public interface IGifEncoderOptions : IEncoderOptions + 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 quality of output for images. + /// Gets 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. - int Quality { get; } + int PaletteSize { get; } /// /// Gets the transparency threshold. @@ -33,6 +40,6 @@ namespace ImageSharp.Formats /// /// Gets the quantizer for reducing the color count. /// - IQuantizer Quantizer { get; } + IQuantizer Quantizer { get; } } } diff --git a/src/ImageSharp/Formats/Gif/ImageExtensions.cs b/src/ImageSharp/Formats/Gif/ImageExtensions.cs index d64203f6c..ea9c9b504 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/IEncoderOptions.cs b/src/ImageSharp/Formats/IEncoderOptions.cs deleted file mode 100644 index 0fd3d1c43..000000000 --- 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 4fd25df13..66eabb1b8 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; @@ -21,9 +22,8 @@ namespace ImageSharp.Formats /// 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 a28511c17..4ad41ebc2 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; @@ -21,8 +22,7 @@ namespace ImageSharp.Formats /// 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 index de2e400fa..d6ddc0b0b 100644 --- a/src/ImageSharp/Formats/IImageFormat.cs +++ b/src/ImageSharp/Formats/IImageFormat.cs @@ -8,53 +8,28 @@ 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 + /// Describes an image format. /// public interface IImageFormat { /// - /// Gets the standard identifier used on the Internet to indicate the type of data that a file contains. + /// Gets the name that describes this image format. /// - string MimeType { get; } + string Name { get; } /// - /// Gets the default file extension for this format. + /// Gets the default mimetype that the image foramt uses /// - string Extension { get; } + string DefaultMimeType { get; } /// - /// Gets the supported file extensions for this format. + /// Gets all the mimetypes that have been used by this image foramt. /// - /// - /// The supported file extension. - /// - IEnumerable SupportedExtensions { get; } + IEnumerable MimeTypes { get; } /// - /// Gets the image encoder for encoding an image from a stream. + /// Gets the file extensions this image format commonly uses. /// - 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); + IEnumerable FileExtensions { get; } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/IImageFormatDetector.cs b/src/ImageSharp/Formats/IImageFormatDetector.cs new file mode 100644 index 000000000..a53da07e8 --- /dev/null +++ b/src/ImageSharp/Formats/IImageFormatDetector.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 IImageFormatDetector + { + /// + /// 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 + IImageFormat DetectFormat(ReadOnlySpan header); + } +} diff --git a/src/ImageSharp/Formats/IDecoderOptions.cs b/src/ImageSharp/Formats/Jpeg/IJpegDecoderOptions.cs similarity index 53% rename from src/ImageSharp/Formats/IDecoderOptions.cs rename to src/ImageSharp/Formats/Jpeg/IJpegDecoderOptions.cs index cdfd90d5e..6830e2e4a 100644 --- a/src/ImageSharp/Formats/IDecoderOptions.cs +++ b/src/ImageSharp/Formats/Jpeg/IJpegDecoderOptions.cs @@ -1,14 +1,20 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // -namespace ImageSharp +namespace ImageSharp.Formats { + using System; + using System.Collections.Generic; + using System.IO; + + using ImageSharp.PixelFormats; + /// - /// Encapsulates the shared decoder options. + /// Image decoder for generating an image out of a jpg stream. /// - public interface IDecoderOptions + internal interface IJpegDecoderOptions { /// /// Gets a value indicating whether the metadata should be ignored when the image is being decoded. diff --git a/src/ImageSharp/Formats/Jpeg/IJpegEncoderOptions.cs b/src/ImageSharp/Formats/Jpeg/IJpegEncoderOptions.cs index a54517965..947c98ee2 100644 --- a/src/ImageSharp/Formats/Jpeg/IJpegEncoderOptions.cs +++ b/src/ImageSharp/Formats/Jpeg/IJpegEncoderOptions.cs @@ -1,15 +1,26 @@ -// +// // 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; + /// - /// Encapsulates the options for the . + /// Encoder for writing the data image to a stream in jpeg format. /// - public interface IJpegEncoderOptions : IEncoderOptions + 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). diff --git a/src/ImageSharp/Formats/Jpeg/ImageExtensions.cs b/src/ImageSharp/Formats/Jpeg/ImageExtensions.cs index 420af6b74..8fbf9e5a7 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/JpegConfigurationModule.cs b/src/ImageSharp/Formats/Jpeg/JpegConfigurationModule.cs new file mode 100644 index 000000000..bb8c4e83f --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/JpegConfigurationModule.cs @@ -0,0 +1,22 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + /// + /// Registers the image encoders, decoders and mime type detectors for the jpeg format. + /// + public sealed 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/JpegConstants.cs b/src/ImageSharp/Formats/Jpeg/JpegConstants.cs index dcda39842..99c0399dc 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 file extensions 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 33d82ace8..38160c148 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs @@ -5,7 +5,6 @@ namespace ImageSharp.Formats { - using System; using System.IO; using ImageSharp.PixelFormats; @@ -13,22 +12,24 @@ namespace ImageSharp.Formats /// /// Image decoder for generating an image out of a jpg stream. /// - public class JpegDecoder : IImageDecoder + public sealed class JpegDecoder : IImageDecoder, IJpegDecoderOptions { + /// + /// 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 Image Decode(Configuration configuration, Stream stream) where TPixel : struct, IPixel { Guard.NotNull(stream, "stream"); - // using (var decoder = new JpegDecoderCore(options, configuration)) - // { - // return decoder.Decode(stream); - // } - using (var decoder = new Jpeg.Port.JpegDecoderCore(options, configuration)) + // using (var decoder = new JpegDecoderCore(configuration, this)) + using (var decoder = new Jpeg.Port.JpegDecoderCore(configuration, this)) { return decoder.Decode(stream); } } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 971684371..0ce927e51 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 @@ -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,12 @@ namespace ImageSharp.Formats /// /// Initializes a new instance of the class. /// - /// The decoder options. /// The configuration. - public JpegDecoderCore(IDecoderOptions options, Configuration configuration) + /// The options. + public JpegDecoderCore(Configuration configuration, IJpegDecoderOptions options) { + this.IgnoreMetadata = options.IgnoreMetadata; 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 +185,11 @@ namespace ImageSharp.Formats /// public int TotalMCUCount => this.MCUCountX * this.MCUCountY; + /// + /// Gets a value indicating whether the metadata should be ignored when the image is being decoded. + /// + public bool IgnoreMetadata { get; private set; } + /// /// Decodes the image from the specified and sets /// the data to image. @@ -938,7 +938,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 +968,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 152fd2c64..6c6561468 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; @@ -12,16 +14,25 @@ namespace ImageSharp.Formats /// /// Encoder for writing the data image to a stream in jpeg format. /// - public class JpegEncoder : IImageEncoder + public sealed class JpegEncoder : IImageEncoder, IJpegEncoderOptions { - /// - public void Encode(Image image, Stream stream, IEncoderOptions options) - where TPixel : struct, IPixel - { - IJpegEncoderOptions gifOptions = JpegEncoderOptions.Create(options); + /// + /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded. + /// + public bool IgnoreMetadata { get; set; } - this.Encode(image, stream, gifOptions); - } + /// + /// 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; } /// /// Encodes the image to the specified stream from the . @@ -29,12 +40,11 @@ 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); + 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 b65a56e73..d2b7d2d7c 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. @@ -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. /// @@ -155,17 +150,38 @@ namespace ImageSharp.Formats private Stream outputStream; /// - /// The subsampling method to use. + /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded. + /// + 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). /// - private JpegSubsample subsample; + /// The quality of the jpg image from 0 to 100. + private int quality = 0; + + /// + /// Gets or sets the subsampling method to use. + /// + private JpegSubsample? subsample; /// /// Initializes a new instance of the class. /// - /// The options for the encoder. + /// The options public JpegEncoderCore(IJpegEncoderOptions options) { - this.options = options ?? new JpegEncoderOptions(); + 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; } /// @@ -186,21 +202,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 +796,7 @@ namespace ImageSharp.Formats private void WriteProfiles(Image image) where TPixel : struct, IPixel { - if (this.options.IgnoreMetadata) + if (this.ignoreMetadata) { return; } diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderOptions.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderOptions.cs deleted file mode 100644 index 73e483164..000000000 --- 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 index b93c6ae69..23cd5d875 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegFormat.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegFormat.cs @@ -8,82 +8,20 @@ namespace ImageSharp.Formats using System.Collections.Generic; /// - /// Encapsulates the means to encode and decode jpeg images. + /// Registers the image encoders, decoders and mime type detectors for the jpeg format. /// - public class JpegFormat : IImageFormat + internal sealed class JpegFormat : IImageFormat { /// - public string MimeType => "image/jpeg"; + public string Name => "JPEG"; /// - public string Extension => "jpg"; + public string DefaultMimeType => "image/jpeg"; /// - public IEnumerable SupportedExtensions => new string[] { "jpg", "jpeg", "jfif" }; + public IEnumerable MimeTypes => JpegConstants.MimeTypes; /// - 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; - } + 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 new file mode 100644 index 000000000..b72b290c0 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/JpegImageFormatDetector.cs @@ -0,0 +1,87 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using System; + + /// + /// Detects Jpeg file headers + /// + public sealed class JpegImageFormatDetector : IImageFormatDetector + { + /// + public int HeaderSize => 11; + + /// + public IImageFormat DetectFormat(ReadOnlySpan header) + { + if (this.IsSupportedFileFormat(header)) + { + return ImageFormats.Jpeg; + } + + return null; + } + + private bool IsSupportedFileFormat(ReadOnlySpan 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(ReadOnlySpan header) + { + // TODO: This should be in constants + 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(ReadOnlySpan header) + { + // TODO: This should be in constants + 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(ReadOnlySpan header) + { + // TODO: This should be in constants + bool isJpg = + header[0] == 0xFF && // 255 + header[1] == 0xD8; // 216 + + return isJpg; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs index ca93e96dd..f0e05fabd 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs @@ -22,11 +22,6 @@ namespace ImageSharp.Formats.Jpeg.Port /// internal sealed class JpegDecoderCore : IDisposable { - /// - /// The decoder options. - /// - private readonly IDecoderOptions options; - /// /// The global configuration /// @@ -85,12 +80,12 @@ namespace ImageSharp.Formats.Jpeg.Port /// /// Initializes a new instance of the class. /// - /// The decoder options. /// The configuration. - public JpegDecoderCore(IDecoderOptions options, Configuration configuration) + /// The options. + public JpegDecoderCore(Configuration configuration, IJpegDecoderOptions options) { this.configuration = configuration ?? Configuration.Default; - this.options = options ?? new DecoderOptions(); + this.IgnoreMetadata = options.IgnoreMetadata; } /// @@ -98,6 +93,11 @@ namespace ImageSharp.Formats.Jpeg.Port /// public Stream InputStream { get; private set; } + /// + /// Gets a value indicating whether the metadata should be ignored when the image is being decoded. + /// + public bool IgnoreMetadata { get; } + /// /// Finds the next file marker within the byte stream. /// @@ -413,7 +413,7 @@ namespace ImageSharp.Formats.Jpeg.Port /// The image. private void ProcessApp1Marker(int remaining, ImageMetaData metadata) { - if (remaining < 6 || this.options.IgnoreMetadata) + if (remaining < 6 || this.IgnoreMetadata) { // Skip the application header length this.InputStream.Skip(remaining); @@ -444,7 +444,7 @@ namespace ImageSharp.Formats.Jpeg.Port { // 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.InputStream.Skip(remaining); return; diff --git a/src/ImageSharp/Formats/Png/IPngDecoderOptions.cs b/src/ImageSharp/Formats/Png/IPngDecoderOptions.cs index cc6d194bf..de163ba7e 100644 --- a/src/ImageSharp/Formats/Png/IPngDecoderOptions.cs +++ b/src/ImageSharp/Formats/Png/IPngDecoderOptions.cs @@ -1,17 +1,26 @@ -// +// // 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; /// - /// Encapsulates the options for the . + /// The optioas for decoding png images /// - public interface IPngDecoderOptions : IDecoderOptions + 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. /// diff --git a/src/ImageSharp/Formats/Png/IPngEncoderOptions.cs b/src/ImageSharp/Formats/Png/IPngEncoderOptions.cs index 0008080d3..8f0a4cd82 100644 --- a/src/ImageSharp/Formats/Png/IPngEncoderOptions.cs +++ b/src/ImageSharp/Formats/Png/IPngEncoderOptions.cs @@ -1,21 +1,30 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // namespace ImageSharp.Formats { - using Quantizers; + using System.Collections.Generic; + using System.IO; + + using ImageSharp.PixelFormats; + using ImageSharp.Quantizers; /// - /// Encapsulates the options for the . + /// The options availible for manipulating the encoder pipeline /// - public interface IPngEncoderOptions : IEncoderOptions + internal interface IPngEncoderOptions { /// - /// Gets the quality of output for images. + /// 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 Quality { get; } + int PaletteSize { get; } /// /// Gets the png color type @@ -24,19 +33,20 @@ namespace ImageSharp.Formats /// /// 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. + /// 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. + /// Gets quantizer for reducing the color count. /// IQuantizer Quantizer { get; } @@ -47,7 +57,7 @@ namespace ImageSharp.Formats /// /// Gets a value indicating whether this instance should write - /// gamma information to the stream. + /// gamma information to the stream. The default value is false. /// bool WriteGamma { get; } } diff --git a/src/ImageSharp/Formats/Png/ImageExtensions.cs b/src/ImageSharp/Formats/Png/ImageExtensions.cs index 3841b313c..c81738576 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 { - var 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/PngConfigurationModule.cs b/src/ImageSharp/Formats/Png/PngConfigurationModule.cs new file mode 100644 index 000000000..bb1c2086c --- /dev/null +++ b/src/ImageSharp/Formats/Png/PngConfigurationModule.cs @@ -0,0 +1,21 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + /// + /// Registers the image encoders, decoders and mime type detectors for the png format. + /// + public sealed class PngConfigurationModule : IConfigurationModule + { + /// + public void Configure(Configuration host) + { + host.SetEncoder(ImageFormats.Png, new PngEncoder()); + host.SetDecoder(ImageFormats.Png, new PngDecoder()); + 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 new file mode 100644 index 000000000..84e76a341 --- /dev/null +++ b/src/ImageSharp/Formats/Png/PngConstants.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.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 png. + /// + public static readonly IEnumerable MimeTypes = new[] { "image/png" }; + + /// + /// The list of file extensions that equate to a png. + /// + public static readonly IEnumerable FileExtensions = new[] { "png" }; + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Png/PngDecoder.cs b/src/ImageSharp/Formats/Png/PngDecoder.cs index 3a34147e2..61a8cb212 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; /// @@ -30,17 +31,17 @@ namespace ImageSharp.Formats /// /// /// - public class PngDecoder : IImageDecoder + public sealed class PngDecoder : IImageDecoder, IPngDecoderOptions { - /// - public Image Decode(Configuration configuration, Stream stream, IDecoderOptions options) - - where TPixel : struct, IPixel - { - IPngDecoderOptions pngOptions = PngDecoderOptions.Create(options); + /// + /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded. + /// + public bool IgnoreMetadata { get; set; } - return this.Decode(configuration, stream, pngOptions); - } + /// + /// Gets or sets the encoding that should be used when reading text chunks. + /// + public Encoding TextEncoding { get; set; } = PngConstants.DefaultEncoding; /// /// Decodes the image from the specified stream to the . @@ -48,12 +49,12 @@ 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); + return decoder.Decode(stream); } } } diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index 2ff6a4308..e6f19b915 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; @@ -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. @@ -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,22 +149,33 @@ namespace ImageSharp.Formats /// private int currentRowBytesRead; + /// + /// Gets or sets the png color type + /// + private PngColorType pngColorType; + + /// + /// Gets the encoding to use + /// + private Encoding textEncoding; + + /// + /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded. + /// + private bool ignoreMetadata; + /// /// Initializes a new instance of the class. /// - /// The decoder options. /// The configuration. - public PngDecoderCore(IPngDecoderOptions options, Configuration configuration) + /// The decoder options. + public PngDecoderCore(Configuration configuration, IPngDecoderOptions options) { this.configuration = configuration ?? Configuration.Default; - this.options = options ?? new PngDecoderOptions(); + this.textEncoding = options.TextEncoding ?? PngConstants.DefaultEncoding; + this.ignoreMetadata = options.IgnoreMetadata; } - /// - /// Gets or sets the png color type - /// - public PngColorType PngColorType { get; set; } - /// /// Decodes the stream to the image. /// @@ -221,7 +227,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 +349,7 @@ namespace ImageSharp.Formats /// The private int CalculateBytesPerPixel() { - switch (this.PngColorType) + switch (this.pngColorType) { case PngColorType.Grayscale: return 1; @@ -572,7 +577,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 +736,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 +901,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 +917,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 +972,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 e8990ec45..000000000 --- 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 f89b624f7..bfd82a074 100644 --- a/src/ImageSharp/Formats/Png/PngEncoder.cs +++ b/src/ImageSharp/Formats/Png/PngEncoder.cs @@ -5,23 +5,61 @@ 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. /// - public class PngEncoder : IImageEncoder + public sealed class PngEncoder : IImageEncoder, IPngEncoderOptions { - /// - public void Encode(Image image, Stream stream, IEncoderOptions options) - where TPixel : struct, IPixel - { - IPngEncoderOptions pngOptions = PngEncoderOptions.Create(options); + /// + /// Gets or sets a value indicating whether the metadata should be ignored when the image is being encoded. + /// + public bool IgnoreMetadata { get; set; } - this.Encode(image, stream, pngOptions); - } + /// + /// Gets or sets the size of the color palette to use. Set to zero to leav png encoding to use pixel data. + /// + public int PaletteSize { get; set; } = 0; + + /// + /// 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,13 +67,12 @@ 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 encoder = new PngEncoderCore(this)) { - encode.Encode(image, stream); + encoder.Encode(image, stream); } } } diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index 645df0548..cfbd0c449 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. /// @@ -128,13 +118,50 @@ namespace ImageSharp.Formats /// private IQuantizer quantizer; + /// + /// Gets or sets a value indicating whether to ignore metadata + /// + private bool ignoreMetadata; + + /// + /// Gets or sets the Quality value + /// + private int paletteSize; + + /// + /// Gets or sets the CompressionLevel value + /// + private int compressionLevel; + + /// + /// Gets or sets the Gamma value + /// + private float gamma; + + /// + /// Gets or sets the Threshold value + /// + private byte threshold; + + /// + /// Gets or sets a value indicating whether to Write Gamma + /// + private bool writeGamma; + /// /// Initializes a new instance of the class. /// - /// The options for the encoder. + /// The options for influancing the encoder public PngEncoderCore(IPngEncoderOptions options) { - this.options = options ?? new PngEncoderOptions(); + 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; } /// @@ -164,27 +191,20 @@ 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; - // 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 @@ -514,7 +534,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; } @@ -525,7 +545,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; @@ -552,7 +572,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 +630,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 +675,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 90175c6d6..000000000 --- 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 index 0f5a74da0..551b4a8c7 100644 --- a/src/ImageSharp/Formats/Png/PngFormat.cs +++ b/src/ImageSharp/Formats/Png/PngFormat.cs @@ -8,40 +8,20 @@ namespace ImageSharp.Formats using System.Collections.Generic; /// - /// Encapsulates the means to encode and decode png images. + /// Registers the image encoders, decoders and mime type detectors for the jpeg format. /// - public class PngFormat : IImageFormat + internal sealed class PngFormat : IImageFormat { /// - public string MimeType => "image/png"; + public string Name => "PNG"; /// - public string Extension => "png"; + public string DefaultMimeType => "image/png"; /// - public IEnumerable SupportedExtensions => new string[] { "png" }; + public IEnumerable MimeTypes => PngConstants.MimeTypes; /// - 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 - } + public IEnumerable FileExtensions => PngConstants.FileExtensions; } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Png/PngHeader.cs b/src/ImageSharp/Formats/Png/PngHeader.cs index 50d6cc9ec..9cf823fa1 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/PngImageFormatDetector.cs b/src/ImageSharp/Formats/Png/PngImageFormatDetector.cs new file mode 100644 index 000000000..fdea3eb8a --- /dev/null +++ b/src/ImageSharp/Formats/Png/PngImageFormatDetector.cs @@ -0,0 +1,43 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using System; + + /// + /// Detects png file headers + /// + public sealed class PngImageFormatDetector : IImageFormatDetector + { + /// + public int HeaderSize => 8; + + /// + public IImageFormat DetectFormat(ReadOnlySpan header) + { + if (this.IsSupportedFileFormat(header)) + { + return ImageFormats.Png; + } + + return null; + } + + private bool IsSupportedFileFormat(ReadOnlySpan header) + { + // TODO: This should be in constants + 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 + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Png/PngInterlaceMode.cs b/src/ImageSharp/Formats/Png/PngInterlaceMode.cs index ec3b8ebe7..b43aff0b7 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 935cdf953..cbd292dc4 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 0743d8ded..136f919da 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 diff --git a/src/ImageSharp/IConfigurationModule.cs b/src/ImageSharp/IConfigurationModule.cs new file mode 100644 index 000000000..95b92ed05 --- /dev/null +++ b/src/ImageSharp/IConfigurationModule.cs @@ -0,0 +1,19 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + /// + /// 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/IImage.cs b/src/ImageSharp/Image/IImage.cs index 55abdb244..b9e8e392a 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 c162f1772..101310706 100644 --- a/src/ImageSharp/Image/Image.Decode.cs +++ b/src/ImageSharp/Image/Image.Decode.cs @@ -5,11 +5,10 @@ namespace ImageSharp { - using System.Buffers; using System.IO; using System.Linq; using Formats; - + using ImageSharp.Memory; using ImageSharp.PixelFormats; /// @@ -22,8 +21,8 @@ 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) + /// The mime type or null if none found. + 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,45 +31,55 @@ namespace ImageSharp return null; } - IImageFormat format; - 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; - format = config.ImageFormats.FirstOrDefault(x => x.IsSupportedFileFormat(header)); + return config.FormatDetectors.Select(x => x.DetectFormat(buffer)).LastOrDefault(x => x != null); } - finally + } + + /// + /// By reading the header on the provided stream this calculates the images format. + /// + /// The image stream to read the header from. + /// The configuration. + /// The IImageFormat. + /// The image format or null if none found. + private static IImageDecoder DiscoverDecoder(Stream stream, Configuration config, out IImageFormat format) + { + format = InternalDetectFormat(stream, config); + if (format != null) { - ArrayPool.Shared.Return(header); + return config.FindDecoder(format); } - return format; + return null; } +#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, IImageFormat format) 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, out IImageFormat format); + 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, format); } } } \ No newline at end of file diff --git a/src/ImageSharp/Image/Image.FromBytes.cs b/src/ImageSharp/Image/Image.FromBytes.cs index 628092359..c7023b860 100644 --- a/src/ImageSharp/Image/Image.FromBytes.cs +++ b/src/ImageSharp/Image/Image.FromBytes.cs @@ -15,54 +15,78 @@ namespace ImageSharp /// public static partial class Image { + /// + /// 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 format or null if none found. + public static IImageFormat DetectFormat(byte[] data) + { + return DetectFormat(null, data); + } + + /// + /// 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 IImageFormat DetectFormat(Configuration config, byte[] data) + { + using (Stream stream = new MemoryStream(data)) + { + return DetectFormat(config, stream); + } + } + /// /// Create a new instance of the class from the given byte array. /// /// 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 IImageFormat format) => Load(null, data, out format); /// - /// 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. /// 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. /// + /// 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 IImageFormat format) => Load(config, data, out format); /// - /// 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 +97,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 IImageFormat format) where TPixel : struct, IPixel { - return Load(null, data, options); + return Load(null, data, out format); } /// /// 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 (var memoryStream = new MemoryStream(data)) + { + return Load(config, memoryStream); + } } /// /// 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 IImageFormat format) where TPixel : struct, IPixel { - return Load(data, decoder, null); + using (var memoryStream = new MemoryStream(data)) + { + return Load(config, memoryStream, out format); + } } /// /// 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 (var ms = new MemoryStream(data)) + using (var memoryStream = new MemoryStream(data)) { - return Load(config, ms, options); + return Load(memoryStream, 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 (var ms = new MemoryStream(data)) + using (var memoryStream = new MemoryStream(data)) { - return Load(ms, decoder, options); + return Load(config, memoryStream, decoder); } } } diff --git a/src/ImageSharp/Image/Image.FromFile.cs b/src/ImageSharp/Image/Image.FromFile.cs index a135c43f5..ca395ce08 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 mime type. + /// + /// The image file to open and to read the header from. + /// The mime type or null if none found. + public static IImageFormat DetectFormat(string filePath) + { + return DetectFormat(null, filePath); + } + + /// + /// 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 mime type or null if none found. + public static IImageFormat DetectFormat(Configuration config, string filePath) + { + config = config ?? Configuration.Default; + using (Stream file = config.FileSystem.OpenRead(filePath)) + { + return DetectFormat(config, file); + } + } + /// /// Create a new instance of the class from the given file. /// @@ -30,12 +55,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 IImageFormat format) => Load(path, out format); /// /// Create a new instance of the class from the given file. @@ -51,37 +76,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 IImageFormat format) => Load(config, path, out format); /// /// 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 +120,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 IImageFormat format) where TPixel : struct, IPixel { - return Load(null, path, options); + return Load(null, path, out format); } /// /// 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 +152,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 stream = config.FileSystem.OpenRead(path)) + { + return Load(config, stream); + } } /// /// 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 IImageFormat format) where TPixel : struct, IPixel { - return Load(path, decoder, null); + config = config ?? Configuration.Default; + using (Stream stream = config.FileSystem.OpenRead(path)) + { + return Load(config, stream, out format); + } } /// /// 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; - using (Stream s = config.FileSystem.OpenRead(path)) + config = config ?? Configuration.Default; + using (Stream stream = config.FileSystem.OpenRead(path)) { - return Load(s, decoder, options); + return Load(config, stream, decoder); } } } diff --git a/src/ImageSharp/Image/Image.FromStream.cs b/src/ImageSharp/Image/Image.FromStream.cs index 1bcb5adc9..29d93ae85 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.Text; using Formats; @@ -17,26 +18,47 @@ namespace ImageSharp /// public static partial class Image { + /// + /// By reading the header on the provided stream this calculates the images mime type. + /// + /// The image stream to read the header from. + /// The mime type or null if none found. + public static IImageFormat DetectFormat(Stream stream) + { + return DetectFormat(null, stream); + } + + /// + /// 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 mime type or null if none found. + public static IImageFormat DetectFormat(Configuration config, Stream stream) + { + 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. /// /// 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 IImageFormat format) => Load(stream, out format); /// /// 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 +85,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 IImageFormat format) => Load(config, stream, out format); /// /// Create a new instance of the class from the given stream. @@ -84,44 +106,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 IImageFormat format) where TPixel : struct, IPixel { - return Load(null, stream, options); + return Load(null, stream, out format); } /// /// 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 +152,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,29 +179,31 @@ 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 IImageFormat format) + where TPixel : struct, IPixel { config = config ?? Configuration.Default; - Image img = WithSeekableStream(stream, s => Decode(s, options, config)); + (Image img, IImageFormat format) data = WithSeekableStream(stream, s => Decode(s, config)); + + format = data.format; - if (img != null) + if (data.img != null) { - return img; + return data.img; } - StringBuilder stringBuilder = new StringBuilder(); - stringBuilder.AppendLine("Image cannot be loaded. Available formats:"); + var stringBuilder = new StringBuilder(); + stringBuilder.AppendLine("Image cannot be loaded. Available decoders:"); - foreach (IImageFormat format in config.ImageFormats) + foreach (KeyValuePair val in config.ImageDecoders) { - stringBuilder.AppendLine("-" + format); + stringBuilder.AppendLine($" - {val.Key.Name} : {val.Value.GetType().Name}"); } throw new NotSupportedException(stringBuilder.ToString()); @@ -198,12 +222,12 @@ namespace ImageSharp } // We want to be able to load images from things like HttpContext.Request.Body - using (MemoryStream 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 1cda8ebc7..5e8bcab31 100644 --- a/src/ImageSharp/Image/Image{TPixel}.cs +++ b/src/ImageSharp/Image/Image{TPixel}.cs @@ -9,8 +9,8 @@ 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; using Formats; @@ -95,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(); } /// @@ -139,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. /// @@ -163,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 format to save the image to. /// Thrown if the stream is null. /// The - public Image Save(Stream stream) + public Image Save(Stream stream, IImageFormat format) { - return this.Save(stream, (IEncoderOptions)null); - } + Guard.NotNull(format, nameof(format)); + IImageEncoder encoder = this.Configuration.FindEncoder(format); - /// - /// 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) + { + var 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 (KeyValuePair val in this.Configuration.ImageEncoders) + { + stringBuilder.AppendLine($" - {val.Key.Name} : {val.Value.GetType().Name}"); + } - /// - /// 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); } /// @@ -217,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; } @@ -250,64 +204,37 @@ 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)); + var format = this.Configuration.FindFormatByFileExtensions(ext); if (format == null) { - throw new InvalidOperationException($"No image formats have been registered for the file extension '{ext}'."); + var stringBuilder = new StringBuilder(); + 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($" - {fmt.Name} : {string.Join(", ", fmt.FileExtensions)}"); + } + + throw new NotSupportedException(stringBuilder.ToString()); } - return this.Save(filePath, format, options); - } + IImageEncoder encoder = this.Configuration.FindEncoder(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); - } + 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($" - {enc.Key} : {enc.Value.GetType().Name}"); + } - /// - /// 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); - } + 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 encoder to save the image with. - /// Thrown if the encoder is null. - /// The - public Image Save(string filePath, IImageEncoder encoder) - { - return this.Save(filePath, encoder, null); + return this.Save(filePath, encoder); } /// @@ -315,15 +242,14 @@ namespace ImageSharp /// /// 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) + public Image Save(string filePath, IImageEncoder encoder) { 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 @@ -331,21 +257,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 format. /// The - public string ToBase64String() + public string ToBase64String(IImageFormat format) { - using (MemoryStream stream = new MemoryStream()) + using (var stream = new MemoryStream()) { - this.Save(stream); + this.Save(stream, format); stream.Flush(); - return $"data:{this.CurrentImageFormat.MimeType};base64,{Convert.ToBase64String(stream.ToArray())}"; + return $"data:{format.DefaultMimeType};base64,{Convert.ToBase64String(stream.ToArray())}"; } } @@ -360,7 +287,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()) @@ -374,7 +301,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; } @@ -418,7 +345,6 @@ namespace ImageSharp /// private void CopyProperties(IImage other) { - this.CurrentImageFormat = other.CurrentImageFormat; this.MetaData = new ImageMetaData(other.MetaData); } } diff --git a/src/ImageSharp/ImageFormats.cs b/src/ImageSharp/ImageFormats.cs new file mode 100644 index 000000000..f79191eae --- /dev/null +++ b/src/ImageSharp/ImageFormats.cs @@ -0,0 +1,35 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using ImageSharp.Formats; + + /// + /// 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(); + } +} \ No newline at end of file diff --git a/src/ImageSharp/ImageSharp.csproj b/src/ImageSharp/ImageSharp.csproj index 9b4388088..8733131a7 100644 --- a/src/ImageSharp/ImageSharp.csproj +++ b/src/ImageSharp/ImageSharp.csproj @@ -44,6 +44,7 @@ + ..\..\ImageSharp.ruleset diff --git a/src/ImageSharp/Memory/Buffer.cs b/src/ImageSharp/Memory/Buffer.cs index a894ea53a..4b3681c74 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/src/ImageSharp/MetaData/ImageMetaData.cs b/src/ImageSharp/MetaData/ImageMetaData.cs index 91ea9ac4b..5361b486d 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 d6e8ac692..41574d109 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 88e82f079..02e3211a7 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(), PaletteSize = 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 }, PaletteSize = 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(), PaletteSize = 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 }, PaletteSize = 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(), PaletteSize = 256 }; this.bmpCore.SaveAsPng(memoryStream, options); } diff --git a/tests/ImageSharp.Benchmarks/Image/EncodePng.cs b/tests/ImageSharp.Benchmarks/Image/EncodePng.cs index 3ec83aa7c..81bb235ee 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 aa3c4edfc..417588180 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,22 +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 = new Configuration(); + } + [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.ImageFormats.Count); + Assert.Equal(4, DefaultConfiguration.ImageEncoders.Count()); + Assert.Equal(4, DefaultConfiguration.ImageDecoders.Count()); } /// @@ -67,243 +72,97 @@ 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() - { - Assert.True(Configuration.Default.ImageFormats != null); - } - - /// - /// Tests the method throws an exception - /// when the format is null. - /// - [Fact] - public void TestAddImageFormatThrowsWithNullFormat() + public void AddImageFormatDetectorNullthrows() { Assert.Throws(() => { - Configuration.Default.AddImageFormat(null); + DefaultConfiguration.AddImageFormatDetector(null); }); } - /// - /// Tests the method throws an exception - /// when the encoder is null. - /// [Fact] - public void TestAddImageFormatThrowsWithNullEncoder() + public void RegisterNullMimeTypeEncoder() { - var format = new TestFormat { Encoder = null }; - Assert.Throws(() => { - Configuration.Default.AddImageFormat(format); + DefaultConfiguration.SetEncoder(null, new Mock().Object); }); - } - - /// - /// 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); + DefaultConfiguration.SetEncoder(ImageFormats.Bitmap, null); }); - } - - /// - /// 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); + DefaultConfiguration.SetEncoder(null, null); }); } - /// - /// Tests the method throws an exception - /// when the extension is null or an empty string. - /// [Fact] - public void TestAddImageFormatThrowsWithNullOrEmptyExtension() + public void RegisterNullSetDecoder() { - var format = new TestFormat { Extension = null }; - Assert.Throws(() => { - Configuration.Default.AddImageFormat(format); - }); - - format = new TestFormat { Extension = string.Empty }; - - Assert.Throws(() => - { - Configuration.Default.AddImageFormat(format); + DefaultConfiguration.SetDecoder(null, new Mock().Object); }); - } - - /// - /// 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); + DefaultConfiguration.SetDecoder(ImageFormats.Bitmap, null); }); - - format = new TestFormat { SupportedExtensions = Enumerable.Empty() }; - - Assert.Throws(() => + Assert.Throws(() => { - Configuration.Default.AddImageFormat(format); + DefaultConfiguration.SetDecoder(null, null); }); } - /// - /// Tests the method throws an exception - /// when the supported extensions list does not contain the default extension. - /// [Fact] - public void TestAddImageFormatThrowsWithoutDefaultExtension() + public void RegisterMimeTypeEncoderReplacesLast() { - var format = new TestFormat { Extension = "test" }; + var encoder1 = new Mock().Object; + ConfigurationEmpty.SetEncoder(TestFormat.GlobalTestFormat, encoder1); + var found = ConfigurationEmpty.FindEncoder(TestFormat.GlobalTestFormat); + Assert.Equal(encoder1, found); - Assert.Throws(() => - { - Configuration.Default.AddImageFormat(format); - }); + var encoder2 = new Mock().Object; + ConfigurationEmpty.SetEncoder(TestFormat.GlobalTestFormat, encoder2); + var found2 = ConfigurationEmpty.FindEncoder(TestFormat.GlobalTestFormat); + Assert.Equal(encoder2, found2); + Assert.NotEqual(found, found2); } - /// - /// Tests the method throws an exception - /// when the supported extensions list contains an empty string. - /// [Fact] - public void TestAddImageFormatThrowsWithEmptySupportedExtension() + public void RegisterMimeTypeDecoderReplacesLast() { - var format = new TestFormat - { - Extension = "test", - SupportedExtensions = new[] { "test", string.Empty } - }; + var decoder1 = new Mock().Object; + ConfigurationEmpty.SetDecoder(TestFormat.GlobalTestFormat, decoder1); + var found = ConfigurationEmpty.FindDecoder(TestFormat.GlobalTestFormat); + Assert.Equal(decoder1, found); - Assert.Throws(() => - { - Configuration.Default.AddImageFormat(format); - }); + var decoder2 = new Mock().Object; + ConfigurationEmpty.SetDecoder(TestFormat.GlobalTestFormat, decoder2); + var found2 = ConfigurationEmpty.FindDecoder(TestFormat.GlobalTestFormat); + Assert.Equal(decoder2, found2); + Assert.NotEqual(found, found2); } - /// - /// 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() + public void ConstructorCallConfigureOnFormatProvider() { - Configuration.Default.AddImageFormat(new PngFormat()); + var provider = new Mock(); + var config = new Configuration(provider.Object); - var image = new Image(1, 1); - Assert.Equal(image.Configuration.ParallelOptions, Configuration.Default.ParallelOptions); - Assert.Equal(image.Configuration.ImageFormats, Configuration.Default.ImageFormats); + provider.Verify(x => x.Configure(config)); } - /// - /// Test that the default image constructor copies the configuration. - /// [Fact] - public void TestImageCopiesConfiguration() - { - Configuration.Default.AddImageFormat(new PngFormat()); - - 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 + public void AddFormatCallsConfig() { - /// - /// 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; } + var provider = new Mock(); + var config = new Configuration(); + config.Configure(provider.Object); - /// - 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(); - } + provider.Verify(x => x.Configure(config)); } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Drawing/BeziersTests.cs b/tests/ImageSharp.Tests/Drawing/BeziersTests.cs index ed2b7726c..7ddc2de5a 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 SixLabors.Primitives.PointF[] { + image.BackgroundColor(Rgba32.Blue) + .DrawBeziers(Rgba32.HotPink, 5, + new SixLabors.Primitives.PointF[] { 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 SixLabors.Primitives.PointF[] { + image.BackgroundColor(Rgba32.Blue) + .DrawBeziers(color, + 10, + new SixLabors.Primitives.PointF[]{ 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 3df3a508c..da5028f04 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 SixLabors.Primitives.PointF[] { 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 7bacebe42..bbabdf0ea 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 dc0b83615..ce127cfe0 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 1f35a3788..e058572fb 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 c2a5d240c..f47d56696 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 SixLabors.Primitives.PointF[] { + image + .BackgroundColor(Rgba32.Blue) + .DrawLines(Rgba32.HotPink, 5, + new SixLabors.Primitives.PointF[]{ 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 SixLabors.Primitives.PointF[] { + image + .BackgroundColor(Rgba32.Blue) + .DrawLines(Rgba32.HotPink, 5, + new SixLabors.Primitives.PointF[] { 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,20 +78,17 @@ 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 SixLabors.Primitives.PointF[] { + image + .BackgroundColor(Rgba32.Blue) + .DrawLines(Pens.Dash(Rgba32.HotPink, 5), + new SixLabors.Primitives.PointF[] { new Vector2(10, 10), new Vector2(200, 150), new Vector2(50, 300) - }) - .Save(output); + }) + .Save($"{path}/Dashed.png"); } } - } [Fact] public void ImageShouldBeOverlayedByPathDotted() @@ -105,20 +96,17 @@ 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 SixLabors.Primitives.PointF[] { + image + .BackgroundColor(Rgba32.Blue) + .DrawLines(Pens.Dot(Rgba32.HotPink, 5), + new SixLabors.Primitives.PointF[] { new Vector2(10, 10), new Vector2(200, 150), new Vector2(50, 300) - }) - .Save(output); + }) + .Save($"{path}/Dot.png"); } } - } [Fact] public void ImageShouldBeOverlayedByPathDashDot() @@ -126,20 +114,17 @@ 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 SixLabors.Primitives.PointF[] { + image + .BackgroundColor(Rgba32.Blue) + .DrawLines(Pens.DashDot(Rgba32.HotPink, 5), + new SixLabors.Primitives.PointF[] { new Vector2(10, 10), new Vector2(200, 150), new Vector2(50, 300) - }) - .Save(output); + }) + .Save($"{path}/DashDot.png"); } } - } [Fact] public void ImageShouldBeOverlayedByPathDashDotDot() @@ -147,18 +132,15 @@ 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 SixLabors.Primitives.PointF[] { + image + .BackgroundColor(Rgba32.Blue) + .DrawLines(Pens.DashDotDot(Rgba32.HotPink, 5), new SixLabors.Primitives.PointF[] { new Vector2(10, 10), new Vector2(200, 150), new Vector2(50, 300) - }) - .Save(output); + }) + .Save($"{path}/DashDotDot.png"); } - } [Fact] public void ImageShouldBeOverlayedPathWithOpacity() @@ -169,31 +151,27 @@ 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 SixLabors.Primitives.PointF[] { + image + .BackgroundColor(Rgba32.Blue) + .DrawLines(color, 10, new SixLabors.Primitives.PointF[] { 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)); + //shift background color towards forground color by the opacity amount + Rgba32 mergedColor = new Rgba32(Vector4.Lerp(Rgba32.Blue.ToVector4(), Rgba32.HotPink.ToVector4(), 150f / 255f)); - using (PixelAccessor sourcePixels = image.Lock()) - { - Assert.Equal(mergedColor, sourcePixels[11, 11]); + using (PixelAccessor sourcePixels = image.Lock()) + { + Assert.Equal(mergedColor, sourcePixels[11, 11]); - Assert.Equal(mergedColor, sourcePixels[199, 149]); + Assert.Equal(mergedColor, sourcePixels[199, 149]); - Assert.Equal(Rgba32.Blue, sourcePixels[50, 50]); + Assert.Equal(Rgba32.Blue, sourcePixels[50, 50]); + } } - } [Fact] public void ImageShouldBeOverlayedByPathOutline() @@ -202,30 +180,27 @@ 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 SixLabors.Primitives.PointF[] { + image + .BackgroundColor(Rgba32.Blue) + .DrawLines(Rgba32.HotPink, 10, new SixLabors.Primitives.PointF[] { 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()) - { - Assert.Equal(Rgba32.HotPink, sourcePixels[11, 11]); + using (PixelAccessor sourcePixels = image.Lock()) + { + Assert.Equal(Rgba32.HotPink, sourcePixels[11, 11]); - Assert.Equal(Rgba32.HotPink, sourcePixels[198, 10]); + Assert.Equal(Rgba32.HotPink, sourcePixels[198, 10]); - Assert.Equal(Rgba32.Blue, sourcePixels[10, 50]); + Assert.Equal(Rgba32.Blue, sourcePixels[10, 50]); - Assert.Equal(Rgba32.Blue, sourcePixels[50, 50]); + Assert.Equal(Rgba32.Blue, sourcePixels[50, 50]); + } } - } + } } -} diff --git a/tests/ImageSharp.Tests/Drawing/PolygonTests.cs b/tests/ImageSharp.Tests/Drawing/PolygonTests.cs index fcd5f3dc8..501b10e4a 100644 --- a/tests/ImageSharp.Tests/Drawing/PolygonTests.cs +++ b/tests/ImageSharp.Tests/Drawing/PolygonTests.cs @@ -25,8 +25,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, @@ -35,8 +33,7 @@ namespace ImageSharp.Tests.Drawing new Vector2(200, 150), new Vector2(50, 300) }) - .Save(output); - } + .Save($"{path}/Simple.png"); using (PixelAccessor sourcePixels = image.Lock()) { @@ -65,13 +62,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)); @@ -96,13 +90,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 4655f1c14..9c62e860a 100644 --- a/tests/ImageSharp.Tests/Drawing/RecolorImageTest.cs +++ b/tests/ImageSharp.Tests/Drawing/RecolorImageTest.cs @@ -27,11 +27,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}"); } } } @@ -47,12 +44,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 b6b143b5d..ba904cb3f 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 CubicBezierLineSegment(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 CubicBezierLineSegment(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 5e0244d02..c3af3d5c2 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 b87f0a11d..793bcfc9f 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.RectangularePolygon(10, 10, 190, 140)) - .Save(output); - } + image + .BackgroundColor(Rgba32.Blue) + .Fill(Rgba32.HotPink, new SixLabors.Shapes.RectangularePolygon(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 EllipsePolygon(50, 50, 30, 50) - .Rotate((float)(Math.PI / 3))) - .Save(output); - } + image + .BackgroundColor(Rgba32.Blue) + .Fill(Rgba32.HotPink, new EllipsePolygon(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 SixLabors.Primitives.PointF[] - { + image + .Fill(Rgba32.Blue) + .FillPolygon(Rgba32.HotPink, new SixLabors.Primitives.PointF[] + { 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 cf073d3d0..e5af28d8b 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 ec1a8c465..4ef8fe061 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()); + File.WriteAllText(filename, image.ToBase64String(ImageFormats.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 IImageFormat 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 89df2d086..06bfd8990 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 70cc8e2ba..c36539686 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 446c5e96a..9401d098d 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, new JpegDecoder())) { 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 8c3e9ef84..86faeee23 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs @@ -38,14 +38,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); } } @@ -62,9 +60,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 @@ -76,7 +72,7 @@ namespace ImageSharp.Tests [Fact] public void Encode_IgnoreMetadataIsFalse_ExifProfileIsWritten() { - EncoderOptions options = new EncoderOptions() + JpegEncoder options = new JpegEncoder() { IgnoreMetadata = false }; @@ -87,7 +83,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)) @@ -101,7 +97,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 f3412e45e..daf8da6a3 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 5ab1fac2f..6d82b5374 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 02edf7688..24907cfdb 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 90f994f36..bac340a71 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() { PaletteSize = 256 }); ms.Position = 0; using (Image img2 = Image.Load(ms, new PngDecoder())) { diff --git a/tests/ImageSharp.Tests/Image/ImageDiscoverMimeType.cs b/tests/ImageSharp.Tests/Image/ImageDiscoverMimeType.cs new file mode 100644 index 000000000..59a39c454 --- /dev/null +++ b/tests/ImageSharp.Tests/Image/ImageDiscoverMimeType.cs @@ -0,0 +1,111 @@ +// +// 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 DiscoverImageFormatTests + { + private readonly Mock fileSystem; + private readonly string FilePath; + 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 DiscoverImageFormatTests() + { + this.localImageFormatMock = 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.fileSystem = new Mock(); + + this.LocalConfiguration = new Configuration() + { + FileSystem = this.fileSystem.Object + }; + this.LocalConfiguration.AddImageFormatDetector(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 DiscoverImageFormatByteArray() + { + var type = Image.DetectFormat(DataStream.ToArray()); + Assert.Equal(TestFormat.GlobalTestFormat, type); + } + + [Fact] + public void DiscoverImageFormatByteArray_WithConfig() + { + var type = Image.DetectFormat(this.LocalConfiguration, DataStream.ToArray()); + Assert.Equal(localImageFormat, type); + } + + [Fact] + public void DiscoverImageFormatFile() + { + var type = Image.DetectFormat(this.FilePath); + Assert.Equal(TestFormat.GlobalTestFormat, type); + } + + [Fact] + public void DiscoverImageFormatFilePath_WithConfig() + { + var type = Image.DetectFormat(this.LocalConfiguration, FilePath); + Assert.Equal(localImageFormat, type); + } + + + [Fact] + public void DiscoverImageFormatStream() + { + var type = Image.DetectFormat(this.DataStream); + Assert.Equal(TestFormat.GlobalTestFormat, type); + } + + [Fact] + public void DiscoverImageFormatFileStream_WithConfig() + { + var type = Image.DetectFormat(this.LocalConfiguration, DataStream); + Assert.Equal(localImageFormat, type); + } + + [Fact] + public void DiscoverImageFormatNoDetectorsRegisterdShouldReturnNull() + { + 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 7a5b8ffc5..bb64ceda3 100644 --- a/tests/ImageSharp.Tests/Image/ImageLoadTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageLoadTests.cs @@ -20,11 +20,11 @@ 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; + private readonly Mock localMimeTypeDetector; + private readonly Mock localImageFormatMock; public Configuration LocalConfiguration { get; private set; } public byte[] Marker { get; private set; } @@ -35,19 +35,16 @@ namespace ImageSharp.Tests { this.returnImage = new Image(1, 1); + this.localImageFormatMock = new Mock(); + 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.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); @@ -58,14 +55,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.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.decoderOptions = new Mock().Object; this.FilePath = Guid.NewGuid().ToString(); this.fileSystem.Setup(x => x.OpenRead(this.FilePath)).Returns(this.DataStream); @@ -80,11 +79,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); } [Fact] @@ -94,10 +90,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 +102,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 +115,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 +128,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 +141,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 +152,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 +161,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 +174,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 +185,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 +198,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)); - - 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)); + this.localDecoder.Verify(x => x.Decode(this.LocalConfiguration, It.IsAny())); 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 +221,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 +231,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 +244,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 +254,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 +266,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 +286,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 9e9852cb2..eecb98380 100644 --- a/tests/ImageSharp.Tests/Image/ImageSaveTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageSaveTests.cs @@ -22,37 +22,32 @@ 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; + private Mock localMimeTypeDetector; + private Mock localImageFormat; 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.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.DetectFormat(It.IsAny>())).Returns(localImageFormat.Object); + this.encoder = new Mock(); 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.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.AddImageFormatDetector(this.localMimeTypeDetector.Object); + config.SetEncoder(localImageFormat.Object, this.encoder.Object); + this.Image = new Image(config, 1, 1); } [Fact] @@ -62,19 +57,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 +69,24 @@ namespace ImageSharp.Tests this.Image.Save("path.jpg", this.encoderNotInFormat.Object); - this.encoderNotInFormat.Verify(x => x.Encode(this.Image, stream, null)); - } - - [Fact] - public void SavePathWithEncoderAndOptions() - { - 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); - - this.encoderNotInFormat.Verify(x => x.Encode(this.Image, stream, this.encoderOptions)); + this.encoderNotInFormat.Verify(x => x.Encode(this.Image, stream)); } [Fact] - public void SaveStream() + public void ToBase64String() { - Stream stream = new MemoryStream(); - this.Image.Save(stream); + var str = this.Image.ToBase64String(localImageFormat.Object); - this.encoder.Verify(x => x.Encode(this.Image, stream, null)); + this.encoder.Verify(x => x.Encode(this.Image, It.IsAny())); } [Fact] - public void SaveStreamWithOptions() + public void SaveStreamWithMime() { Stream stream = new MemoryStream(); + this.Image.Save(stream, localImageFormat.Object); - 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 +96,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 a3ec4cec2..3157c27d8 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.DefaultMimeType); } } @@ -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.DefaultMimeType); } } } diff --git a/tests/ImageSharp.Tests/MetaData/ImageMetaDataTests.cs b/tests/ImageSharp.Tests/MetaData/ImageMetaDataTests.cs index bc64d613a..c60c8978c 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 698ad8313..b09aff78e 100644 --- a/tests/ImageSharp.Tests/TestFile.cs +++ b/tests/ImageSharp.Tests/TestFile.cs @@ -5,12 +5,12 @@ namespace ImageSharp.Tests { - using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; + using ImageSharp.Formats; /// /// A test image file. @@ -133,13 +133,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); } private Image GetImage() diff --git a/tests/ImageSharp.Tests/TestFormat.cs b/tests/ImageSharp.Tests/TestFormat.cs index 318df5ead..5a3cd102e 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 : ImageSharp.Formats.IImageFormat + 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() @@ -58,14 +58,15 @@ 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"); - foreach (DecodeOperation d in discovered) { + foreach (DecodeOperation d in discovered) + { this.DecodeCalls.Remove(d); } } @@ -79,7 +80,7 @@ namespace ImageSharp.Tests { this._sampleImages.Add(typeof(TPixel), new Image(1, 1)); } - + return (Image)this._sampleImages[typeof(TPixel)]; } } @@ -92,7 +93,15 @@ namespace ImageSharp.Tests public int HeaderSize => this.header.Length; - public bool IsSupportedFileFormat(byte[] 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) { @@ -107,18 +116,21 @@ namespace ImageSharp.Tests } return true; } + + public void Configure(Configuration host) + { + host.AddImageFormatDetector(new TestHeader(this)); + host.SetEncoder(this, new TestEncoder(this)); + host.SetDecoder(this, new TestDecoder(this)); + } + 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) { @@ -141,6 +153,26 @@ namespace ImageSharp.Tests } } + public class TestHeader : IImageFormatDetector + { + + private TestFormat testFormat; + + public int HeaderSize => testFormat.HeaderSize; + + public IImageFormat DetectFormat(ReadOnlySpan header) + { + if (testFormat.IsSupportedFileFormat(header)) + return testFormat; + + return null; + } + + public TestHeader(TestFormat testFormat) + { + this.testFormat = testFormat; + } + } public class TestDecoder : ImageSharp.Formats.IImageDecoder { private TestFormat testFormat; @@ -150,8 +182,13 @@ namespace ImageSharp.Tests this.testFormat = testFormat; } + public IEnumerable MimeTypes => new[] { testFormat.MimeType }; + + public IEnumerable FileExtensions => testFormat.SupportedExtensions; + + public int HeaderSize => testFormat.HeaderSize; - public Image Decode(Configuration config, Stream stream, IDecoderOptions options) where TPixel : struct, IPixel + public Image Decode(Configuration config, Stream stream) where TPixel : struct, IPixel { var ms = new MemoryStream(); @@ -160,13 +197,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 +216,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 290123885..47085cf5f 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,16 +121,16 @@ 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)); + var format = Configuration.Default.FindFormatByFileExtensions(extension); + return Configuration.Default.FindEncoder(format); } private string GetTestOutputDir() { string testGroupName = Path.GetFileNameWithoutExtension(this.TestGroupName); - return CreateOutputDirectory(testGroupName); } }