diff --git a/src/ImageSharp/Configuration.cs b/src/ImageSharp/Configuration.cs index 5734d70fb..43613867e 100644 --- a/src/ImageSharp/Configuration.cs +++ b/src/ImageSharp/Configuration.cs @@ -6,6 +6,7 @@ namespace ImageSharp { using System; + using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; @@ -17,7 +18,7 @@ namespace ImageSharp /// /// Provides initialization code which allows extending the library. /// - public class Configuration + public class Configuration : IImageFormatHost { /// /// A lazily initialized configuration default instance. @@ -30,14 +31,24 @@ namespace ImageSharp private readonly object syncRoot = new object(); /// - /// The list of supported . + /// The list of supported keyed to mimestypes. /// - private readonly List encoders = new List(); + private readonly ConcurrentDictionary mimeTypeEncoders = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); /// - /// The list of supported . + /// The list of supported keyed to fiel extensions. /// - private readonly List decoders = new List(); + private readonly ConcurrentDictionary extensionsEncoders = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + + /// + /// The list of supported keyed to mimestypes. + /// + private readonly ConcurrentDictionary mimeTypeDecoders = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + + /// + /// The list of supported s. + /// + private readonly List mimeTypeDetectors = new List(); /// /// Initializes a new instance of the class. @@ -46,30 +57,55 @@ namespace ImageSharp { } + /// + /// Initializes a new instance of the class. + /// + /// A collection of providers to configure + public Configuration(params IImageFormatProvider[] providers) + { + if (providers != null) + { + foreach (IImageFormatProvider p in providers) + { + p.Configure(this); + } + } + } + /// /// Gets the default instance. /// public static Configuration Default { get; } = Lazy.Value; /// - /// Gets the collection of supported + /// Gets the global parallel options for processing tasks in parallel. /// - public IReadOnlyCollection ImageEncoders => new ReadOnlyCollection(this.encoders); + public ParallelOptions ParallelOptions { get; } = new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount }; /// - /// Gets the collection of supported + /// Gets the maximum header size of all formats. /// - public IReadOnlyCollection ImageDecoders => new ReadOnlyCollection(this.decoders); + internal int MaxHeaderSize { get; private set; } /// - /// Gets the global parallel options for processing tasks in parallel. + /// Gets the currently registerd s. /// - public ParallelOptions ParallelOptions { get; } = new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount }; + internal IEnumerable MimeTypeDetectors => this.mimeTypeDetectors; /// - /// Gets the maximum header size of all formats. + /// Gets the typeof of all the current image decoders /// - internal int MaxHeaderSize { get; private set; } + internal IEnumerable AllMimeImageDecoders => this.mimeTypeDecoders.Select(x => x.Value.GetType()).Distinct().ToList(); + + /// + /// Gets the typeof of all the current image decoders + /// + internal IEnumerable AllMimeImageEncoders => this.mimeTypeEncoders.Select(x => x.Value.GetType()).Distinct().ToList(); + + /// + /// Gets the typeof of all the current image decoders + /// + internal IEnumerable AllExtImageEncoders => this.mimeTypeEncoders.Select(x => x.Value.GetType()).Distinct().ToList(); #if !NETSTANDARD1_1 /// @@ -79,36 +115,102 @@ namespace ImageSharp #endif /// - /// Adds a new to the collection of supported image formats. + /// Registers a new format provider. /// - /// The new format to add. - public void AddImageFormat(IImageDecoder decoder) + /// The format providers to call configure on. + public void AddImageFormat(IImageFormatProvider formatProvider) + { + Guard.NotNull(formatProvider, nameof(formatProvider)); + formatProvider.Configure(this); + } + + /// + public void SetMimeTypeEncoder(string mimeType, IImageEncoder encoder) { + Guard.NotNullOrEmpty(mimeType, nameof(mimeType)); + Guard.NotNull(encoder, nameof(encoder)); + this.mimeTypeEncoders.AddOrUpdate(mimeType?.Trim(), encoder, (s, e) => encoder); + } + + /// + public void SetFileExtensionEncoder(string extension, IImageEncoder encoder) + { + Guard.NotNullOrEmpty(extension, nameof(extension)); + Guard.NotNull(encoder, nameof(encoder)); + this.extensionsEncoders.AddOrUpdate(extension?.Trim(), encoder, (s, e) => encoder); + } + + /// + public void SetMimeTypeDecoder(string mimeType, IImageDecoder decoder) + { + Guard.NotNullOrEmpty(mimeType, nameof(mimeType)); Guard.NotNull(decoder, nameof(decoder)); - Guard.NotNullOrEmpty(decoder.FileExtensions, nameof(decoder.FileExtensions)); - Guard.NotNullOrEmpty(decoder.MimeTypes, nameof(decoder.MimeTypes)); + this.mimeTypeDecoders.AddOrUpdate(mimeType, decoder, (s, e) => decoder); + } + + /// + /// Removes all the registerd detectors + /// + public void ClearMimeTypeDetector() + { + this.mimeTypeDetectors.Clear(); + } + + /// + public void AddMimeTypeDetector(IMimeTypeDetector detector) + { + Guard.NotNull(detector, nameof(detector)); + this.mimeTypeDetectors.Add(detector); + this.SetMaxHeaderSize(); + } - lock (this.syncRoot) + /// + /// For the specified mimetype find the decoder. + /// + /// the mimetype to discover + /// the IImageDecoder if found othersize null + public IImageDecoder FindMimeTypeDecoder(string mimeType) + { + Guard.NotNullOrEmpty(mimeType, nameof(mimeType)); + if (this.mimeTypeDecoders.TryGetValue(mimeType, out IImageDecoder dec)) { - this.decoders.Add(decoder); + return dec; + } + + return null; + } - this.SetMaxHeaderSize(); + /// + /// For the specified mimetype find the encoder. + /// + /// the mimetype to discover + /// the IImageEncoder if found othersize null + public IImageEncoder FindMimeTypeEncoder(string mimeType) + { + Guard.NotNullOrEmpty(mimeType, nameof(mimeType)); + if (this.mimeTypeEncoders.TryGetValue(mimeType, out IImageEncoder dec)) + { + return dec; } + + return null; } /// - /// Adds a new to the collection of supported image formats. + /// For the specified mimetype find the encoder. /// - /// The new format to add. - public void AddImageFormat(IImageEncoder encoder) + /// the extensions to discover + /// the IImageEncoder if found othersize null + public IImageEncoder FindFileExtensionsEncoder(string extensions) { - Guard.NotNull(encoder, nameof(encoder)); - Guard.NotNullOrEmpty(encoder.FileExtensions, nameof(encoder.FileExtensions)); - Guard.NotNullOrEmpty(encoder.MimeTypes, nameof(encoder.MimeTypes)); - lock (this.syncRoot) + extensions = extensions?.TrimStart('.'); + Guard.NotNullOrEmpty(extensions, nameof(extensions)); + if (this.extensionsEncoders.TryGetValue(extensions, out IImageEncoder dec)) { - this.encoders.Add(encoder); + return dec; } + + return null; } /// @@ -117,19 +219,11 @@ namespace ImageSharp /// The default configuration of internal static Configuration CreateDefaultInstance() { - Configuration config = new Configuration(); - - // lets try auto loading the known image formats - config.AddImageFormat(new Formats.PngEncoder()); - config.AddImageFormat(new Formats.JpegEncoder()); - config.AddImageFormat(new Formats.GifEncoder()); - config.AddImageFormat(new Formats.BmpEncoder()); - - config.AddImageFormat(new Formats.PngDecoder()); - config.AddImageFormat(new Formats.JpegDecoder()); - config.AddImageFormat(new Formats.GifDecoder()); - config.AddImageFormat(new Formats.BmpDecoder()); - return config; + return new Configuration( + new PngImageFormatProvider(), + new JpegImageFormatProvider(), + new GifImageFormatProvider(), + new BmpImageFormatProvider()); } /// @@ -137,7 +231,7 @@ namespace ImageSharp /// private void SetMaxHeaderSize() { - this.MaxHeaderSize = this.decoders.Max(x => x.HeaderSize); + this.MaxHeaderSize = this.mimeTypeDetectors.Max(x => x.HeaderSize); } } } diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoder.cs b/src/ImageSharp/Formats/Bmp/BmpDecoder.cs index 9ff331490..e1dc489f4 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoder.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoder.cs @@ -28,23 +28,6 @@ namespace ImageSharp.Formats /// public class BmpDecoder : IImageDecoder { - /// - public IEnumerable MimeTypes => BmpConstants.MimeTypes; - - /// - public IEnumerable FileExtensions => BmpConstants.FileExtensions; - - /// - public int HeaderSize => 2; - - /// - public bool IsSupportedFileFormat(Span header) - { - return header.Length >= this.HeaderSize && - header[0] == 0x42 && // B - header[1] == 0x4D; // M - } - /// public Image Decode(Configuration configuration, Stream stream) diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoder.cs b/src/ImageSharp/Formats/Bmp/BmpEncoder.cs index 25db0eda0..f47bedb81 100644 --- a/src/ImageSharp/Formats/Bmp/BmpEncoder.cs +++ b/src/ImageSharp/Formats/Bmp/BmpEncoder.cs @@ -22,12 +22,6 @@ namespace ImageSharp.Formats /// public BmpBitsPerPixel BitsPerPixel { get; set; } = BmpBitsPerPixel.Pixel24; - /// - public IEnumerable MimeTypes => BmpConstants.MimeTypes; - - /// - public IEnumerable FileExtensions => BmpConstants.FileExtensions; - /// public void Encode(Image image, Stream stream) where TPixel : struct, IPixel diff --git a/src/ImageSharp/Formats/Bmp/BmpImageFormatProvider.cs b/src/ImageSharp/Formats/Bmp/BmpImageFormatProvider.cs new file mode 100644 index 000000000..145e1fdb6 --- /dev/null +++ b/src/ImageSharp/Formats/Bmp/BmpImageFormatProvider.cs @@ -0,0 +1,42 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Text; + using ImageSharp.PixelFormats; + + /// + /// Detects gif file headers + /// + public class BmpImageFormatProvider : IImageFormatProvider + { + /// + public void Configure(IImageFormatHost host) + { + var encoder = new BmpEncoder(); + foreach (string mimeType in BmpConstants.MimeTypes) + { + host.SetMimeTypeEncoder(mimeType, encoder); + } + + foreach (string mimeType in BmpConstants.FileExtensions) + { + host.SetFileExtensionEncoder(mimeType, encoder); + } + + var decoder = new BmpDecoder(); + foreach (string mimeType in BmpConstants.MimeTypes) + { + host.SetMimeTypeDecoder(mimeType, decoder); + } + + host.AddMimeTypeDetector(new BmpMimeTypeDetector()); + } + } +} diff --git a/src/ImageSharp/Formats/Bmp/BmpMimeTypeDetector.cs b/src/ImageSharp/Formats/Bmp/BmpMimeTypeDetector.cs new file mode 100644 index 000000000..c13181d6a --- /dev/null +++ b/src/ImageSharp/Formats/Bmp/BmpMimeTypeDetector.cs @@ -0,0 +1,40 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Text; + using ImageSharp.PixelFormats; + + /// + /// Detects bmp file headers + /// + internal class BmpMimeTypeDetector : IMimeTypeDetector + { + /// + public int HeaderSize => 2; + + /// + public string DetectMimeType(Span header) + { + if (this.IsSupportedFileFormat(header)) + { + return "image/bmp"; + } + + return null; + } + + private bool IsSupportedFileFormat(Span header) + { + return header.Length >= this.HeaderSize && + header[0] == 0x42 && // B + header[1] == 0x4D; // M + } + } +} diff --git a/src/ImageSharp/Formats/Gif/GifDecoder.cs b/src/ImageSharp/Formats/Gif/GifDecoder.cs index 506b37dc8..c922767b8 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoder.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoder.cs @@ -16,12 +16,6 @@ namespace ImageSharp.Formats /// public class GifDecoder : IImageDecoder { - /// - public IEnumerable MimeTypes => GifConstants.MimeTypes; - - /// - public IEnumerable FileExtensions => GifConstants.FileExtensions; - /// /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded. /// @@ -32,21 +26,6 @@ namespace ImageSharp.Formats /// public Encoding TextEncoding { get; set; } = GifConstants.DefaultEncoding; - /// - public int HeaderSize => 6; - - /// - public bool IsSupportedFileFormat(Span header) - { - return header.Length >= this.HeaderSize && - header[0] == 0x47 && // G - header[1] == 0x49 && // I - header[2] == 0x46 && // F - header[3] == 0x38 && // 8 - (header[4] == 0x39 || header[4] == 0x37) && // 9 or 7 - header[5] == 0x61; // a - } - /// public Image Decode(Configuration configuration, Stream stream) where TPixel : struct, IPixel diff --git a/src/ImageSharp/Formats/Gif/GifEncoder.cs b/src/ImageSharp/Formats/Gif/GifEncoder.cs index f7dba9266..3ded88429 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoder.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoder.cs @@ -17,12 +17,6 @@ namespace ImageSharp.Formats /// public class GifEncoder : IImageEncoder { - /// - public IEnumerable MimeTypes => GifConstants.MimeTypes; - - /// - public IEnumerable FileExtensions => GifConstants.FileExtensions; - /// /// Gets or sets a value indicating whether the metadata should be ignored when the image is being encoded. /// diff --git a/src/ImageSharp/Formats/Gif/GifImageFormatProvider.cs b/src/ImageSharp/Formats/Gif/GifImageFormatProvider.cs new file mode 100644 index 000000000..7e521353f --- /dev/null +++ b/src/ImageSharp/Formats/Gif/GifImageFormatProvider.cs @@ -0,0 +1,42 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Text; + using ImageSharp.PixelFormats; + + /// + /// Detects gif file headers + /// + public class GifImageFormatProvider : IImageFormatProvider + { + /// + public void Configure(IImageFormatHost host) + { + var encoder = new GifEncoder(); + foreach (string mimeType in GifConstants.MimeTypes) + { + host.SetMimeTypeEncoder(mimeType, encoder); + } + + foreach (string mimeType in GifConstants.FileExtensions) + { + host.SetFileExtensionEncoder(mimeType, encoder); + } + + var decoder = new GifDecoder(); + foreach (string mimeType in GifConstants.MimeTypes) + { + host.SetMimeTypeDecoder(mimeType, decoder); + } + + host.AddMimeTypeDetector(new GifMimeTypeDetector()); + } + } +} diff --git a/src/ImageSharp/Formats/Gif/GifMimeTypeDetector.cs b/src/ImageSharp/Formats/Gif/GifMimeTypeDetector.cs new file mode 100644 index 000000000..f4ad8fa8e --- /dev/null +++ b/src/ImageSharp/Formats/Gif/GifMimeTypeDetector.cs @@ -0,0 +1,44 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Text; + using ImageSharp.PixelFormats; + + /// + /// Detects gif file headers + /// + public class GifMimeTypeDetector : IMimeTypeDetector + { + /// + public int HeaderSize => 6; + + /// + public string DetectMimeType(Span header) + { + if (this.IsSupportedFileFormat(header)) + { + return "image/gif"; + } + + return null; + } + + private bool IsSupportedFileFormat(Span header) + { + return header.Length >= this.HeaderSize && + header[0] == 0x47 && // G + header[1] == 0x49 && // I + header[2] == 0x46 && // F + header[3] == 0x38 && // 8 + (header[4] == 0x39 || header[4] == 0x37) && // 9 or 7 + header[5] == 0x61; // a + } + } +} diff --git a/src/ImageSharp/Formats/IImageDecoder.cs b/src/ImageSharp/Formats/IImageDecoder.cs index ff655d718..66eabb1b8 100644 --- a/src/ImageSharp/Formats/IImageDecoder.cs +++ b/src/ImageSharp/Formats/IImageDecoder.cs @@ -16,32 +16,6 @@ namespace ImageSharp.Formats /// public interface IImageDecoder { - /// - /// Gets the collection of mime types that this decoder supports decoding on. - /// - IEnumerable MimeTypes { get; } - - /// - /// Gets the collection of file extensionsthis decoder supports decoding. - /// - IEnumerable FileExtensions { get; } - - /// - /// Gets the size of the header for this image type. - /// - /// The size of the header. - int HeaderSize { get; } - - /// - /// Returns a value indicating whether the supports the specified - /// file header. - /// - /// The containing the file header. - /// - /// True if the decoder supports the file header; otherwise, false. - /// - bool IsSupportedFileFormat(Span header); - /// /// Decodes the image from the specified stream to the . /// diff --git a/src/ImageSharp/Formats/IImageEncoder.cs b/src/ImageSharp/Formats/IImageEncoder.cs index 465f8eec5..4ad41ebc2 100644 --- a/src/ImageSharp/Formats/IImageEncoder.cs +++ b/src/ImageSharp/Formats/IImageEncoder.cs @@ -16,16 +16,6 @@ namespace ImageSharp.Formats /// public interface IImageEncoder { - /// - /// Gets the collection of mime types that this decoder supports encoding for. - /// - IEnumerable MimeTypes { get; } - - /// - /// Gets the collection of file extensionsthis decoder supports encoding for. - /// - IEnumerable FileExtensions { get; } - /// /// Encodes the image to the specified stream from the . /// diff --git a/src/ImageSharp/Formats/IImageFormatProvider.cs b/src/ImageSharp/Formats/IImageFormatProvider.cs new file mode 100644 index 000000000..c7d354ec6 --- /dev/null +++ b/src/ImageSharp/Formats/IImageFormatProvider.cs @@ -0,0 +1,56 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using System; + using System.Collections.Generic; + using System.Text; + + /// + /// Represents an abstract class that can register image encoders, decoders and mime type detectors + /// + public interface IImageFormatProvider + { + /// + /// Called when loaded so the provider and register its encoders, decodes and mime type detectors into an IImageFormatHost. + /// + /// The host that will retain the encoders, decodes and mime type detectors. + void Configure(IImageFormatHost host); + } + + /// + /// Represents an abstract class that can have encoders decoders and mimetype detecotrs loaded into. + /// + public interface IImageFormatHost + { + /// + /// Sets a specific image encoder as the encoder for a specific mimetype + /// + /// the target mimetype + /// the encoder to use + void SetMimeTypeEncoder(string mimeType, IImageEncoder encoder); // could/should this be an Action??? + + /// + /// Sets a specific image encoder as the encoder for a specific mimetype + /// + /// the target mimetype + /// the encoder to use + void SetFileExtensionEncoder(string extension, IImageEncoder encoder); + + /// + /// Sets a specific image decoder as the decoder for a specific mimetype + /// + /// the target mimetype + /// the decoder to use + void SetMimeTypeDecoder(string mimeType, IImageDecoder decoder); + + /// + /// Adds a new detector for detecting in mime types + /// + /// The detector + void AddMimeTypeDetector(IMimeTypeDetector detector); + } +} diff --git a/src/ImageSharp/Formats/IMimeTypeDetector.cs b/src/ImageSharp/Formats/IMimeTypeDetector.cs new file mode 100644 index 000000000..f55be7151 --- /dev/null +++ b/src/ImageSharp/Formats/IMimeTypeDetector.cs @@ -0,0 +1,30 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using System; + using System.Collections.Generic; + using System.Text; + + /// + /// Used for detecting mime types from a file header + /// + public interface IMimeTypeDetector + { + /// + /// Gets the size of the header for this image type. + /// + /// The size of the header. + int HeaderSize { get; } + + /// + /// Detect mimetype + /// + /// The containing the file header. + /// returns the mime type of detected othersie returns null + string DetectMimeType(Span header); + } +} diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs index 09575a12e..b809908e9 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs @@ -21,22 +21,6 @@ namespace ImageSharp.Formats /// public bool IgnoreMetadata { get; set; } - /// - public IEnumerable MimeTypes => JpegConstants.MimeTypes; - - /// - public IEnumerable FileExtensions => JpegConstants.FileExtensions; - - /// - public int HeaderSize => 11; - - /// - public bool IsSupportedFileFormat(Span header) - { - return header.Length >= this.HeaderSize && - (IsJfif(header) || IsExif(header) || IsJpeg(header)); - } - /// public Image Decode(Configuration configuration, Stream stream) where TPixel : struct, IPixel @@ -49,54 +33,5 @@ namespace ImageSharp.Formats return decoder.Decode(stream); } } - - /// - /// Returns a value indicating whether the given bytes identify Jfif data. - /// - /// The bytes representing the file header. - /// The - private static bool IsJfif(Span header) - { - bool isJfif = - header[6] == 0x4A && // J - header[7] == 0x46 && // F - header[8] == 0x49 && // I - header[9] == 0x46 && // F - header[10] == 0x00; - - return isJfif; - } - - /// - /// Returns a value indicating whether the given bytes identify EXIF data. - /// - /// The bytes representing the file header. - /// The - private static bool IsExif(Span header) - { - bool isExif = - header[6] == 0x45 && // E - header[7] == 0x78 && // X - header[8] == 0x69 && // I - header[9] == 0x66 && // F - header[10] == 0x00; - - return isExif; - } - - /// - /// Returns a value indicating whether the given bytes identify Jpeg data. - /// This is a last chance resort for jpegs that contain ICC information. - /// - /// The bytes representing the file header. - /// The - private static bool IsJpeg(Span header) - { - bool isJpg = - header[0] == 0xFF && // 255 - header[1] == 0xD8; // 216 - - return isJpg; - } } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs index f76df0585..d0be9eaf8 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs @@ -34,12 +34,6 @@ namespace ImageSharp.Formats /// The subsample ratio of the jpg image. public JpegSubsample? Subsample { get; set; } - /// - public IEnumerable MimeTypes => JpegConstants.MimeTypes; - - /// - public IEnumerable FileExtensions => JpegConstants.FileExtensions; - /// /// Encodes the image to the specified stream from the . /// diff --git a/src/ImageSharp/Formats/Jpeg/JpegImageFormatProvider.cs b/src/ImageSharp/Formats/Jpeg/JpegImageFormatProvider.cs new file mode 100644 index 000000000..6cd49e20e --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/JpegImageFormatProvider.cs @@ -0,0 +1,42 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Text; + using ImageSharp.PixelFormats; + + /// + /// Detects png file headers + /// + public class JpegImageFormatProvider : IImageFormatProvider + { + /// + public void Configure(IImageFormatHost host) + { + var pngEncoder = new JpegEncoder(); + foreach (string mimeType in JpegConstants.MimeTypes) + { + host.SetMimeTypeEncoder(mimeType, pngEncoder); + } + + foreach (string mimeType in JpegConstants.FileExtensions) + { + host.SetFileExtensionEncoder(mimeType, pngEncoder); + } + + var pngDecoder = new JpegDecoder(); + foreach (string mimeType in JpegConstants.MimeTypes) + { + host.SetMimeTypeDecoder(mimeType, pngDecoder); + } + + host.AddMimeTypeDetector(new JpegMimeTypeDetector()); + } + } +} diff --git a/src/ImageSharp/Formats/Jpeg/JpegMimeTypeDetector.cs b/src/ImageSharp/Formats/Jpeg/JpegMimeTypeDetector.cs new file mode 100644 index 000000000..f84a91f70 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/JpegMimeTypeDetector.cs @@ -0,0 +1,88 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Text; + using ImageSharp.PixelFormats; + + /// + /// Detects Jpeg file headers + /// + public class JpegMimeTypeDetector : IMimeTypeDetector + { + /// + public int HeaderSize => 11; + + /// + public string DetectMimeType(Span header) + { + if (this.IsSupportedFileFormat(header)) + { + return "image/jpeg"; + } + + return null; + } + + private bool IsSupportedFileFormat(Span header) + { + return header.Length >= this.HeaderSize && + (this.IsJfif(header) || this.IsExif(header) || this.IsJpeg(header)); + } + + /// + /// Returns a value indicating whether the given bytes identify Jfif data. + /// + /// The bytes representing the file header. + /// The + private bool IsJfif(Span header) + { + bool isJfif = + header[6] == 0x4A && // J + header[7] == 0x46 && // F + header[8] == 0x49 && // I + header[9] == 0x46 && // F + header[10] == 0x00; + + return isJfif; + } + + /// + /// Returns a value indicating whether the given bytes identify EXIF data. + /// + /// The bytes representing the file header. + /// The + private bool IsExif(Span header) + { + bool isExif = + header[6] == 0x45 && // E + header[7] == 0x78 && // X + header[8] == 0x69 && // I + header[9] == 0x66 && // F + header[10] == 0x00; + + return isExif; + } + + /// + /// Returns a value indicating whether the given bytes identify Jpeg data. + /// This is a last chance resort for jpegs that contain ICC information. + /// + /// The bytes representing the file header. + /// The + private bool IsJpeg(Span header) + { + bool isJpg = + header[0] == 0xFF && // 255 + header[1] == 0xD8; // 216 + + return isJpg; + } + } +} diff --git a/src/ImageSharp/Formats/Png/PngDecoder.cs b/src/ImageSharp/Formats/Png/PngDecoder.cs index da00ff906..c9fab8e3f 100644 --- a/src/ImageSharp/Formats/Png/PngDecoder.cs +++ b/src/ImageSharp/Formats/Png/PngDecoder.cs @@ -38,34 +38,11 @@ namespace ImageSharp.Formats /// public bool IgnoreMetadata { get; set; } - /// - public IEnumerable MimeTypes => PngConstants.MimeTypes; - - /// - public IEnumerable FileExtensions => PngConstants.FileExtensions; - - /// - public int HeaderSize => 8; - /// /// Gets or sets the encoding that should be used when reading text chunks. /// public Encoding TextEncoding { get; set; } = PngConstants.DefaultEncoding; - /// - public bool IsSupportedFileFormat(Span header) - { - return header.Length >= this.HeaderSize && - header[0] == 0x89 && - header[1] == 0x50 && // P - header[2] == 0x4E && // N - header[3] == 0x47 && // G - header[4] == 0x0D && // CR - header[5] == 0x0A && // LF - header[6] == 0x1A && // EOF - header[7] == 0x0A; // LF - } - /// /// Decodes the image from the specified stream to the . /// diff --git a/src/ImageSharp/Formats/Png/PngEncoder.cs b/src/ImageSharp/Formats/Png/PngEncoder.cs index 023e465e9..d15161ded 100644 --- a/src/ImageSharp/Formats/Png/PngEncoder.cs +++ b/src/ImageSharp/Formats/Png/PngEncoder.cs @@ -16,12 +16,6 @@ namespace ImageSharp.Formats /// public class PngEncoder : IImageEncoder { - /// - public IEnumerable MimeTypes => PngConstants.MimeTypes; - - /// - public IEnumerable FileExtensions => PngConstants.FileExtensions; - /// /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded. /// diff --git a/src/ImageSharp/Formats/Png/PngImageFormatProvider.cs b/src/ImageSharp/Formats/Png/PngImageFormatProvider.cs new file mode 100644 index 000000000..5708cc812 --- /dev/null +++ b/src/ImageSharp/Formats/Png/PngImageFormatProvider.cs @@ -0,0 +1,42 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Text; + using ImageSharp.PixelFormats; + + /// + /// Detects png file headers + /// + public class PngImageFormatProvider : IImageFormatProvider + { + /// + public void Configure(IImageFormatHost host) + { + var pngEncoder = new PngEncoder(); + foreach (string mimeType in PngConstants.MimeTypes) + { + host.SetMimeTypeEncoder(mimeType, pngEncoder); + } + + foreach (string mimeType in PngConstants.FileExtensions) + { + host.SetFileExtensionEncoder(mimeType, pngEncoder); + } + + var pngDecoder = new PngDecoder(); + foreach (string mimeType in PngConstants.MimeTypes) + { + host.SetMimeTypeDecoder(mimeType, pngDecoder); + } + + host.AddMimeTypeDetector(new PngMimeTypeDetector()); + } + } +} diff --git a/src/ImageSharp/Formats/Png/PngMimeTypeDetector.cs b/src/ImageSharp/Formats/Png/PngMimeTypeDetector.cs new file mode 100644 index 000000000..6b5d43fbd --- /dev/null +++ b/src/ImageSharp/Formats/Png/PngMimeTypeDetector.cs @@ -0,0 +1,46 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Text; + using ImageSharp.PixelFormats; + + /// + /// Detects png file headers + /// + public class PngMimeTypeDetector : IMimeTypeDetector + { + /// + public int HeaderSize => 8; + + /// + public string DetectMimeType(Span header) + { + if (this.IsSupportedFileFormat(header)) + { + return "image/png"; + } + + return null; + } + + private bool IsSupportedFileFormat(Span header) + { + return header.Length >= this.HeaderSize && + header[0] == 0x89 && + header[1] == 0x50 && // P + header[2] == 0x4E && // N + header[3] == 0x47 && // G + header[4] == 0x0D && // CR + header[5] == 0x0A && // LF + header[6] == 0x1A && // EOF + header[7] == 0x0A; // LF + } + } +} diff --git a/src/ImageSharp/Image/Image.Decode.cs b/src/ImageSharp/Image/Image.Decode.cs index 2730da8f2..96d5df45f 100644 --- a/src/ImageSharp/Image/Image.Decode.cs +++ b/src/ImageSharp/Image/Image.Decode.cs @@ -22,8 +22,8 @@ namespace ImageSharp /// /// The image stream to read the header from. /// The configuration. - /// The image format or null if none found. - private static IImageDecoder DiscoverDecoder(Stream stream, Configuration config) + /// The mimetype or null if none found. + private static string InternalDiscoverMimeType(Stream stream, Configuration config) { // This is probably a candidate for making into a public API in the future! int maxHeaderSize = config.MaxHeaderSize; @@ -32,19 +32,31 @@ namespace ImageSharp return null; } - IImageDecoder format; byte[] header = ArrayPool.Shared.Rent(maxHeaderSize); try { long startPosition = stream.Position; stream.Read(header, 0, maxHeaderSize); stream.Position = startPosition; - format = config.ImageDecoders.LastOrDefault(x => x.IsSupportedFileFormat(header)); // we should use last in case user has registerd a new one with their own settings + return config.MimeTypeDetectors.Select(x => x.DetectMimeType(header)).LastOrDefault(x => x != null); } finally { ArrayPool.Shared.Return(header); } + } + + /// + /// By reading the header on the provided stream this calculates the images format. + /// + /// The image stream to read the header from. + /// The configuration. + /// The mimeType. + /// The image format or null if none found. + private static IImageDecoder DiscoverDecoder(Stream stream, Configuration config, out string mimeType) + { + + format = config.FindMimeTypeDecoder(mimeType); return format; } @@ -59,18 +71,18 @@ namespace ImageSharp /// /// A new . /// - private static (Image img, IImageDecoder decoder) Decode(Stream stream, Configuration config) + private static (Image img, string mimeType) Decode(Stream stream, Configuration config) #pragma warning restore SA1008 // Opening parenthesis must be spaced correctly where TPixel : struct, IPixel { - IImageDecoder decoder = DiscoverDecoder(stream, config); + IImageDecoder decoder = DiscoverDecoder(stream, config, out string mimeType); if (decoder == null) { return (null, null); } Image img = decoder.Decode(config, stream); - return (img, decoder); + return (img, mimeType); } } } \ No newline at end of file diff --git a/src/ImageSharp/Image/Image.FromBytes.cs b/src/ImageSharp/Image/Image.FromBytes.cs index 8cc0e8f37..948fadf0c 100644 --- a/src/ImageSharp/Image/Image.FromBytes.cs +++ b/src/ImageSharp/Image/Image.FromBytes.cs @@ -15,6 +15,30 @@ namespace ImageSharp /// public static partial class Image { + /// + /// By reading the header on the provided byte array this calculates the images mimetype. + /// + /// The byte array containing image data to read the header from. + /// The mimetype or null if none found. + public static string DiscoverMimeType(byte[] data) + { + return DiscoverMimeType(null, data); + } + + /// + /// By reading the header on the provided byte array this calculates the images mimetype. + /// + /// The configuration. + /// The byte array containing image data to read the header from. + /// The mimetype or null if none found. + public static string DiscoverMimeType(Configuration config, byte[] data) + { + using (Stream stream = new MemoryStream(data)) + { + return DiscoverMimeType(config, stream); + } + } + /// /// Create a new instance of the class from the given byte array. /// diff --git a/src/ImageSharp/Image/Image.FromFile.cs b/src/ImageSharp/Image/Image.FromFile.cs index 991294921..96d509752 100644 --- a/src/ImageSharp/Image/Image.FromFile.cs +++ b/src/ImageSharp/Image/Image.FromFile.cs @@ -16,6 +16,31 @@ namespace ImageSharp /// public static partial class Image { + /// + /// By reading the header on the provided file this calculates the images mimetype. + /// + /// The image file to open and to read the header from. + /// The mimetype or null if none found. + public static string DiscoverMimeType(string filePath) + { + return DiscoverMimeType(null, filePath); + } + + /// + /// By reading the header on the provided file this calculates the images mimetype. + /// + /// The configuration. + /// The image file to open and to read the header from. + /// The mimetype or null if none found. + public static string DiscoverMimeType(Configuration config, string filePath) + { + config = config ?? Configuration.Default; + using (Stream file = config.FileSystem.OpenRead(filePath)) + { + return DiscoverMimeType(config, file); + } + } + /// /// Create a new instance of the class from the given file. /// diff --git a/src/ImageSharp/Image/Image.FromStream.cs b/src/ImageSharp/Image/Image.FromStream.cs index 79c66af90..1aa93525c 100644 --- a/src/ImageSharp/Image/Image.FromStream.cs +++ b/src/ImageSharp/Image/Image.FromStream.cs @@ -18,6 +18,27 @@ namespace ImageSharp /// public static partial class Image { + /// + /// By reading the header on the provided stream this calculates the images mimetype. + /// + /// The image stream to read the header from. + /// The mimetype or null if none found. + public static string DiscoverMimeType(Stream stream) + { + return DiscoverMimeType(null, stream); + } + + /// + /// By reading the header on the provided stream this calculates the images mimetype. + /// + /// The configuration. + /// The image stream to read the header from. + /// The mimetype or null if none found. + public static string DiscoverMimeType(Configuration config, Stream stream) + { + return WithSeekableStream(stream, s => InternalDiscoverMimeType(s, config ?? Configuration.Default)); + } + /// /// Create a new instance of the class from the given stream. /// @@ -169,21 +190,21 @@ namespace ImageSharp { config = config ?? Configuration.Default; mimeType = null; - (Image img, IImageDecoder decoder) data = WithSeekableStream(stream, s => Decode(s, config)); + (Image img, string mimeType) data = WithSeekableStream(stream, s => Decode(s, config)); - mimeType = data.decoder?.MimeTypes.FirstOrDefault(); + mimeType = data.mimeType; if (data.img != null) { return data.img; } - StringBuilder stringBuilder = new StringBuilder(); + var stringBuilder = new StringBuilder(); stringBuilder.AppendLine("Image cannot be loaded. Available decoders:"); - foreach (IImageDecoder format in config.ImageDecoders) + foreach (Type format in config.AllMimeImageDecoders) { - stringBuilder.AppendLine("-" + format); + stringBuilder.AppendLine(" - " + format.Name); } throw new NotSupportedException(stringBuilder.ToString()); @@ -202,7 +223,7 @@ namespace ImageSharp } // We want to be able to load images from things like HttpContext.Request.Body - using (MemoryStream ms = new MemoryStream()) + using (var ms = new MemoryStream()) { stream.CopyTo(ms); ms.Position = 0; diff --git a/src/ImageSharp/Image/Image{TPixel}.cs b/src/ImageSharp/Image/Image{TPixel}.cs index 27f3801c4..af9cc3914 100644 --- a/src/ImageSharp/Image/Image{TPixel}.cs +++ b/src/ImageSharp/Image/Image{TPixel}.cs @@ -158,16 +158,16 @@ namespace ImageSharp public Image Save(Stream stream, string mimeType) { Guard.NotNullOrEmpty(mimeType, nameof(mimeType)); - IImageEncoder encoder = this.Configuration.ImageEncoders?.LastOrDefault(x => x?.MimeTypes?.Contains(mimeType, StringComparer.OrdinalIgnoreCase) == true); + IImageEncoder encoder = this.Configuration.FindMimeTypeEncoder(mimeType); if (encoder == null) { - StringBuilder stringBuilder = new StringBuilder(); + var stringBuilder = new StringBuilder(); stringBuilder.AppendLine("Can't find encoder for provided mime type. Available encoded:"); - foreach (IImageEncoder format in this.Configuration.ImageEncoders) + foreach (Type format in this.Configuration.AllMimeImageEncoders) { - stringBuilder.AppendLine("-" + format); + stringBuilder.AppendLine(" - " + format); } throw new NotSupportedException(stringBuilder.ToString()); @@ -207,15 +207,15 @@ namespace ImageSharp Guard.NotNullOrEmpty(filePath, nameof(filePath)); string ext = Path.GetExtension(filePath).Trim('.'); - IImageEncoder encoder = this.Configuration.ImageEncoders?.LastOrDefault(x => x?.FileExtensions?.Contains(ext, StringComparer.OrdinalIgnoreCase) == true); + IImageEncoder encoder = this.Configuration.FindFileExtensionsEncoder(ext); if (encoder == null) { - StringBuilder stringBuilder = new StringBuilder(); + var stringBuilder = new StringBuilder(); stringBuilder.AppendLine($"Can't find encoder for file extention '{ext}'. Available encoded:"); - foreach (IImageEncoder format in this.Configuration.ImageEncoders) + foreach (Type format in this.Configuration.AllExtImageEncoders) { - stringBuilder.AppendLine("-" + format); + stringBuilder.AppendLine(" - " + format); } throw new NotSupportedException(stringBuilder.ToString()); @@ -255,7 +255,7 @@ namespace ImageSharp /// The public string ToBase64String(string mimeType) { - using (MemoryStream stream = new MemoryStream()) + using (var stream = new MemoryStream()) { this.Save(stream, mimeType); stream.Flush(); @@ -274,7 +274,7 @@ namespace ImageSharp { scaleFunc = PackedPixelConverterHelper.ComputeScaleFunction(scaleFunc); - Image target = new Image(this.Configuration, this.Width, this.Height); + var target = new Image(this.Configuration, this.Width, this.Height); target.CopyProperties(this); using (PixelAccessor pixels = this.Lock()) @@ -288,7 +288,7 @@ namespace ImageSharp { for (int x = 0; x < target.Width; x++) { - TPixel2 color = default(TPixel2); + var color = default(TPixel2); color.PackFromVector4(scaleFunc(pixels[x, y].ToVector4())); targetPixels[x, y] = color; } diff --git a/tests/ImageSharp.Tests/ConfigurationTests.cs b/tests/ImageSharp.Tests/ConfigurationTests.cs index e2d50649a..e8927c75c 100644 --- a/tests/ImageSharp.Tests/ConfigurationTests.cs +++ b/tests/ImageSharp.Tests/ConfigurationTests.cs @@ -13,7 +13,7 @@ namespace ImageSharp.Tests using ImageSharp.Formats; using ImageSharp.IO; using ImageSharp.PixelFormats; - + using Moq; using Xunit; /// @@ -21,23 +21,27 @@ namespace ImageSharp.Tests /// public class ConfigurationTests { + public Configuration ConfigurationEmpty { get; private set; } + public Configuration DefaultConfiguration { get; private set; } + + public ConfigurationTests() + { + this.DefaultConfiguration = Configuration.CreateDefaultInstance(); + this.ConfigurationEmpty = Configuration.CreateDefaultInstance(); + } + [Fact] public void DefaultsToLocalFileSystem() { - var configuration = Configuration.CreateDefaultInstance(); - - ImageSharp.IO.IFileSystem fs = configuration.FileSystem; - - Assert.IsType(fs); + Assert.IsType(DefaultConfiguration.FileSystem); + Assert.IsType(ConfigurationEmpty.FileSystem); } [Fact] public void IfAutoloadWellknwonFormatesIsTrueAllFormateAreLoaded() { - var configuration = Configuration.CreateDefaultInstance(); - - Assert.Equal(4, configuration.ImageDecoders.Count); - Assert.Equal(4, configuration.ImageDecoders.Count); + Assert.Equal(4, DefaultConfiguration.AllMimeImageDecoders.Count()); + Assert.Equal(4, DefaultConfiguration.AllMimeImageDecoders.Count()); } /// @@ -68,58 +72,129 @@ namespace ImageSharp.Tests Assert.True(Configuration.Default.ParallelOptions.MaxDegreeOfParallelism == Environment.ProcessorCount); } - /// - /// Test that the default configuration parallel options is not null. - /// [Fact] - public void TestDefultConfigurationImageFormatsIsNotNull() + public void AddMimeTypeDetectorNullthrows() { - Assert.True(Configuration.Default.ImageDecoders != null); - Assert.True(Configuration.Default.ImageEncoders != null); + Assert.Throws(() => + { + DefaultConfiguration.AddMimeTypeDetector(null); + }); } - /// - /// Tests the method throws an exception - /// when the format is null. - /// [Fact] - public void TestAddImageFormatThrowsWithNullFormat() + public void RegisterNullMimeTypeEncoder() { Assert.Throws(() => { - Configuration.Default.AddImageFormat((IImageEncoder)null); + DefaultConfiguration.SetMimeTypeEncoder(null, new Mock().Object); }); Assert.Throws(() => { - Configuration.Default.AddImageFormat((IImageDecoder)null); + DefaultConfiguration.SetMimeTypeEncoder("sdsdsd", null); + }); + Assert.Throws(() => + { + DefaultConfiguration.SetMimeTypeEncoder(null, null); + }); + } + + [Fact] + public void RegisterNullFileExtEncoder() + { + Assert.Throws(() => + { + DefaultConfiguration.SetFileExtensionEncoder(null, new Mock().Object); + }); + Assert.Throws(() => + { + DefaultConfiguration.SetFileExtensionEncoder("sdsdsd", null); + }); + Assert.Throws(() => + { + DefaultConfiguration.SetFileExtensionEncoder(null, null); }); } - /// - /// Test that the default image constructors use default configuration. - /// [Fact] - public void TestImageUsesDefaultConfiguration() + public void RegisterNullMimeTypeDecoder() { - Configuration.Default.AddImageFormat(new PngDecoder()); + Assert.Throws(() => + { + DefaultConfiguration.SetMimeTypeDecoder(null, new Mock().Object); + }); + Assert.Throws(() => + { + DefaultConfiguration.SetMimeTypeDecoder("sdsdsd", null); + }); + Assert.Throws(() => + { + DefaultConfiguration.SetMimeTypeDecoder(null, null); + }); + } - var image = new Image(1, 1); - Assert.Equal(image.Configuration.ParallelOptions, Configuration.Default.ParallelOptions); - Assert.Equal(image.Configuration.ImageDecoders, Configuration.Default.ImageDecoders); + [Fact] + public void RegisterMimeTypeEncoderReplacesLast() + { + var encoder1 = new Mock().Object; + ConfigurationEmpty.SetMimeTypeEncoder("test", encoder1); + var found = ConfigurationEmpty.FindMimeTypeEncoder("TEST"); + Assert.Equal(encoder1, found); + + var encoder2 = new Mock().Object; + ConfigurationEmpty.SetMimeTypeEncoder("TEST", encoder2); + var found2 = ConfigurationEmpty.FindMimeTypeEncoder("test"); + Assert.Equal(encoder2, found2); + Assert.NotEqual(found, found2); + } + + [Fact] + public void RegisterFileExtEnecoderReplacesLast() + { + var encoder1 = new Mock().Object; + ConfigurationEmpty.SetFileExtensionEncoder("TEST", encoder1); + var found = ConfigurationEmpty.FindFileExtensionsEncoder("test"); + Assert.Equal(encoder1, found); + + var encoder2 = new Mock().Object; + ConfigurationEmpty.SetFileExtensionEncoder("test", encoder2); + var found2 = ConfigurationEmpty.FindFileExtensionsEncoder("TEST"); + Assert.Equal(encoder2, found2); + Assert.NotEqual(found, found2); + } + + [Fact] + public void RegisterMimeTypeDecoderReplacesLast() + { + var decoder1 = new Mock().Object; + ConfigurationEmpty.SetMimeTypeDecoder("test", decoder1); + var found = ConfigurationEmpty.FindMimeTypeDecoder("TEST"); + Assert.Equal(decoder1, found); + + var decoder2 = new Mock().Object; + ConfigurationEmpty.SetMimeTypeDecoder("TEST", decoder2); + var found2 = ConfigurationEmpty.FindMimeTypeDecoder("test"); + Assert.Equal(decoder2, found2); + Assert.NotEqual(found, found2); + } + + + [Fact] + public void ConstructorCallConfigureOnFormatProvider() + { + var provider = new Mock(); + var config = new Configuration(provider.Object); + + provider.Verify(x => x.Configure(config)); } - /// - /// Test that the default image constructor copies the configuration. - /// [Fact] - public void TestImageCopiesConfiguration() + public void AddFormatCallsConfig() { - Configuration.Default.AddImageFormat(new PngDecoder()); + var provider = new Mock(); + var config = new Configuration(); + config.AddImageFormat(provider.Object); - var image = new Image(1, 1); - var image2 = new Image(image); - Assert.Equal(image2.Configuration.ParallelOptions, image.Configuration.ParallelOptions); - Assert.True(image2.Configuration.ImageDecoders.SequenceEqual(image.Configuration.ImageDecoders)); + provider.Verify(x => x.Configure(config)); } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Image/ImageLoadTests.cs b/tests/ImageSharp.Tests/Image/ImageLoadTests.cs index 65c2cc52c..efd7b6935 100644 --- a/tests/ImageSharp.Tests/Image/ImageLoadTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageLoadTests.cs @@ -23,6 +23,7 @@ namespace ImageSharp.Tests private Image returnImage; private Mock localDecoder; private readonly string FilePath; + private readonly Mock localMimeTypeDetector; public Configuration LocalConfiguration { get; private set; } public byte[] Marker { get; private set; } @@ -34,10 +35,9 @@ namespace ImageSharp.Tests this.returnImage = new Image(1, 1); this.localDecoder = new Mock(); - this.localDecoder.Setup(x => x.MimeTypes).Returns(new[] { "img/test" }); - this.localDecoder.Setup(x => x.FileExtensions).Returns(new[] { "png", "jpg" }); - this.localDecoder.Setup(x => x.HeaderSize).Returns(1); - this.localDecoder.Setup(x => x.IsSupportedFileFormat(It.IsAny>())).Returns(true); + this.localMimeTypeDetector = new Mock(); + this.localMimeTypeDetector.Setup(x => x.HeaderSize).Returns(1); + this.localMimeTypeDetector.Setup(x => x.DetectMimeType(It.IsAny>())).Returns("test"); this.localDecoder.Setup(x => x.Decode(It.IsAny(), It.IsAny())) @@ -56,8 +56,8 @@ namespace ImageSharp.Tests { FileSystem = this.fileSystem.Object }; - - this.LocalConfiguration.AddImageFormat(this.localDecoder.Object); + this.LocalConfiguration.AddMimeTypeDetector(this.localMimeTypeDetector.Object); + this.LocalConfiguration.SetMimeTypeDecoder("test", this.localDecoder.Object); TestFormat.RegisterGloablTestFormat(); this.Marker = Guid.NewGuid().ToByteArray(); diff --git a/tests/ImageSharp.Tests/Image/ImageSaveTests.cs b/tests/ImageSharp.Tests/Image/ImageSaveTests.cs index 46177ba5f..e2e0b1364 100644 --- a/tests/ImageSharp.Tests/Image/ImageSaveTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageSaveTests.cs @@ -24,24 +24,26 @@ namespace ImageSharp.Tests private readonly Mock fileSystem; private readonly Mock encoder; private readonly Mock encoderNotInFormat; + private Mock localMimeTypeDetector; public ImageSaveTests() { - this.encoder = new Mock(); - this.encoder.Setup(x => x.MimeTypes).Returns(new[] { "img/test" }); - this.encoder.Setup(x => x.FileExtensions).Returns(new string[] { "png", "jpg" }); + this.localMimeTypeDetector = new Mock(); + this.localMimeTypeDetector.Setup(x => x.HeaderSize).Returns(1); + this.localMimeTypeDetector.Setup(x => x.DetectMimeType(It.IsAny>())).Returns("img/test"); + this.encoder = new Mock(); this.encoderNotInFormat = new Mock(); - this.encoderNotInFormat.Setup(x => x.MimeTypes).Returns(new[] { "img/test" }); - this.encoderNotInFormat.Setup(x => x.FileExtensions).Returns(new string[] { "png", "jpg" }); this.fileSystem = new Mock(); var config = new Configuration() { FileSystem = this.fileSystem.Object }; - config.AddImageFormat(this.encoder.Object); + config.AddMimeTypeDetector(this.localMimeTypeDetector.Object); + config.SetMimeTypeEncoder("img/test", this.encoder.Object); + config.SetFileExtensionEncoder("png", this.encoder.Object); this.Image = new Image(config, 1, 1); } diff --git a/tests/ImageSharp.Tests/TestFormat.cs b/tests/ImageSharp.Tests/TestFormat.cs index 1d06f7328..701b02c09 100644 --- a/tests/ImageSharp.Tests/TestFormat.cs +++ b/tests/ImageSharp.Tests/TestFormat.cs @@ -19,14 +19,13 @@ namespace ImageSharp.Tests /// /// A test image file. /// - public class TestFormat + public class TestFormat : IImageFormatProvider { public static TestFormat GlobalTestFormat { get; } = new TestFormat(); public static void RegisterGloablTestFormat() { - Configuration.Default.AddImageFormat(GlobalTestFormat.Encoder); - Configuration.Default.AddImageFormat(GlobalTestFormat.Decoder); + Configuration.Default.AddImageFormat(GlobalTestFormat); } public TestFormat() @@ -66,7 +65,8 @@ namespace ImageSharp.Tests Assert.True(discovered.Any(), "No calls to decode on this formate with the proveded options happend"); - foreach (DecodeOperation d in discovered) { + foreach (DecodeOperation d in discovered) + { this.DecodeCalls.Remove(d); } } @@ -80,7 +80,7 @@ namespace ImageSharp.Tests { this._sampleImages.Add(typeof(TPixel), new Image(1, 1)); } - + return (Image)this._sampleImages[typeof(TPixel)]; } } @@ -108,12 +108,25 @@ namespace ImageSharp.Tests } return true; } + + public void Configure(IImageFormatHost host) + { + host.AddMimeTypeDetector(new TestHeader(this)); + foreach (var ext in this.SupportedExtensions) + { + host.SetFileExtensionEncoder(ext, new TestEncoder(this)); + } + + host.SetMimeTypeEncoder(this.MimeType, new TestEncoder(this)); + host.SetMimeTypeDecoder(this.MimeType, new TestDecoder(this)); + } + public struct DecodeOperation { public byte[] marker; internal Configuration config; - public bool IsMatch(byte[] testMarker, Configuration config) + public bool IsMatch(byte[] testMarker, Configuration config) { if (this.config != config) @@ -137,6 +150,26 @@ namespace ImageSharp.Tests } } + public class TestHeader : IMimeTypeDetector + { + + private TestFormat testFormat; + + public int HeaderSize => testFormat.HeaderSize; + + public string DetectMimeType(Span header) + { + if (testFormat.IsSupportedFileFormat(header)) + return testFormat.MimeType; + + return null; + } + + public TestHeader(TestFormat testFormat) + { + this.testFormat = testFormat; + } + } public class TestDecoder : ImageSharp.Formats.IImageDecoder { private TestFormat testFormat; diff --git a/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs b/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs index ee5246660..21b167ca2 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs @@ -124,7 +124,7 @@ namespace ImageSharp.Tests private static IImageEncoder GetImageFormatByExtension(string extension) { extension = extension?.TrimStart('.'); - return Configuration.Default.ImageEncoders.Last(f => f.FileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase)); + return Configuration.Default.FindFileExtensionsEncoder(extension); } private string GetTestOutputDir()