diff --git a/.travis.yml b/.travis.yml index af8d4ad9de..4ae163530e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,7 +26,7 @@ env: global: # The next declaration is the encrypted COVERITY_SCAN_TOKEN, created # via the "travis encrypt" command using the project repo's public key - - secure: "aim+fUyx7kDQQcAYV1mX16cvyFEYsxiW3y26xjmeuKzsOf6DIUK328pE8KnO50bMWhfVPjuW7jWc43jI+nbUeIW5018aFcjoOrEK2F8JvJ0UKtEo+ONchypJSXA2TSdL0iIlufMBepsmlBsSLkCwHCJYohYcueZV0u9NVPc3n282KLL8ItRZeSFG/cL/a2yrkFnTFhq9OtkUtP4CcVE7BOtzjfftNcn4Rup73e5JkLT7L9AZS7eCYkIYV0KRlT2pOa/FuOHlfP9NP+NVtd83GXUY2FKBsmN3EmrQgGDTfwfwcJjN5dqIqzkIXmLV8IKQ3aiW2//02pIe5VrdqHQG+EVMRcdpCWyKUkMj0g4rGYkqKCtVJojKtOR93ycOGUDc6+cMMoyn3J2qFydkp278dGWeLuwtGfD25fHXorqK1aL9/bGPcwdinrBmcwnuy1IECtuTkEfAPsb6O4nArnDsTEzeQxwa/MAicmpux//TNKgkQGqzCPeHKbl4vOfyyI6kCsf8edWv8fOSPvJUGvL14+/TZ6lY8S+30fosOmwMCe7xlbtcVlBVtOsKx/XUufrP2Vuptlc8INaq6++XtgpCoMLL0SJfBFQKZRmBGavv1Ztyf0aL6Qp303HKGTyXOEq2k18iJmukB6JcnEGVsaAyteGlruQIbPgHWbxhZSoJZPw=" + - secure: "rjMvEMN9rpvIXqXqCAAKzbHyABzr7E4wPU/dYJ/mHBqlCccFpQrEXVVM1MfRFXYuWZSaIioknhLATZjT5xvIYpTNM6D57z4OTmqeRHhYm80=" before_install: - echo -n | openssl s_client -connect scan.coverity.com:443 | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' | sudo tee -a /etc/ssl/certs/ca- @@ -34,7 +34,7 @@ before_install: addons: coverity_scan: project: - name: "JimBobSquarePants/ImageSharp" + name: "SixLabors/ImageSharp" description: "Build submitted via Travis CI" notification_email: james_south@hotmail.com build_command_prepend: "dotnet restore" diff --git a/ImageSharp.sln b/ImageSharp.sln index e546420efb..a584c56868 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/README.md b/README.md index e46b113793..7113d6ba1a 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -# ImageSharp ImageSharp +# ImageSharp ImageSharp **ImageSharp** is a new, fully featured, fully managed, cross-platform, 2D graphics API designed to allow the processing of images without the use of `System.Drawing`. @@ -9,12 +9,12 @@ Built against .Net Standard 1.1 ImageSharp can be used in device, cloud, and emb > > Pre-release downloads are available from the [MyGet package repository](https://www.myget.org/gallery/imagesharp). -[![GitHub license](https://img.shields.io/badge/license-Apache%202-blue.svg)](https://raw.githubusercontent.com/JimBobSquarePants/ImageSharp/master/APACHE-2.0-LICENSE.txt) -[![GitHub issues](https://img.shields.io/github/issues/JimBobSquarePants/ImageSharp.svg)](https://github.com/JimBobSquarePants/ImageSharp/issues) -[![GitHub stars](https://img.shields.io/github/stars/JimBobSquarePants/ImageSharp.svg)](https://github.com/JimBobSquarePants/ImageSharp/stargazers) -[![GitHub forks](https://img.shields.io/github/forks/JimBobSquarePants/ImageSharp.svg)](https://github.com/JimBobSquarePants/ImageSharp/network) +[![GitHub license](https://img.shields.io/badge/license-Apache%202-blue.svg)](https://raw.githubusercontent.com/SixLabors/ImageSharp/master/APACHE-2.0-LICENSE.txt) +[![GitHub issues](https://img.shields.io/github/issues/SixLabors/ImageSharp.svg)](https://github.com/SixLabors/ImageSharp/issues) +[![GitHub stars](https://img.shields.io/github/stars/SixLabors/ImageSharp.svg)](https://github.com/SixLabors/ImageSharp/stargazers) +[![GitHub forks](https://img.shields.io/github/forks/SixLabors/ImageSharp.svg)](https://github.com/SixLabors/ImageSharp/network) [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/ImageSharp/General?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -[![Twitter](https://img.shields.io/twitter/url/https/github.com/JimBobSquarePants/ImageSharp.svg?style=social)](https://twitter.com/intent/tweet?hashtags=imagesharp,dotnet,oss&text=ImageSharp.+A+new+cross-platform+2D+graphics+API+in+C%23&url=https%3a%2f%2fgithub.com%2fJimBobSquarePants%2fImageSharp&via=james_m_south) +[![Twitter](https://img.shields.io/twitter/url/https/github.com/SixLabors/ImageSharp.svg?style=social)](https://twitter.com/intent/tweet?hashtags=imagesharp,dotnet,oss&text=ImageSharp.+A+new+cross-platform+2D+graphics+API+in+C%23&url=https%3a%2f%2fgithub.com%2fSixLabors%2fImageSharp&via=sixlabors) [![OpenCollective](https://opencollective.com/imagesharp/backers/badge.svg)](#backers) [![OpenCollective](https://opencollective.com/imagesharp/sponsors/badge.svg)](#sponsors) @@ -22,8 +22,8 @@ Built against .Net Standard 1.1 ImageSharp can be used in device, cloud, and emb | |Build Status|Code Coverage| |-------------|:----------:|:-----------:| -|**Linux/Mac**|[![Build Status](https://travis-ci.org/JimBobSquarePants/ImageSharp.svg)](https://travis-ci.org/JimBobSquarePants/ImageSharp)|[![Code coverage](https://codecov.io/gh/JimBobSquarePants/ImageSharp/branch/master/graph/badge.svg)](https://codecov.io/gh/JimBobSquarePants/ImageSharp)| -|**Windows** |[![Build Status](https://ci.appveyor.com/api/projects/status/hu6d1gdpxdw0q360/branch/master?svg=true)](https://ci.appveyor.com/project/JamesSouth/imagesharp/branch/master)|[![Code coverage](https://codecov.io/gh/JimBobSquarePants/ImageSharp/branch/master/graph/badge.svg)](https://codecov.io/gh/JimBobSquarePants/ImageSharp)| +|**Linux/Mac**|[![Build Status](https://travis-ci.org/SixLabors/ImageSharp.svg)](https://travis-ci.org/SixLabors/ImageSharp)|[![Code coverage](https://codecov.io/gh/SixLabors/ImageSharp/branch/master/graph/badge.svg)](https://codecov.io/gh/SixLabors/ImageSharp)| +|**Windows** |[![Build Status](https://ci.appveyor.com/api/projects/status/m9pn907xdah3ca39/branch/master?svg=true)](https://ci.appveyor.com/project/six-labors/imagesharp/branch/master)|[![Code coverage](https://codecov.io/gh/SixLabors/ImageSharp/branch/master/graph/badge.svg)](https://codecov.io/gh/SixLabors/ImageSharp)| ### Installation @@ -64,7 +64,7 @@ Alternatively on Linux you can use: To clone it locally click the "Clone in Windows" button above or run the following git commands. ```bash -git clone https://github.com/JimBobSquarePants/ImageSharp +git clone https://github.com/SixLabors/ImageSharp ``` ### Features @@ -121,7 +121,7 @@ For optimized access within a loop it is recommended that the following methods 1. `image.GetRowSpan(y)` 2. `image.GetRowSpan(x, y)` -For advanced pixel format usage there are multiple [PixelFormat implementations](https://github.com/JimBobSquarePants/ImageSharp/tree/master/src/ImageSharp/PixelFormats) available allowing developers to implement their own color models in the same manner as Microsoft XNA Game Studio and MonoGame. +For advanced pixel format usage there are multiple [PixelFormat implementations](https://github.com/SixLabors/ImageSharp/tree/master/src/ImageSharp/PixelFormats) available allowing developers to implement their own color models in the same manner as Microsoft XNA Game Studio and MonoGame. All in all this should allow image processing to be much more accessible to developers which has always been my goal from the start. diff --git a/appveyor.yml b/appveyor.yml index 6b7ba946ec..fdbe46b015 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -22,7 +22,7 @@ deploy: server: https://www.myget.org/F/imagesharp/api/v2/package symbol_server: https://www.myget.org/F/imagesharp/symbols/api/v2/package api_key: - secure: fz0rUrt3B1HczUC1ZehwVsrFSWX9WZGDQoueDztLte9/+yQG+BBU7UrO+coE8lUf + secure: P2Fz82nty+itjL+kNRCsMQcqzngmVtkU0R4CZqgST7zgUaE6/1q9ekh5MKKlZLkD artifact: /.*\.nupkg/ on: branch: master diff --git a/samples/ChangeDefaultEncoderOptions/ChangeDefaultEncoderOptions.csproj b/samples/ChangeDefaultEncoderOptions/ChangeDefaultEncoderOptions.csproj new file mode 100644 index 0000000000..5797be0f56 --- /dev/null +++ b/samples/ChangeDefaultEncoderOptions/ChangeDefaultEncoderOptions.csproj @@ -0,0 +1,12 @@ + + + + Exe + netcoreapp1.1 + + + + + + + \ No newline at end of file diff --git a/samples/ChangeDefaultEncoderOptions/Program.cs b/samples/ChangeDefaultEncoderOptions/Program.cs new file mode 100644 index 0000000000..dab8d445ca --- /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 70491ba22e..32a0854359 100644 --- a/src/ImageSharp/Common/Exceptions/ImageFormatException.cs +++ b/src/ImageSharp/Common/Exceptions/ImageFormatException.cs @@ -11,7 +11,7 @@ namespace ImageSharp /// The exception that is thrown when the library tries to load /// an image, which has an invalid format. /// - public class ImageFormatException : Exception + public sealed class ImageFormatException : Exception { /// /// Initializes a new instance of the class. diff --git a/src/ImageSharp/Common/Exceptions/ImageProcessingException.cs b/src/ImageSharp/Common/Exceptions/ImageProcessingException.cs index a59be9ca8f..ef84a1e393 100644 --- a/src/ImageSharp/Common/Exceptions/ImageProcessingException.cs +++ b/src/ImageSharp/Common/Exceptions/ImageProcessingException.cs @@ -10,7 +10,7 @@ namespace ImageSharp /// /// The exception that is thrown when an error occurs when applying a process to an image. /// - public class ImageProcessingException : Exception + public sealed class ImageProcessingException : Exception { /// /// Initializes a new instance of the class. diff --git a/src/ImageSharp/Configuration.cs b/src/ImageSharp/Configuration.cs index fa983d3557..a9322467cb 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 ConcurrentBag imageFormats = new ConcurrentBag(); + + /// + /// The list of supported s. + /// + private ConcurrentBag imageFormatDetectors = new ConcurrentBag(); /// /// 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 = new ConcurrentBag(); + } - 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 0000000000..f70ff1a56d --- /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 0000000000..d394b61f6e --- /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 9090e9a8cd..5baf1b1a5a 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 997a77d6c5..817d00f7e7 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs @@ -52,7 +52,8 @@ namespace ImageSharp.Formats /// Initializes a new instance of the class. /// /// The configuration. - public BmpDecoderCore(Configuration configuration) + /// The options + public BmpDecoderCore(Configuration configuration, IBmpDecoderOptions options) { this.configuration = configuration; } diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoder.cs b/src/ImageSharp/Formats/Bmp/BmpEncoder.cs index dc2bc0e972..dfba0b41c0 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 617edde8eb..e41c295012 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 a0f9ff8e05..0000000000 --- a/src/ImageSharp/Formats/Bmp/BmpEncoderOptions.cs +++ /dev/null @@ -1,45 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Formats -{ - /// - /// Encapsulates the options for the . - /// - public sealed class BmpEncoderOptions : EncoderOptions, IBmpEncoderOptions - { - /// - /// Initializes a new instance of the class. - /// - public BmpEncoderOptions() - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The options for the encoder. - private BmpEncoderOptions(IEncoderOptions options) - : base(options) - { - } - - /// - /// Gets or sets the number of bits per pixel. - /// - public BmpBitsPerPixel BitsPerPixel { get; set; } = BmpBitsPerPixel.Pixel24; - - /// - /// Converts the options to a instance with a cast - /// or by creating a new instance with the specfied options. - /// - /// The options for the encoder. - /// The options for the . - internal static IBmpEncoderOptions Create(IEncoderOptions options) - { - return options as IBmpEncoderOptions ?? new BmpEncoderOptions(options); - } - } -} diff --git a/src/ImageSharp/Formats/Bmp/BmpFileHeader.cs b/src/ImageSharp/Formats/Bmp/BmpFileHeader.cs index 4be602f4b1..f9b20a48f8 100644 --- a/src/ImageSharp/Formats/Bmp/BmpFileHeader.cs +++ b/src/ImageSharp/Formats/Bmp/BmpFileHeader.cs @@ -15,7 +15,7 @@ namespace ImageSharp.Formats /// All of the other integer values are stored in little-endian format /// (i.e. least-significant byte first). /// - internal class BmpFileHeader + internal sealed class BmpFileHeader { /// /// Defines of the data structure in the bitmap file. diff --git a/src/ImageSharp/Formats/Bmp/BmpFormat.cs b/src/ImageSharp/Formats/Bmp/BmpFormat.cs index bf73d31621..bd25eb9b75 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 bmp 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 0000000000..697ee0f981 --- /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 e652cb5044..dc6a489d34 100644 --- a/src/ImageSharp/Formats/Bmp/BmpInfoHeader.cs +++ b/src/ImageSharp/Formats/Bmp/BmpInfoHeader.cs @@ -10,7 +10,7 @@ namespace ImageSharp.Formats /// the screen. /// /// - internal class BmpInfoHeader + internal sealed class BmpInfoHeader { /// /// Defines of the data structure in the bitmap file. diff --git a/src/ImageSharp/Formats/Bmp/IBmpDecoderOptions.cs b/src/ImageSharp/Formats/Bmp/IBmpDecoderOptions.cs new file mode 100644 index 0000000000..9285b9cf7a --- /dev/null +++ b/src/ImageSharp/Formats/Bmp/IBmpDecoderOptions.cs @@ -0,0 +1,21 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using System; + using System.Collections.Generic; + using System.IO; + + using ImageSharp.PixelFormats; + + /// + /// Image decoder options for decoding Windows bitmap streams. + /// + internal interface IBmpDecoderOptions + { + // added this for consistancy so we can add stuff as required, no options currently availible + } +} diff --git a/src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs b/src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs index 6cf37cbae3..dd17043fad 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 5257b07b39..0000000000 --- a/src/ImageSharp/Formats/DecoderOptions.cs +++ /dev/null @@ -1,37 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - /// - /// Encapsulates the shared decoder options. - /// - public class DecoderOptions : IDecoderOptions - { - /// - /// Initializes a new instance of the class. - /// - public DecoderOptions() - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The decoder options - protected DecoderOptions(IDecoderOptions options) - { - if (options != null) - { - this.IgnoreMetadata = options.IgnoreMetadata; - } - } - - /// - /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded. - /// - public bool IgnoreMetadata { get; set; } = false; - } -} diff --git a/src/ImageSharp/Formats/EncoderOptions.cs b/src/ImageSharp/Formats/EncoderOptions.cs deleted file mode 100644 index 27a7e9781d..0000000000 --- a/src/ImageSharp/Formats/EncoderOptions.cs +++ /dev/null @@ -1,37 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - /// - /// Encapsulates the shared encoder options. - /// - public class EncoderOptions : IEncoderOptions - { - /// - /// Initializes a new instance of the class. - /// - public EncoderOptions() - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The encoder options - protected EncoderOptions(IEncoderOptions options) - { - if (options != null) - { - this.IgnoreMetadata = options.IgnoreMetadata; - } - } - - /// - /// Gets or sets a value indicating whether the metadata should be ignored when the image is being encoded. - /// - public bool IgnoreMetadata { get; set; } = false; - } -} diff --git a/src/ImageSharp/Formats/Gif/GifConfigurationModule.cs b/src/ImageSharp/Formats/Gif/GifConfigurationModule.cs new file mode 100644 index 0000000000..ee134d66cd --- /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 4af291c2ba..9bec6c48f9 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 88aaccf6a4..927289094f 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 ef0331c0d9..948103fed2 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. /// @@ -151,6 +157,10 @@ namespace ImageSharp.Formats } nextFlag = stream.ReadByte(); + if (nextFlag == -1) + { + break; + } } } finally @@ -269,7 +279,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 +290,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 +374,6 @@ namespace ImageSharp.Formats if (this.previousFrame == null) { - this.metaData.Quality = colorTableLength / 3; - // This initializes the image to become fully transparent because the alpha channel is zero. this.image = new Image(this.configuration, imageWidth, imageHeight, this.metaData); diff --git a/src/ImageSharp/Formats/Gif/GifDecoderOptions.cs b/src/ImageSharp/Formats/Gif/GifDecoderOptions.cs deleted file mode 100644 index bc7709f759..0000000000 --- a/src/ImageSharp/Formats/Gif/GifDecoderOptions.cs +++ /dev/null @@ -1,47 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Formats -{ - using System.Text; - - /// - /// Encapsulates the options for the . - /// - public sealed class GifDecoderOptions : DecoderOptions, IGifDecoderOptions - { - /// - /// Initializes a new instance of the class. - /// - public GifDecoderOptions() - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The options for the decoder. - private GifDecoderOptions(IDecoderOptions options) - : base(options) - { - } - - /// - /// Gets or sets the encoding that should be used when reading comments. - /// - public Encoding TextEncoding { get; set; } = GifConstants.DefaultEncoding; - - /// - /// Converts the options to a instance with a cast - /// or by creating a new instance with the specfied options. - /// - /// The options for the decoder. - /// The options for the . - internal static IGifDecoderOptions Create(IDecoderOptions options) - { - return options as IGifDecoderOptions ?? new GifDecoderOptions(options); - } - } -} diff --git a/src/ImageSharp/Formats/Gif/GifEncoder.cs b/src/ImageSharp/Formats/Gif/GifEncoder.cs index b5cadd834e..b48db56356 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 5ef7ca1658..81b3cfba4a 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 5d7c6e40b6..0000000000 --- a/src/ImageSharp/Formats/Gif/GifEncoderOptions.cs +++ /dev/null @@ -1,65 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Formats -{ - using System.Text; - - using Quantizers; - - /// - /// Encapsulates the options for the . - /// - public sealed class GifEncoderOptions : EncoderOptions, IGifEncoderOptions - { - /// - /// Initializes a new instance of the class. - /// - public GifEncoderOptions() - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The options for the encoder. - private GifEncoderOptions(IEncoderOptions options) - : base(options) - { - } - - /// - /// Gets or sets the encoding that should be used when writing comments. - /// - public Encoding TextEncoding { get; set; } = GifConstants.DefaultEncoding; - - /// - /// Gets or sets the quality of output for images. - /// - /// For gifs the value ranges from 1 to 256. - public int Quality { get; set; } - - /// - /// Gets or sets the transparency threshold. - /// - public byte Threshold { get; set; } = 128; - - /// - /// Gets or sets the quantizer for reducing the color count. - /// - public IQuantizer Quantizer { get; set; } - - /// - /// Converts the options to a instance with a - /// cast or by creating a new instance with the specfied options. - /// - /// The options for the encoder. - /// The options for the . - internal static IGifEncoderOptions Create(IEncoderOptions options) - { - return options as IGifEncoderOptions ?? new GifEncoderOptions(options); - } - } -} diff --git a/src/ImageSharp/Formats/Gif/GifFormat.cs b/src/ImageSharp/Formats/Gif/GifFormat.cs index 2851b0b6b2..744aadff9a 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 gif 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 0000000000..04fcfc516c --- /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 729bf1d111..caaa8932bb 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 c1d6b7ad86..c38ec7e451 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 d64203f6ce..ea9c9b5047 100644 --- a/src/ImageSharp/Formats/Gif/ImageExtensions.cs +++ b/src/ImageSharp/Formats/Gif/ImageExtensions.cs @@ -39,16 +39,16 @@ namespace ImageSharp /// The pixel format. /// The image this method extends. /// The stream to save the image to. - /// The options for the encoder. + /// The options for the encoder. /// Thrown if the stream is null. /// /// The . /// - public static Image SaveAsGif(this Image source, Stream stream, IGifEncoderOptions options) + public static Image SaveAsGif(this Image source, Stream stream, GifEncoder encoder) where TPixel : struct, IPixel { - GifEncoder encoder = new GifEncoder(); - encoder.Encode(source, stream, options); + encoder = encoder ?? new GifEncoder(); + encoder.Encode(source, stream); return source; } diff --git a/src/ImageSharp/Formats/IEncoderOptions.cs b/src/ImageSharp/Formats/IEncoderOptions.cs deleted file mode 100644 index 0fd3d1c438..0000000000 --- a/src/ImageSharp/Formats/IEncoderOptions.cs +++ /dev/null @@ -1,18 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - /// - /// Encapsulates the shared encoder options. - /// - public interface IEncoderOptions - { - /// - /// Gets a value indicating whether the metadata should be ignored when the image is being encoded. - /// - bool IgnoreMetadata { get; } - } -} diff --git a/src/ImageSharp/Formats/IImageDecoder.cs b/src/ImageSharp/Formats/IImageDecoder.cs index 4fd25df13e..66eabb1b82 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 a28511c173..4ad41ebc27 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 de2e400fa0..d6ddc0b0bb 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 0000000000..a53da07e8b --- /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 cdfd90d5e2..6830e2e4a5 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 a545179653..947c98ee2a 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 420af6b742..8fbf9e5a74 100644 --- a/src/ImageSharp/Formats/Jpeg/ImageExtensions.cs +++ b/src/ImageSharp/Formats/Jpeg/ImageExtensions.cs @@ -39,16 +39,16 @@ namespace ImageSharp /// The pixel format. /// The image this method extends. /// The stream to save the image to. - /// The options for the encoder. + /// The options for the encoder. /// Thrown if the stream is null. /// /// The . /// - public static Image SaveAsJpeg(this Image source, Stream stream, IJpegEncoderOptions options) + public static Image SaveAsJpeg(this Image source, Stream stream, JpegEncoder encoder) where TPixel : struct, IPixel { - JpegEncoder encoder = new JpegEncoder(); - encoder.Encode(source, stream, options); + encoder = encoder ?? new JpegEncoder(); + encoder.Encode(source, stream); return source; } diff --git a/src/ImageSharp/Formats/Jpeg/JpegConfigurationModule.cs b/src/ImageSharp/Formats/Jpeg/JpegConfigurationModule.cs new file mode 100644 index 0000000000..bb8c4e83f2 --- /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 dcda39842e..99c0399dcc 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 56d025504d..b3caddeca7 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs @@ -6,6 +6,7 @@ namespace ImageSharp.Formats { using System; + using System.Collections.Generic; using System.IO; using ImageSharp.PixelFormats; @@ -13,15 +14,20 @@ 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 (JpegDecoderCore decoder = new JpegDecoderCore(options, configuration)) + using (JpegDecoderCore decoder = new JpegDecoderCore(configuration, this)) { return decoder.Decode(stream); } diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 9716843719..0ce927e516 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -18,7 +18,7 @@ namespace ImageSharp.Formats /// /// Performs the jpeg decoding operation. /// - internal unsafe class JpegDecoderCore : IDisposable + internal sealed unsafe class JpegDecoderCore : IDisposable { /// /// The maximum number of color components @@ -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 152fd2c64c..6c6561468f 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 b65a56e73d..d2b7d2d7c4 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs @@ -17,7 +17,7 @@ namespace ImageSharp.Formats /// /// Image encoder for writing an image to a stream as a jpeg. /// - internal unsafe class JpegEncoderCore + internal sealed unsafe class JpegEncoderCore { /// /// The number of quantization tables. @@ -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 73e483164c..0000000000 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderOptions.cs +++ /dev/null @@ -1,56 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Formats -{ - /// - /// Encapsulates the options for the . - /// - public sealed class JpegEncoderOptions : EncoderOptions, IJpegEncoderOptions - { - /// - /// Initializes a new instance of the class. - /// - public JpegEncoderOptions() - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The options for the encoder. - private JpegEncoderOptions(IEncoderOptions options) - : base(options) - { - } - - /// - /// Gets or sets the quality, that will be used to encode the image. Quality - /// index must be between 0 and 100 (compression from max to min). - /// - /// - /// If the quality is less than or equal to 90, the subsampling ratio will switch to - /// - /// The quality of the jpg image from 0 to 100. - public int Quality { get; set; } - - /// - /// Gets or sets the subsample ration, that will be used to encode the image. - /// - /// The subsample ratio of the jpg image. - public JpegSubsample? Subsample { get; set; } - - /// - /// Converts the options to a instance with a - /// cast or by creating a new instance with the specfied options. - /// - /// The options for the encoder. - /// The options for the . - internal static IJpegEncoderOptions Create(IEncoderOptions options) - { - return options as IJpegEncoderOptions ?? new JpegEncoderOptions(options); - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/JpegFormat.cs b/src/ImageSharp/Formats/Jpeg/JpegFormat.cs index b93c6ae690..23cd5d8752 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 0000000000..b72b290c04 --- /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/Png/IPngDecoderOptions.cs b/src/ImageSharp/Formats/Png/IPngDecoderOptions.cs index cc6d194bfc..de163ba7e5 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 0008080d3f..8f0a4cd829 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 3841b313c2..c817385760 100644 --- a/src/ImageSharp/Formats/Png/ImageExtensions.cs +++ b/src/ImageSharp/Formats/Png/ImageExtensions.cs @@ -38,16 +38,16 @@ namespace ImageSharp /// The pixel format. /// The image this method extends. /// The stream to save the image to. - /// The options for the encoder. + /// The options for the encoder. /// Thrown if the stream is null. /// /// The . /// - public static Image SaveAsPng(this Image source, Stream stream, IPngEncoderOptions options) + public static Image SaveAsPng(this Image source, Stream stream, PngEncoder encoder) where TPixel : struct, IPixel { - 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 0000000000..bb1c2086cc --- /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 0000000000..84e76a3419 --- /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 3a34147e2c..61a8cb2127 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 2ff6a43085..d9df44a091 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]; @@ -262,45 +267,48 @@ namespace ImageSharp.Formats /// The bytes to convert from. Cannot be null. /// The number of bytes per scanline /// The number of bits per value. - /// The resulting array. Is never null. + /// The resulting array. Is never null. /// is null. /// is less than or equals than zero. - private static byte[] ToArrayByBitsLength(byte[] source, int bytesPerScanline, int bits) + private static Span ToArrayByBitsLength(Span source, int bytesPerScanline, int bits) { Guard.NotNull(source, nameof(source)); Guard.MustBeGreaterThan(bits, 0, nameof(bits)); - byte[] result; - - if (bits < 8) + if (bits >= 8) { - result = new byte[bytesPerScanline * 8 / bits]; - int mask = 0xFF >> (8 - bits); - int resultOffset = 0; + return source; + } - // ReSharper disable once ForCanBeConvertedToForeach - // First byte is the marker so skip. - for (int i = 1; i < bytesPerScanline; i++) + byte[] result = new byte[bytesPerScanline * 8 / bits]; + int mask = 0xFF >> (8 - bits); + int resultOffset = 0; + + for (int i = 0; i < bytesPerScanline; i++) + { + byte b = source[i]; + for (int shift = 0; shift < 8; shift += bits) { - byte b = source[i]; - for (int shift = 0; shift < 8; shift += bits) - { - int colorIndex = (b >> (8 - bits - shift)) & mask; + int colorIndex = (b >> (8 - bits - shift)) & mask; - result[resultOffset] = (byte)colorIndex; + result[resultOffset] = (byte)colorIndex; - resultOffset++; - } + resultOffset++; } } - else - { - result = source; - } return result; } + private static bool IsCriticalChunk(PngChunk chunk) + { + return + chunk.Type == PngChunkTypes.Header || + chunk.Type == PngChunkTypes.Palette || + chunk.Type == PngChunkTypes.Data || + chunk.Type == PngChunkTypes.End; + } + /// /// Reads the data chunk containing physical dimension data. /// @@ -344,7 +352,7 @@ namespace ImageSharp.Formats /// The private int CalculateBytesPerPixel() { - switch (this.PngColorType) + switch (this.pngColorType) { case PngColorType.Grayscale: return 1; @@ -570,13 +578,15 @@ namespace ImageSharp.Formats { var color = default(TPixel); Span rowSpan = pixels.GetRowSpan(this.currentRow); + + // Trim the first marker byte from the buffer var scanlineBuffer = new Span(defilteredScanline, 1); - switch (this.PngColorType) + switch (this.pngColorType) { case PngColorType.Grayscale: int factor = 255 / ((int)Math.Pow(2, this.header.BitDepth) - 1); - byte[] newScanline1 = ToArrayByBitsLength(defilteredScanline, this.bytesPerScanline, this.header.BitDepth); + Span newScanline1 = ToArrayByBitsLength(scanlineBuffer, this.bytesPerScanline, this.header.BitDepth); for (int x = 0; x < this.header.Width; x++) { byte intensity = (byte)(newScanline1[x] * factor); @@ -590,10 +600,10 @@ namespace ImageSharp.Formats for (int x = 0; x < this.header.Width; x++) { - int offset = 1 + (x * this.bytesPerPixel); + int offset = x * this.bytesPerPixel; - byte intensity = defilteredScanline[offset]; - byte alpha = defilteredScanline[offset + this.bytesPerSample]; + byte intensity = scanlineBuffer[offset]; + byte alpha = scanlineBuffer[offset + this.bytesPerSample]; color.PackFromRgba32(new Rgba32(intensity, intensity, intensity, alpha)); rowSpan[x] = color; @@ -603,7 +613,7 @@ namespace ImageSharp.Formats case PngColorType.Palette: - this.ProcessScanlineFromPalette(defilteredScanline, rowSpan); + this.ProcessScanlineFromPalette(scanlineBuffer, rowSpan); break; @@ -668,10 +678,10 @@ namespace ImageSharp.Formats /// The type of pixel we are expanding to /// The scanline /// Thecurrent output image row - private void ProcessScanlineFromPalette(byte[] defilteredScanline, Span row) + private void ProcessScanlineFromPalette(Span defilteredScanline, Span row) where TPixel : struct, IPixel { - byte[] newScanline = ToArrayByBitsLength(defilteredScanline, this.bytesPerScanline, this.header.BitDepth); + Span newScanline = ToArrayByBitsLength(defilteredScanline, this.bytesPerScanline, this.header.BitDepth); byte[] pal = this.palette; var color = default(TPixel); @@ -683,19 +693,11 @@ namespace ImageSharp.Formats // channel and we should try to read it. for (int x = 0; x < this.header.Width; x++) { - int index = newScanline[x + 1]; + int index = newScanline[x]; int pixelOffset = index * 3; rgba.A = this.paletteAlpha.Length > index ? this.paletteAlpha[index] : (byte)255; - - if (rgba.A > 0) - { - rgba.Rgb = pal.GetRgb24(pixelOffset); - } - else - { - rgba = default(Rgba32); - } + rgba.Rgb = pal.GetRgb24(pixelOffset); color.PackFromRgba32(rgba); row[x] = color; @@ -707,7 +709,7 @@ namespace ImageSharp.Formats for (int x = 0; x < this.header.Width; x++) { - int index = newScanline[x + 1]; + int index = newScanline[x]; int pixelOffset = index * 3; rgba.Rgb = pal.GetRgb24(pixelOffset); @@ -731,15 +733,15 @@ namespace ImageSharp.Formats { var color = default(TPixel); - switch (this.PngColorType) + // Trim the first marker byte from the buffer + var scanlineBuffer = new Span(defilteredScanline, 1); + + switch (this.pngColorType) { case PngColorType.Grayscale: int factor = 255 / ((int)Math.Pow(2, this.header.BitDepth) - 1); - byte[] newScanline1 = ToArrayByBitsLength( - defilteredScanline, - this.bytesPerScanline, - this.header.BitDepth); - for (int x = pixelOffset, o = 1; x < this.header.Width; x += increment, o++) + Span newScanline1 = ToArrayByBitsLength(scanlineBuffer, this.bytesPerScanline, this.header.BitDepth); + for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o++) { byte intensity = (byte)(newScanline1[o] * factor); color.PackFromRgba32(new Rgba32(intensity, intensity, intensity)); @@ -750,10 +752,10 @@ namespace ImageSharp.Formats case PngColorType.GrayscaleWithAlpha: - for (int x = pixelOffset, o = 1; x < this.header.Width; x += increment, o += this.bytesPerPixel) + for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += this.bytesPerPixel) { - byte intensity = defilteredScanline[o]; - byte alpha = defilteredScanline[o + this.bytesPerSample]; + byte intensity = scanlineBuffer[o]; + byte alpha = scanlineBuffer[o + this.bytesPerSample]; color.PackFromRgba32(new Rgba32(intensity, intensity, intensity, alpha)); rowSpan[x] = color; } @@ -762,31 +764,20 @@ namespace ImageSharp.Formats case PngColorType.Palette: - byte[] newScanline = ToArrayByBitsLength( - defilteredScanline, - this.bytesPerScanline, - this.header.BitDepth); + Span newScanline = ToArrayByBitsLength(scanlineBuffer, this.bytesPerScanline, this.header.BitDepth); var rgba = default(Rgba32); if (this.paletteAlpha != null && this.paletteAlpha.Length > 0) { // If the alpha palette is not null and has one or more entries, this means, that the image contains an alpha // channel and we should try to read it. - for (int x = pixelOffset, o = 1; x < this.header.Width; x += increment, o++) + for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o++) { int index = newScanline[o]; int offset = index * 3; rgba.A = this.paletteAlpha.Length > index ? this.paletteAlpha[index] : (byte)255; - - if (rgba.A > 0) - { - rgba.Rgb = this.palette.GetRgb24(offset); - } - else - { - rgba = default(Rgba32); - } + rgba.Rgb = this.palette.GetRgb24(offset); color.PackFromRgba32(rgba); rowSpan[x] = color; @@ -796,7 +787,7 @@ namespace ImageSharp.Formats { rgba.A = 255; - for (int x = pixelOffset, o = 1; x < this.header.Width; x += increment, o++) + for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o++) { int index = newScanline[o]; int offset = index * 3; @@ -820,14 +811,12 @@ namespace ImageSharp.Formats using (var compressed = new Buffer(length)) { // TODO: Should we use pack from vector here instead? - this.From16BitTo8Bit(new Span(defilteredScanline), compressed, length); - for (int x = pixelOffset, o = 1; - x < this.header.Width; - x += increment, o += this.bytesPerPixel) + this.From16BitTo8Bit(scanlineBuffer, compressed, length); + for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += 3) { rgba.R = compressed[o]; - rgba.G = compressed[o + this.bytesPerSample]; - rgba.B = compressed[o + (2 * this.bytesPerSample)]; + rgba.G = compressed[o + 1]; + rgba.B = compressed[o + 2]; color.PackFromRgba32(rgba); rowSpan[x] = color; @@ -836,11 +825,11 @@ namespace ImageSharp.Formats } else { - for (int x = pixelOffset, o = 1; x < this.header.Width; x += increment, o += this.bytesPerPixel) + for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += this.bytesPerPixel) { - rgba.R = defilteredScanline[o]; - rgba.G = defilteredScanline[o + this.bytesPerSample]; - rgba.B = defilteredScanline[o + (2 * this.bytesPerSample)]; + rgba.R = scanlineBuffer[o]; + rgba.G = scanlineBuffer[o + this.bytesPerSample]; + rgba.B = scanlineBuffer[o + (2 * this.bytesPerSample)]; color.PackFromRgba32(rgba); rowSpan[x] = color; @@ -857,13 +846,13 @@ namespace ImageSharp.Formats using (var compressed = new Buffer(length)) { // TODO: Should we use pack from vector here instead? - this.From16BitTo8Bit(new Span(defilteredScanline), compressed, length); - for (int x = pixelOffset, o = 1; x < this.header.Width; x += increment, o += this.bytesPerPixel) + this.From16BitTo8Bit(scanlineBuffer, compressed, length); + for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += 4) { rgba.R = compressed[o]; - rgba.G = compressed[o + this.bytesPerSample]; - rgba.B = compressed[o + (2 * this.bytesPerSample)]; - rgba.A = compressed[o + (3 * this.bytesPerSample)]; + rgba.G = compressed[o + 1]; + rgba.B = compressed[o + 2]; + rgba.A = compressed[o + 3]; color.PackFromRgba32(rgba); rowSpan[x] = color; @@ -872,12 +861,12 @@ namespace ImageSharp.Formats } else { - for (int x = pixelOffset, o = 1; x < this.header.Width; x += increment, o += this.bytesPerPixel) + for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += this.bytesPerPixel) { - rgba.R = defilteredScanline[o]; - rgba.G = defilteredScanline[o + this.bytesPerSample]; - rgba.B = defilteredScanline[o + (2 * this.bytesPerSample)]; - rgba.A = defilteredScanline[o + (3 * this.bytesPerSample)]; + rgba.R = scanlineBuffer[o]; + rgba.G = scanlineBuffer[o + this.bytesPerSample]; + rgba.B = scanlineBuffer[o + (2 * this.bytesPerSample)]; + rgba.A = scanlineBuffer[o + (3 * this.bytesPerSample)]; color.PackFromRgba32(rgba); rowSpan[x] = color; @@ -896,7 +885,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 +901,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 +956,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; } /// @@ -1020,9 +1009,9 @@ namespace ImageSharp.Formats this.crc.Update(this.chunkTypeBuffer); this.crc.Update(chunk.Data, 0, chunk.Length); - if (this.crc.Value != chunk.Crc) + if (this.crc.Value != chunk.Crc && IsCriticalChunk(chunk)) { - throw new ImageFormatException("CRC Error. PNG Image chunk is corrupt!"); + throw new ImageFormatException($"CRC Error. PNG {chunk.Type} chunk is corrupt!"); } } diff --git a/src/ImageSharp/Formats/Png/PngDecoderOptions.cs b/src/ImageSharp/Formats/Png/PngDecoderOptions.cs deleted file mode 100644 index e8990ec456..0000000000 --- a/src/ImageSharp/Formats/Png/PngDecoderOptions.cs +++ /dev/null @@ -1,49 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Formats -{ - using System.Text; - - /// - /// Encapsulates the options for the . - /// - public sealed class PngDecoderOptions : DecoderOptions, IPngDecoderOptions - { - private static readonly Encoding DefaultEncoding = Encoding.GetEncoding("ASCII"); - - /// - /// Initializes a new instance of the class. - /// - public PngDecoderOptions() - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The options for the decoder. - private PngDecoderOptions(IDecoderOptions options) - : base(options) - { - } - - /// - /// Gets or sets the encoding that should be used when reading text chunks. - /// - public Encoding TextEncoding { get; set; } = DefaultEncoding; - - /// - /// Converts the options to a instance with a cast - /// or by creating a new instance with the specfied options. - /// - /// The options for the decoder. - /// The options for the . - internal static IPngDecoderOptions Create(IDecoderOptions options) - { - return options as IPngDecoderOptions ?? new PngDecoderOptions(options); - } - } -} diff --git a/src/ImageSharp/Formats/Png/PngEncoder.cs b/src/ImageSharp/Formats/Png/PngEncoder.cs index f89b624f78..bfd82a0748 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 645df05481..cfbd0c4493 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 90175c6d65..0000000000 --- a/src/ImageSharp/Formats/Png/PngEncoderOptions.cs +++ /dev/null @@ -1,82 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Formats -{ - using Quantizers; - - /// - /// Encapsulates the options for the . - /// - public sealed class PngEncoderOptions : EncoderOptions, IPngEncoderOptions - { - /// - /// Initializes a new instance of the class. - /// - public PngEncoderOptions() - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The options for the encoder. - private PngEncoderOptions(IEncoderOptions options) - : base(options) - { - } - - /// - /// Gets or sets the quality of output for images. - /// - public int Quality { get; set; } - - /// - /// Gets or sets the png color type - /// - public PngColorType PngColorType { get; set; } = PngColorType.RgbWithAlpha; - - /// - /// Gets or sets the compression level 1-9. - /// Defaults to 6. - /// - public int CompressionLevel { get; set; } = 6; - - /// - /// Gets or sets the gamma value, that will be written - /// the the stream, when the property - /// is set to true. The default value is 2.2F. - /// - /// The gamma value of the image. - public float Gamma { get; set; } = 2.2F; - - /// - /// Gets or sets quantizer for reducing the color count. - /// - public IQuantizer Quantizer { get; set; } - - /// - /// Gets or sets the transparency threshold. - /// - public byte Threshold { get; set; } = 255; - - /// - /// Gets or sets a value indicating whether this instance should write - /// gamma information to the stream. The default value is false. - /// - public bool WriteGamma { get; set; } - - /// - /// Converts the options to a instance with a - /// cast or by creating a new instance with the specfied options. - /// - /// The options for the encoder. - /// The options for the . - internal static IPngEncoderOptions Create(IEncoderOptions options) - { - return options as IPngEncoderOptions ?? new PngEncoderOptions(options); - } - } -} diff --git a/src/ImageSharp/Formats/Png/PngFormat.cs b/src/ImageSharp/Formats/Png/PngFormat.cs index 0f5a74da04..6df2aa7c58 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 png 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 50d6cc9eca..9cf823fa19 100644 --- a/src/ImageSharp/Formats/Png/PngHeader.cs +++ b/src/ImageSharp/Formats/Png/PngHeader.cs @@ -8,7 +8,7 @@ namespace ImageSharp.Formats /// /// Represents the png header chunk. /// - public sealed class PngHeader + internal sealed class PngHeader { /// /// Gets or sets the dimension in x-direction of the image in pixels. diff --git a/src/ImageSharp/Formats/Png/PngImageFormatDetector.cs b/src/ImageSharp/Formats/Png/PngImageFormatDetector.cs new file mode 100644 index 0000000000..fdea3eb8ae --- /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 ec3b8ebe73..b43aff0b78 100644 --- a/src/ImageSharp/Formats/Png/PngInterlaceMode.cs +++ b/src/ImageSharp/Formats/Png/PngInterlaceMode.cs @@ -8,7 +8,7 @@ namespace ImageSharp.Formats /// /// Provides enumeration of available PNG interlace modes. /// - public enum PngInterlaceMode : byte + internal enum PngInterlaceMode : byte { /// /// Non interlaced diff --git a/src/ImageSharp/Formats/Png/Zlib/IChecksum.cs b/src/ImageSharp/Formats/Png/Zlib/IChecksum.cs index 935cdf9532..cbd292dc4c 100644 --- a/src/ImageSharp/Formats/Png/Zlib/IChecksum.cs +++ b/src/ImageSharp/Formats/Png/Zlib/IChecksum.cs @@ -12,7 +12,7 @@ namespace ImageSharp.Formats /// Value. The complete checksum object can also be reset /// so it can be used again with new data. /// - public interface IChecksum + internal interface IChecksum { /// /// Gets the data checksum computed so far. diff --git a/src/ImageSharp/Formats/Png/Zlib/ZlibInflateStream.cs b/src/ImageSharp/Formats/Png/Zlib/ZlibInflateStream.cs index 0743d8ded3..136f919da7 100644 --- a/src/ImageSharp/Formats/Png/Zlib/ZlibInflateStream.cs +++ b/src/ImageSharp/Formats/Png/Zlib/ZlibInflateStream.cs @@ -9,7 +9,7 @@ /// /// Provides methods and properties for deframing streams from PNGs. /// - internal class ZlibInflateStream : Stream + internal sealed class ZlibInflateStream : Stream { /// /// The inner raw memory stream diff --git a/src/ImageSharp/IConfigurationModule.cs b/src/ImageSharp/IConfigurationModule.cs new file mode 100644 index 0000000000..95b92ed056 --- /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 55abdb244f..b9e8e392a3 100644 --- a/src/ImageSharp/Image/IImage.cs +++ b/src/ImageSharp/Image/IImage.cs @@ -12,11 +12,6 @@ namespace ImageSharp /// internal interface IImage : IImageBase { - /// - /// Gets the currently loaded image format. - /// - IImageFormat CurrentImageFormat { get; } - /// /// Gets the meta data of the image. /// diff --git a/src/ImageSharp/Image/Image.Decode.cs b/src/ImageSharp/Image/Image.Decode.cs index c162f17726..1013107062 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 628092359b..c7023b860b 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 a135c43f5e..ca395ce088 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 1bcb5adc9a..29d93ae859 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 1cda8ebc7a..5e8bcab31b 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 0000000000..f79191eae6 --- /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 9b43880887..8733131a74 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 a894ea53a4..4b3681c744 100644 --- a/src/ImageSharp/Memory/Buffer.cs +++ b/src/ImageSharp/Memory/Buffer.cs @@ -115,6 +115,16 @@ namespace ImageSharp.Memory } } + /// + /// Converts to an . + /// + /// The to convert. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ReadOnlySpan(Buffer buffer) + { + return new ReadOnlySpan(buffer.Array, 0, buffer.Length); + } + /// /// Converts to an . /// diff --git a/src/ImageSharp/MetaData/ImageMetaData.cs b/src/ImageSharp/MetaData/ImageMetaData.cs index 91ea9ac4bf..5361b486d4 100644 --- a/src/ImageSharp/MetaData/ImageMetaData.cs +++ b/src/ImageSharp/MetaData/ImageMetaData.cs @@ -50,7 +50,6 @@ namespace ImageSharp this.HorizontalResolution = other.HorizontalResolution; this.VerticalResolution = other.VerticalResolution; - this.Quality = other.Quality; this.FrameDelay = other.FrameDelay; this.DisposalMethod = other.DisposalMethod; this.RepeatCount = other.RepeatCount; @@ -127,11 +126,6 @@ namespace ImageSharp /// A list of image properties. public IList Properties { get; } = new List(); - /// - /// Gets or sets the quality of the image. This affects the output quality of lossy image formats. - /// - public int Quality { get; set; } - /// /// Gets or sets the number of times any animation is repeated. /// 0 means to repeat indefinitely. diff --git a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs index e6b1d180f1..d563d072aa 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs @@ -79,7 +79,7 @@ namespace ImageSharp.Processing.Processors return; } - this.processMatrix = Matrix3x2Extensions.CreateRotation(-this.Angle, new Point(0, 0)); + this.processMatrix = Matrix3x2Extensions.CreateRotationDegrees(-this.Angle, new Point(0, 0)); if (this.Expand) { this.CreateNewCanvas(sourceRectangle, this.processMatrix); diff --git a/src/ImageSharp/Processing/Transforms/AutoOrient.cs b/src/ImageSharp/Processing/Transforms/AutoOrient.cs index 07e5d5bc9a..7e7c66460c 100644 --- a/src/ImageSharp/Processing/Transforms/AutoOrient.cs +++ b/src/ImageSharp/Processing/Transforms/AutoOrient.cs @@ -5,6 +5,7 @@ namespace ImageSharp { + using System; using ImageSharp.PixelFormats; using ImageSharp.Processing; @@ -77,7 +78,16 @@ namespace ImageSharp return Orientation.Unknown; } - var orientation = (Orientation)value.Value; + Orientation orientation; + if (value.DataType == ExifDataType.Short) + { + orientation = (Orientation)value.Value; + } + else + { + orientation = (Orientation)Convert.ToUInt16(value.Value); + source.MetaData.ExifProfile.RemoveValue(ExifTag.Orientation); + } source.MetaData.ExifProfile.SetValue(ExifTag.Orientation, (ushort)Orientation.TopLeft); diff --git a/tests/ImageSharp.Benchmarks/BenchmarkBase.cs b/tests/ImageSharp.Benchmarks/BenchmarkBase.cs index d6e8ac6925..41574d109f 100644 --- a/tests/ImageSharp.Benchmarks/BenchmarkBase.cs +++ b/tests/ImageSharp.Benchmarks/BenchmarkBase.cs @@ -13,10 +13,6 @@ protected BenchmarkBase() { // Add Image Formats - Configuration.Default.AddImageFormat(new JpegFormat()); - Configuration.Default.AddImageFormat(new PngFormat()); - Configuration.Default.AddImageFormat(new BmpFormat()); - Configuration.Default.AddImageFormat(new GifFormat()); } } } diff --git a/tests/ImageSharp.Benchmarks/Image/EncodeIndexedPng.cs b/tests/ImageSharp.Benchmarks/Image/EncodeIndexedPng.cs index 88e82f0796..02e3211a76 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 3ec83aa7cf..81bb235eea 100644 --- a/tests/ImageSharp.Benchmarks/Image/EncodePng.cs +++ b/tests/ImageSharp.Benchmarks/Image/EncodePng.cs @@ -71,7 +71,7 @@ namespace ImageSharp.Benchmarks.Image new OctreeQuantizer() : new PaletteQuantizer(); - PngEncoderOptions options = new PngEncoderOptions() { Quantizer = quantizer }; + PngEncoder options = new PngEncoder() { Quantizer = quantizer }; this.bmpCore.SaveAsPng(memoryStream, options); } } diff --git a/tests/ImageSharp.Tests/ConfigurationTests.cs b/tests/ImageSharp.Tests/ConfigurationTests.cs index aa3c4edfc1..417588180a 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 ed2b7726c6..7ddc2de5a4 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 3df3a508c8..da5028f04f 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 7bacebe42e..bbabdf0ea6 100644 --- a/tests/ImageSharp.Tests/Drawing/FillPatternTests.cs +++ b/tests/ImageSharp.Tests/Drawing/FillPatternTests.cs @@ -25,10 +25,7 @@ namespace ImageSharp.Tests.Drawing .Fill(background) .Fill(brush); - using (FileStream output = File.OpenWrite($"{path}/{name}.png")) - { - image.Save(output); - } + image.Save($"{path}/{name}.png"); using (PixelAccessor sourcePixels = image.Lock()) { @@ -54,10 +51,7 @@ namespace ImageSharp.Tests.Drawing } } } - using (FileStream output = File.OpenWrite($"{path}/{name}x4.png")) - { - image.Resize(80, 80).Save(output); - } + image.Resize(80, 80).Save($"{path}/{name}x4.png"); } } diff --git a/tests/ImageSharp.Tests/Drawing/FillSolidBrushTests.cs b/tests/ImageSharp.Tests/Drawing/FillSolidBrushTests.cs index dc0b83615d..ce127cfe03 100644 --- a/tests/ImageSharp.Tests/Drawing/FillSolidBrushTests.cs +++ b/tests/ImageSharp.Tests/Drawing/FillSolidBrushTests.cs @@ -16,7 +16,7 @@ namespace ImageSharp.Tests.Drawing using Xunit; - public class FillSolidBrushTests: FileTestBase + public class FillSolidBrushTests : FileTestBase { [Fact] public void ImageShouldBeFloodFilledWithColorOnDefaultBackground() @@ -24,12 +24,9 @@ namespace ImageSharp.Tests.Drawing string path = this.CreateOutputDirectory("Fill", "SolidBrush"); using (Image image = new Image(500, 500)) { - using (FileStream output = File.OpenWrite($"{path}/DefaultBack.png")) - { - image - .Fill(Rgba32.HotPink) - .Save(output); - } + image + .Fill(Rgba32.HotPink) + .Save($"{path}/DefaultBack.png"); using (PixelAccessor sourcePixels = image.Lock()) { @@ -46,13 +43,10 @@ namespace ImageSharp.Tests.Drawing string path = this.CreateOutputDirectory("Fill", "SolidBrush"); using (Image image = new Image(500, 500)) { - using (FileStream output = File.OpenWrite($"{path}/Simple.png")) - { - image - .BackgroundColor(Rgba32.Blue) - .Fill(Rgba32.HotPink) - .Save(output); - } + image + .BackgroundColor(Rgba32.Blue) + .Fill(Rgba32.HotPink) + .Save($"{path}/Simple.png"); using (PixelAccessor sourcePixels = image.Lock()) { @@ -71,13 +65,11 @@ namespace ImageSharp.Tests.Drawing { Rgba32 color = new Rgba32(Rgba32.HotPink.R, Rgba32.HotPink.G, Rgba32.HotPink.B, 150); - using (FileStream output = File.OpenWrite($"{path}/Opacity.png")) - { - image - .BackgroundColor(Rgba32.Blue) - .Fill(color) - .Save(output); - } + image + .BackgroundColor(Rgba32.Blue) + .Fill(color) + .Save($"{path}/Opacity.png"); + //shift background color towards forground color by the opacity amount Rgba32 mergedColor = new Rgba32(Vector4.Lerp(Rgba32.Blue.ToVector4(), Rgba32.HotPink.ToVector4(), 150f / 255f)); diff --git a/tests/ImageSharp.Tests/Drawing/LineComplexPolygonTests.cs b/tests/ImageSharp.Tests/Drawing/LineComplexPolygonTests.cs index 1f35a37884..e058572fb3 100644 --- a/tests/ImageSharp.Tests/Drawing/LineComplexPolygonTests.cs +++ b/tests/ImageSharp.Tests/Drawing/LineComplexPolygonTests.cs @@ -34,13 +34,10 @@ namespace ImageSharp.Tests.Drawing using (Image image = new Image(500, 500)) { - using (FileStream output = File.OpenWrite($"{path}/Simple.png")) - { - image - .BackgroundColor(Rgba32.Blue) - .Draw(Rgba32.HotPink, 5, simplePath.Clip(hole1)) - .Save(output); - } + image + .BackgroundColor(Rgba32.Blue) + .Draw(Rgba32.HotPink, 5, simplePath.Clip(hole1)) + .Save($"{path}/Simple.png"); using (PixelAccessor sourcePixels = image.Lock()) { @@ -84,13 +81,10 @@ namespace ImageSharp.Tests.Drawing using (Image image = new Image(500, 500)) { - using (FileStream output = File.OpenWrite($"{path}/SimpleVanishHole.png")) - { - image - .BackgroundColor(Rgba32.Blue) - .Draw(Rgba32.HotPink, 5, simplePath.Clip(hole1)) - .Save(output); - } + image + .BackgroundColor(Rgba32.Blue) + .Draw(Rgba32.HotPink, 5, simplePath.Clip(hole1)) + .Save($"{path}/SimpleVanishHole.png"); using (PixelAccessor sourcePixels = image.Lock()) { @@ -135,13 +129,10 @@ namespace ImageSharp.Tests.Drawing using (Image image = new Image(500, 500)) { - using (FileStream output = File.OpenWrite($"{path}/SimpleOverlapping.png")) - { - image - .BackgroundColor(Rgba32.Blue) - .Draw(Rgba32.HotPink, 5, simplePath.Clip(hole1)) - .Save(output); - } + image + .BackgroundColor(Rgba32.Blue) + .Draw(Rgba32.HotPink, 5, simplePath.Clip(hole1)) + .Save($"{path}/SimpleOverlapping.png"); using (PixelAccessor sourcePixels = image.Lock()) { @@ -181,13 +172,10 @@ namespace ImageSharp.Tests.Drawing using (Image image = new Image(500, 500)) { - using (FileStream output = File.OpenWrite($"{path}/Dashed.png")) - { - image - .BackgroundColor(Rgba32.Blue) - .Draw(Pens.Dash(Rgba32.HotPink, 5), simplePath.Clip(hole1)) - .Save(output); - } + image + .BackgroundColor(Rgba32.Blue) + .Draw(Pens.Dash(Rgba32.HotPink, 5), simplePath.Clip(hole1)) + .Save($"{path}/Dashed.png"); } } @@ -209,13 +197,10 @@ namespace ImageSharp.Tests.Drawing using (Image image = new Image(500, 500)) { - using (FileStream output = File.OpenWrite($"{path}/Opacity.png")) - { - image - .BackgroundColor(Rgba32.Blue) - .Draw(color, 5, simplePath.Clip(hole1)) - .Save(output); - } + image + .BackgroundColor(Rgba32.Blue) + .Draw(color, 5, simplePath.Clip(hole1)) + .Save($"{path}/Opacity.png"); //shift background color towards forground color by the opacity amount Rgba32 mergedColor = new Rgba32(Vector4.Lerp(Rgba32.Blue.ToVector4(), Rgba32.HotPink.ToVector4(), 150f / 255f)); diff --git a/tests/ImageSharp.Tests/Drawing/LineTests.cs b/tests/ImageSharp.Tests/Drawing/LineTests.cs index c2a5d240c7..f47d566967 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 fcd5f3dc82..501b10e4af 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 4655f1c14e..9c62e860a8 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 b6b143b5d2..ba904cb3f2 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 5e0244d02f..c3af3d5c28 100644 --- a/tests/ImageSharp.Tests/Drawing/SolidComplexPolygonTests.cs +++ b/tests/ImageSharp.Tests/Drawing/SolidComplexPolygonTests.cs @@ -31,16 +31,13 @@ namespace ImageSharp.Tests.Drawing new Vector2(93, 85), new Vector2(65, 137))); IPath clipped = simplePath.Clip(hole1); - // var clipped = new Rectangle(10, 10, 100, 100).Clip(new Rectangle(20, 0, 20, 20)); + // var clipped = new Rectangle(10, 10, 100, 100).Clip(new Rectangle(20, 0, 20, 20)); using (Image image = new Image(500, 500)) { - using (FileStream output = File.OpenWrite($"{path}/Simple.png")) - { - image - .BackgroundColor(Rgba32.Blue) - .Fill(Rgba32.HotPink, clipped) - .Save(output); - } + image + .BackgroundColor(Rgba32.Blue) + .Fill(Rgba32.HotPink, clipped) + .Save($"{path}/Simple.png"); using (PixelAccessor sourcePixels = image.Lock()) { @@ -69,13 +66,10 @@ namespace ImageSharp.Tests.Drawing using (Image image = new Image(500, 500)) { - using (FileStream output = File.OpenWrite($"{path}/SimpleOverlapping.png")) - { - image - .BackgroundColor(Rgba32.Blue) - .Fill(Rgba32.HotPink, simplePath.Clip(hole1)) - .Save(output); - } + image + .BackgroundColor(Rgba32.Blue) + .Fill(Rgba32.HotPink, simplePath.Clip(hole1)) + .Save($"{path}/SimpleOverlapping.png"); using (PixelAccessor sourcePixels = image.Lock()) { @@ -104,13 +98,10 @@ namespace ImageSharp.Tests.Drawing using (Image image = new Image(500, 500)) { - using (FileStream output = File.OpenWrite($"{path}/Opacity.png")) - { - image - .BackgroundColor(Rgba32.Blue) - .Fill(color, simplePath.Clip(hole1)) - .Save(output); - } + image + .BackgroundColor(Rgba32.Blue) + .Fill(color, simplePath.Clip(hole1)) + .Save($"{path}/Opacity.png"); //shift background color towards forground color by the opacity amount Rgba32 mergedColor = new Rgba32(Vector4.Lerp(Rgba32.Blue.ToVector4(), Rgba32.HotPink.ToVector4(), 150f / 255f)); diff --git a/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs b/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs index b87f0a11d4..793bcfc9f0 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/FileTestBase.cs b/tests/ImageSharp.Tests/FileTestBase.cs index 51a1562f53..08ed69f3e2 100644 --- a/tests/ImageSharp.Tests/FileTestBase.cs +++ b/tests/ImageSharp.Tests/FileTestBase.cs @@ -79,8 +79,8 @@ namespace ImageSharp.Tests // TestFile.Create(TestImages.Bmp.NegHeight), // Perf: Enable for local testing only TestFile.Create(TestImages.Png.Splash), // TestFile.Create(TestImages.Png.Cross), // Perf: Enable for local testing only - // TestFile.Create(TestImages.Png.ChunkLength1), // Perf: Enable for local testing only - // TestFile.Create(TestImages.Png.ChunkLength2), // Perf: Enable for local testing only + // TestFile.Create(TestImages.Png.Bad.ChunkLength1), // Perf: Enable for local testing only + // TestFile.Create(TestImages.Png.Bad.ChunkLength2), // Perf: Enable for local testing only // TestFile.Create(TestImages.Png.Powerpoint), // Perf: Enable for local testing only // TestFile.Create(TestImages.Png.Blur), // Perf: Enable for local testing only // TestFile.Create(TestImages.Png.Indexed), // Perf: Enable for local testing only diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs index cf073d3d03..e5af28d8b4 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs @@ -31,7 +31,7 @@ namespace ImageSharp.Tests string filename = file.GetFileNameWithoutExtension(bitsPerPixel); using (Image image = file.CreateImage()) { - image.Save($"{path}/{filename}.bmp", new BmpEncoderOptions { BitsPerPixel = bitsPerPixel }); + image.Save($"{path}/{filename}.bmp", new BmpEncoder { BitsPerPixel = bitsPerPixel }); } } } diff --git a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs index c8b4a71ef6..c6ef3ae08a 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 89df2d086e..06bfd8990d 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs @@ -33,7 +33,7 @@ namespace ImageSharp.Tests [Fact] public void Decode_IgnoreMetadataIsFalse_CommentsAreRead() { - DecoderOptions options = new DecoderOptions() + GifDecoder options = new GifDecoder() { IgnoreMetadata = false }; @@ -51,7 +51,7 @@ namespace ImageSharp.Tests [Fact] public void Decode_IgnoreMetadataIsTrue_CommentsAreIgnored() { - DecoderOptions options = new DecoderOptions() + GifDecoder options = new GifDecoder() { IgnoreMetadata = true }; @@ -67,7 +67,7 @@ namespace ImageSharp.Tests [Fact] public void Decode_TextEncodingSetToUnicode_TextIsReadWithCorrectEncoding() { - GifDecoderOptions options = new GifDecoderOptions() + GifDecoder options = new GifDecoder() { TextEncoding = Encoding.Unicode }; diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs index 70cc8e2ba6..c365396863 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs @@ -29,7 +29,7 @@ namespace ImageSharp.Tests [Fact] public void Encode_IgnoreMetadataIsFalse_CommentsAreWritten() { - EncoderOptions options = new EncoderOptions() + GifEncoder options = new GifEncoder() { IgnoreMetadata = false }; @@ -40,7 +40,7 @@ namespace ImageSharp.Tests { using (MemoryStream memStream = new MemoryStream()) { - input.Save(memStream, new GifFormat(), options); + input.Save(memStream, options); memStream.Position = 0; using (Image output = Image.Load(memStream)) @@ -56,7 +56,7 @@ namespace ImageSharp.Tests [Fact] public void Encode_IgnoreMetadataIsTrue_CommentsAreNotWritten() { - GifEncoderOptions options = new GifEncoderOptions() + GifEncoder options = new GifEncoder() { IgnoreMetadata = true }; @@ -88,7 +88,7 @@ namespace ImageSharp.Tests using (MemoryStream memStream = new MemoryStream()) { - input.Save(memStream, new GifFormat()); + input.Save(memStream, new GifEncoder()); memStream.Position = 0; using (Image output = Image.Load(memStream)) diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs index 2e68d49ddc..d625b86123 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 8c3e9ef84f..86faeee232 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 f3412e45e2..daf8da6a38 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs @@ -82,9 +82,8 @@ namespace ImageSharp.Tests { foreach (Image img in testImages) { - JpegEncoder encoder = new JpegEncoder(); - JpegEncoderOptions options = new JpegEncoderOptions { Quality = quality, Subsample = subsample }; - img.Save(ms, encoder, options); + JpegEncoder options = new JpegEncoder { Quality = quality, Subsample = subsample }; + img.Save(ms, options); ms.Seek(0, SeekOrigin.Begin); } }, diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs index 0e971c3d5f..5f4055a936 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs @@ -3,14 +3,15 @@ // Licensed under the Apache License, Version 2.0. // +using System.IO; +using System.IO.Compression; +using System.Text; +using ImageSharp.Formats; +using ImageSharp.PixelFormats; +using Xunit; + namespace ImageSharp.Tests { - using System.Text; - using Xunit; - - using ImageSharp.Formats; - using ImageSharp.PixelFormats; - public class PngDecoderTests { private const PixelTypes PixelTypes = Tests.PixelTypes.Rgba32 | Tests.PixelTypes.RgbaVector | Tests.PixelTypes.Argb32; @@ -18,7 +19,7 @@ namespace ImageSharp.Tests public static readonly string[] TestFiles = { TestImages.Png.Splash, TestImages.Png.Indexed, TestImages.Png.Interlaced, TestImages.Png.FilterVar, - TestImages.Png.Bad.ChunkLength1, TestImages.Png.Bad.ChunkLength2, TestImages.Png.Rgb48Bpp + TestImages.Png.Bad.ChunkLength1, TestImages.Png.Bad.ChunkLength2, TestImages.Png.Rgb48Bpp, TestImages.Png.Rgb48BppInterlaced }; [Theory] @@ -35,12 +36,12 @@ namespace ImageSharp.Tests [Fact] public void Decode_IgnoreMetadataIsFalse_TextChunckIsRead() { - PngDecoderOptions options = new PngDecoderOptions() + var options = new PngDecoder() { IgnoreMetadata = false }; - TestFile testFile = TestFile.Create(TestImages.Png.Blur); + var testFile = TestFile.Create(TestImages.Png.Blur); using (Image image = testFile.CreateImage(options)) { @@ -53,12 +54,12 @@ namespace ImageSharp.Tests [Fact] public void Decode_IgnoreMetadataIsTrue_TextChunksAreIgnored() { - PngDecoderOptions options = new PngDecoderOptions() + var options = new PngDecoder() { IgnoreMetadata = true }; - TestFile testFile = TestFile.Create(TestImages.Png.Blur); + var testFile = TestFile.Create(TestImages.Png.Blur); using (Image image = testFile.CreateImage(options)) { @@ -69,12 +70,12 @@ namespace ImageSharp.Tests [Fact] public void Decode_TextEncodingSetToUnicode_TextIsReadWithCorrectEncoding() { - PngDecoderOptions options = new PngDecoderOptions() + var options = new PngDecoder() { TextEncoding = Encoding.Unicode }; - TestFile testFile = TestFile.Create(TestImages.Png.Blur); + var testFile = TestFile.Create(TestImages.Png.Blur); using (Image image = testFile.CreateImage(options)) { @@ -82,5 +83,67 @@ namespace ImageSharp.Tests Assert.Equal("潓瑦慷敲", image.MetaData.Properties[0].Name); } } + + [Theory] + [InlineData(PngChunkTypes.Header)] + [InlineData(PngChunkTypes.Palette)] + // [InlineData(PngChunkTypes.Data)] //TODO: Figure out how to test this + [InlineData(PngChunkTypes.End)] + public void Decode_IncorrectCRCForCriticalChunk_ExceptionIsThrown(string chunkName) + { + using (var memStream = new MemoryStream()) + { + memStream.Skip(8); + + WriteChunk(memStream, chunkName); + + CompressStream(memStream); + + var decoder = new PngDecoder(); + + ImageFormatException exception = Assert.Throws(() => + { + decoder.Decode(null, memStream); + }); + + Assert.Equal($"CRC Error. PNG {chunkName} chunk is corrupt!", exception.Message); + } + } + + [Theory] + [InlineData(PngChunkTypes.Gamma)] + [InlineData(PngChunkTypes.PaletteAlpha)] + [InlineData(PngChunkTypes.Physical)] + //[InlineData(PngChunkTypes.Text)] //TODO: Figure out how to test this + public void Decode_IncorrectCRCForNonCriticalChunk_ExceptionIsThrown(string chunkName) + { + using (var memStream = new MemoryStream()) + { + memStream.Skip(8); + + WriteChunk(memStream, chunkName); + + CompressStream(memStream); + + var decoder = new PngDecoder(); + decoder.Decode(null, memStream); + } + } + + private static void WriteChunk(MemoryStream memStream, string chunkName) + { + memStream.Write(new byte[] { 0, 0, 0, 1 }, 0, 4); + memStream.Write(Encoding.GetEncoding("ASCII").GetBytes(chunkName), 0, 4); + memStream.Write(new byte[] { 0, 0, 0, 0, 0 }, 0, 5); + } + + private static void CompressStream(Stream stream) + { + stream.Position = 0; + using (var deflateStream = new DeflateStream(stream, CompressionLevel.NoCompression, true)) + { + } + stream.Position = 0; + } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs index 02edf7688f..24907cfdb7 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs @@ -31,13 +31,13 @@ namespace ImageSharp.Tests { using (Image image = provider.GetImage()) { - PngEncoderOptions options = new PngEncoderOptions() + PngEncoder options = new PngEncoder() { PngColorType = pngColorType }; provider.Utility.TestName += "_" + pngColorType; - provider.Utility.SaveTestOutputFile(image, "png", new PngEncoder(), options); + provider.Utility.SaveTestOutputFile(image, "png", options); } } diff --git a/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs index 05aa3c2d35..6cd3f4e7c2 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs @@ -40,26 +40,27 @@ namespace ImageSharp.Tests.Formats.Png } } - [Theory] - [WithTestPatternImages(100, 100, PixelTypes.Rgba32)] - public void CanSaveIndexedPng(TestImageProvider provider) - where TPixel : struct, IPixel - { - // does saving a file then repoening mean both files are identical??? - using (Image image = provider.GetImage()) - using (MemoryStream ms = new MemoryStream()) - { - // image.Save(provider.Utility.GetTestOutputFileName("bmp")); - image.MetaData.Quality = 256; - image.Save(ms, new PngEncoder()); - ms.Position = 0; - using (Image img2 = Image.Load(ms, new PngDecoder())) - { - // img2.Save(provider.Utility.GetTestOutputFileName("bmp", "_loaded"), new BmpEncoder()); + // JJS: Disabled for now as the decoder now correctly decodes the full pixel components if the + // paletted image has alpha of 0 + //[Theory] + //[WithTestPatternImages(100, 100, PixelTypes.Rgba32)] + //public void CanSaveIndexedPng(TestImageProvider provider) + // where TPixel : struct, IPixel + //{ + // // does saving a file then repoening mean both files are identical??? + // using (Image image = provider.GetImage()) + // using (MemoryStream ms = new MemoryStream()) + // { + // // image.Save(provider.Utility.GetTestOutputFileName("bmp")); + // image.Save(ms, new PngEncoder() { PaletteSize = 256 }); + // ms.Position = 0; + // using (Image img2 = Image.Load(ms, new PngDecoder())) + // { ImageComparer.VerifySimilarity(image, img2, 0.03f); - } - } - } + // ImageComparer.CheckSimilarity(image, img2, 0.03f); + // } + // } + //} // JJS: Commented out for now since the test does not take into lossy nature of indexing. //[Theory] diff --git a/tests/ImageSharp.Tests/Image/ImageDiscoverMimeType.cs b/tests/ImageSharp.Tests/Image/ImageDiscoverMimeType.cs new file mode 100644 index 0000000000..d34fa22e25 --- /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/ImageEqualTests.cs b/tests/ImageSharp.Tests/Image/ImageEqualTests.cs new file mode 100644 index 0000000000..3990060127 --- /dev/null +++ b/tests/ImageSharp.Tests/Image/ImageEqualTests.cs @@ -0,0 +1,62 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using Xunit; + + public class ImageEqualTests + { + [Fact] + public void TestsThatVimImagesAreEqual() + { + var image1Provider = TestImageProvider.File(TestImages.Png.VimImage1); + var image2Provider = TestImageProvider.File(TestImages.Png.VimImage2); + + using (Image img1 = image1Provider.GetImage()) + using (Image img2 = image2Provider.GetImage()) + { + bool imagesEqual = AreImagesEqual(img1, img2); + Assert.True(imagesEqual); + } + } + + [Fact] + public void TestsThatVersioningImagesAreEqual() + { + var image1Provider = TestImageProvider.File(TestImages.Png.VersioningImage1); + var image2Provider = TestImageProvider.File(TestImages.Png.VersioningImage2); + + using (Image img1 = image1Provider.GetImage()) + using (Image img2 = image2Provider.GetImage()) + { + bool imagesEqual = AreImagesEqual(img1, img2); + Assert.True(imagesEqual); + } + } + + private bool AreImagesEqual(Image img1, Image img2) + { + Assert.Equal(img1.Width, img2.Width); + Assert.Equal(img1.Height, img2.Height); + + for (int y = 0; y < img1.Height; y++) + { + for (int x = 0; x < img1.Width; x++) + { + Rgba32 pixel1 = img1[x, y]; + Rgba32 pixel2 = img2[x, y]; + + if (pixel1 != pixel2) + { + return false; + } + } + } + + return true; + } + } +} diff --git a/tests/ImageSharp.Tests/Image/ImageLoadTests.cs b/tests/ImageSharp.Tests/Image/ImageLoadTests.cs index 7a5b8ffc53..4573d0d9de 100644 --- a/tests/ImageSharp.Tests/Image/ImageLoadTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageLoadTests.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // @@ -10,7 +10,6 @@ namespace ImageSharp.Tests using ImageSharp.Formats; using ImageSharp.IO; - using ImageSharp.PixelFormats; using Moq; using Xunit; @@ -20,11 +19,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 +34,17 @@ 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); @@ -77,28 +76,22 @@ namespace ImageSharp.Tests [Fact] public void LoadFromStream() { - Image img = Image.Load(this.DataStream); + 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] public void LoadFromNoneSeekableStream() { NoneSeekableStream stream = new NoneSeekableStream(this.DataStream); - Image img = Image.Load(stream); + 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); } [Fact] @@ -108,48 +101,20 @@ 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); + TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, 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); - - } [Fact] public void LoadFromStreamWithConfig() { Stream stream = new MemoryStream(); - Image img = Image.Load(this.LocalConfiguration, stream); + Image img = Image.Load(this.LocalConfiguration, stream); Assert.NotNull(img); - 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] @@ -160,49 +125,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, 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() { Stream stream = new MemoryStream(); - Image img = Image.Load(stream, this.localDecoder.Object); + Image img = Image.Load(stream, this.localDecoder.Object); Assert.NotNull(img); - this.localDecoder.Verify(x => x.Decode(Configuration.Default, stream, null)); + this.localDecoder.Verify(x => x.Decode(Configuration.Default, stream)); } [Fact] @@ -213,41 +148,17 @@ 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] public void LoadFromBytes() { - Image img = Image.Load(this.DataStream.ToArray()); + 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); } [Fact] @@ -257,46 +168,19 @@ 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); } [Fact] public void LoadFromBytesWithConfig() { - Image img = Image.Load(this.LocalConfiguration, this.DataStream.ToArray()); + Image img = Image.Load(this.LocalConfiguration, this.DataStream.ToArray()); Assert.NotNull(img); - 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 +192,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); + 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,42 +215,18 @@ 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); } [Fact] public void LoadFromFile() { - Image img = Image.Load(this.DataStream); + 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] @@ -406,47 +236,18 @@ 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] public void LoadFromFileWithConfig() { - Image img = Image.Load(this.LocalConfiguration, this.FilePath); + 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)); } [Fact] @@ -456,45 +257,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); + 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 +277,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] @@ -542,7 +296,6 @@ namespace ImageSharp.Tests Assert.Equal(Rgba32.White, px[1, 0]); Assert.Equal(Rgba32.Black, px[1, 1]); - } } @@ -564,7 +317,17 @@ namespace ImageSharp.Tests Assert.Equal(Rgba32.White, px[1, 0]); Assert.Equal(Rgba32.Black, px[1, 1]); + } + } + [Fact] + public void LoadsImageWithoutThrowingCrcException() + { + var image1Provider = TestImageProvider.File(TestImages.Png.VersioningImage1); + + using (Image img = image1Provider.GetImage()) + { + Assert.Equal(166036, img.Pixels.Length); } } diff --git a/tests/ImageSharp.Tests/Image/ImageRotationTests.cs b/tests/ImageSharp.Tests/Image/ImageRotationTests.cs new file mode 100644 index 0000000000..56cec42192 --- /dev/null +++ b/tests/ImageSharp.Tests/Image/ImageRotationTests.cs @@ -0,0 +1,54 @@ +using SixLabors.Primitives; +using Xunit; + +namespace ImageSharp.Tests +{ + public class ImageRotationTests + { + [Fact] + public void RotateImageByMinus90Degrees() + { + (Size original, Size rotated) = Rotate(-90); + Assert.Equal(new Size(original.Height, original.Width), rotated); + } + + [Fact] + public void RotateImageBy90Degrees() + { + (Size original, Size rotated) = Rotate(90); + Assert.Equal(new Size(original.Height, original.Width), rotated); + } + + [Fact] + public void RotateImageBy180Degrees() + { + (Size original, Size rotated) = Rotate(180); + Assert.Equal(original, rotated); + } + + [Fact] + public void RotateImageBy270Degrees() + { + (Size original, Size rotated) = Rotate(270); + Assert.Equal(new Size(original.Height, original.Width), rotated); + } + + [Fact] + public void RotateImageBy360Degrees() + { + (Size original, Size rotated) = Rotate(360); + Assert.Equal(original, rotated); + } + + private static (Size original, Size rotated) Rotate(int angle) + { + TestFile file = TestFile.Create(TestImages.Bmp.Car); + using (Image image = Image.Load(file.FilePath)) + { + Size original = image.Bounds.Size; + image.Rotate(angle); + return (original, image.Bounds.Size); + } + } + } +} diff --git a/tests/ImageSharp.Tests/Image/ImageSaveTests.cs b/tests/ImageSharp.Tests/Image/ImageSaveTests.cs index 9e9852cb2c..ad8a5cc7d5 100644 --- a/tests/ImageSharp.Tests/Image/ImageSaveTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageSaveTests.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // @@ -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 a3ec4cec21..217bf37fe8 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // @@ -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 bc64d613ab..c60c8978ce 100644 --- a/tests/ImageSharp.Tests/MetaData/ImageMetaDataTests.cs +++ b/tests/ImageSharp.Tests/MetaData/ImageMetaDataTests.cs @@ -28,7 +28,6 @@ namespace ImageSharp.Tests metaData.HorizontalResolution = 4; metaData.VerticalResolution = 2; metaData.Properties.Add(imageProperty); - metaData.Quality = 24; metaData.RepeatCount = 1; metaData.DisposalMethod = DisposalMethod.RestoreToBackground; @@ -39,7 +38,6 @@ namespace ImageSharp.Tests Assert.Equal(4, clone.HorizontalResolution); Assert.Equal(2, clone.VerticalResolution); Assert.Equal(imageProperty, clone.Properties[0]); - Assert.Equal(24, clone.Quality); Assert.Equal(1, clone.RepeatCount); Assert.Equal(DisposalMethod.RestoreToBackground, clone.DisposalMethod); } diff --git a/tests/ImageSharp.Tests/Processing/Transforms/AutoOrientTests.cs b/tests/ImageSharp.Tests/Processing/Transforms/AutoOrientTests.cs index b0ff28b3c6..63becbe0ab 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/AutoOrientTests.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/AutoOrientTests.cs @@ -5,6 +5,7 @@ namespace ImageSharp.Tests.Processing.Transforms { + using System; using ImageSharp.PixelFormats; using ImageSharp.Processing; @@ -28,6 +29,16 @@ namespace ImageSharp.Tests.Processing.Transforms { RotateType.Rotate90, FlipType.None, 8 }, }; + public static readonly TheoryData InvalidOrientationValues + = new TheoryData + { + { ExifDataType.Byte, new byte[] { 1 } }, + { ExifDataType.SignedByte, new byte[] { 2 } }, + { ExifDataType.SignedShort, BitConverter.GetBytes((short) 3) }, + { ExifDataType.Long, BitConverter.GetBytes((uint) 4) }, + { ExifDataType.SignedLong, BitConverter.GetBytes((int) 5) } + }; + [Theory] [WithFileCollection(nameof(FlipFiles), nameof(OrientationValues), DefaultPixelType)] public void ImageShouldAutoRotate(TestImageProvider provider, RotateType rotateType, FlipType flipType, ushort orientation) @@ -44,5 +55,29 @@ namespace ImageSharp.Tests.Processing.Transforms .DebugSave(provider, string.Join("_", rotateType, flipType, orientation, "2_after")); } } + + [Theory] + [WithFileCollection(nameof(FlipFiles), nameof(InvalidOrientationValues), DefaultPixelType)] + public void ImageShouldAutoRotateInvalidValues(TestImageProvider provider, ExifDataType dataType, byte[] orientation) + where TPixel : struct, IPixel + { + var profile = new ExifProfile(); + profile.SetValue(ExifTag.JPEGTables, orientation); + + byte[] bytes = profile.ToByteArray(); + // Change the tag into ExifTag.Orientation + bytes[16] = 18; + bytes[17] = 1; + // Change the data type + bytes[18] = (byte)dataType; + // Change the number of components + bytes[20] = 1; + + using (Image image = provider.GetImage()) + { + image.MetaData.ExifProfile = new ExifProfile(bytes); + image.AutoOrient(); + } + } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestFile.cs b/tests/ImageSharp.Tests/TestFile.cs index 9255059ae4..7fa5a9b4e5 100644 --- a/tests/ImageSharp.Tests/TestFile.cs +++ b/tests/ImageSharp.Tests/TestFile.cs @@ -11,7 +11,7 @@ namespace ImageSharp.Tests using System.IO; using System.Linq; using System.Reflection; - + using ImageSharp.Formats; using ImageSharp.PixelFormats; /// @@ -25,10 +25,10 @@ namespace ImageSharp.Tests private static readonly ConcurrentDictionary Cache = new ConcurrentDictionary(); /// - /// The formats directory. + /// The formats directory, as lazy value /// private static readonly string FormatsDirectory = TestEnvironment.GetInputImagesDirectoryFullPath(); - + /// /// The image. /// @@ -71,6 +71,11 @@ namespace ImageSharp.Tests /// public string FileNameWithoutExtension => Path.GetFileNameWithoutExtension(this.file); + /// + /// Gets the "Formats" test file directory. + /// + private static string FormatsDirectory => formatsDirectory.Value; + /// /// Gets the full qualified path to the file. /// @@ -135,13 +140,12 @@ namespace ImageSharp.Tests /// /// Creates a new image. /// - /// The options for the decoder. /// /// The . /// - public Image CreateImage(IDecoderOptions options) + public Image CreateImage(IImageDecoder decoder) { - return Image.Load(this.Bytes, options); + return Image.Load(this.image.Configuration, this.Bytes, decoder); } } } diff --git a/tests/ImageSharp.Tests/TestFormat.cs b/tests/ImageSharp.Tests/TestFormat.cs index 318df5ead3..5a3cd102e7 100644 --- a/tests/ImageSharp.Tests/TestFormat.cs +++ b/tests/ImageSharp.Tests/TestFormat.cs @@ -19,13 +19,13 @@ namespace ImageSharp.Tests /// /// A test image file. /// - public class TestFormat : 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/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 3e4b508c33..606b9f9d54 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -27,6 +27,7 @@ namespace ImageSharp.Tests public const string Interlaced = "Png/interlaced.png"; public const string Rgb48Bpp = "Png/rgb-48bpp.png"; public const string CalliphoraPartial = "Png/CalliphoraPartial.png"; + public const string Rgb48BppInterlaced = "Png/rgb-48bpp-interlaced.png"; // Filtered test images from http://www.schaik.com/pngsuite/pngsuite_fil_png.html public const string Filter0 = "Png/filter0.png"; @@ -38,6 +39,12 @@ namespace ImageSharp.Tests // Filter changing per scanline public const string FilterVar = "Png/filterVar.png"; + public const string VimImage1 = "Png/vim16x16_1.png"; + public const string VimImage2 = "Png/vim16x16_2.png"; + + public const string VersioningImage1 = "Png/versioning-1_1.png"; + public const string VersioningImage2 = "Png/versioning-1_2.png"; + public static class Bad { // Odd chunk lengths @@ -50,7 +57,8 @@ namespace ImageSharp.Tests P1, Pd, Blur, Splash, Cross, Powerpoint, SplashInterlaced, Interlaced, Filter0, Filter1, Filter2, Filter3, Filter4, - FilterVar + FilterVar, VimImage1, VimImage2, VersioningImage1, + VersioningImage2 }; } diff --git a/tests/ImageSharp.Tests/TestImages/Formats/Png/rgb-48bpp-interlaced.png b/tests/ImageSharp.Tests/TestImages/Formats/Png/rgb-48bpp-interlaced.png new file mode 100644 index 0000000000..9202eeae4c Binary files /dev/null and b/tests/ImageSharp.Tests/TestImages/Formats/Png/rgb-48bpp-interlaced.png differ diff --git a/tests/ImageSharp.Tests/TestImages/Formats/Png/versioning-1_1.png b/tests/ImageSharp.Tests/TestImages/Formats/Png/versioning-1_1.png new file mode 100644 index 0000000000..c13f98fd16 Binary files /dev/null and b/tests/ImageSharp.Tests/TestImages/Formats/Png/versioning-1_1.png differ diff --git a/tests/ImageSharp.Tests/TestImages/Formats/Png/versioning-1_2.png b/tests/ImageSharp.Tests/TestImages/Formats/Png/versioning-1_2.png new file mode 100644 index 0000000000..076feeec1b Binary files /dev/null and b/tests/ImageSharp.Tests/TestImages/Formats/Png/versioning-1_2.png differ diff --git a/tests/ImageSharp.Tests/TestImages/Formats/Png/vim16x16_1.png b/tests/ImageSharp.Tests/TestImages/Formats/Png/vim16x16_1.png new file mode 100644 index 0000000000..fb45d22a05 Binary files /dev/null and b/tests/ImageSharp.Tests/TestImages/Formats/Png/vim16x16_1.png differ diff --git a/tests/ImageSharp.Tests/TestImages/Formats/Png/vim16x16_2.png b/tests/ImageSharp.Tests/TestImages/Formats/Png/vim16x16_2.png new file mode 100644 index 0000000000..a4a624d370 Binary files /dev/null and b/tests/ImageSharp.Tests/TestImages/Formats/Png/vim16x16_2.png differ diff --git a/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs b/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs index 10dd261661..16b1d1bf7f 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs @@ -138,14 +138,13 @@ namespace ImageSharp.Tests { string path = this.GetTestOutputFileName(extension: extension, settings: settings); string extension1 = Path.GetExtension(path); + encoder = encoder ?? GetImageFormatByExtension(extension); IImageFormat format = GetImageFormatByExtension(extension1); - encoder = encoder ?? format.Encoder; - using (FileStream stream = File.OpenWrite(path)) { - image.Save(stream, encoder, options); + image.Save(stream, encoder); } } @@ -163,16 +162,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); } }