Browse Source

provide IImageFormatProviders and split mimetype detection from decoders.

pull/254/head
Scott Williams 9 years ago
parent
commit
3499d7b6a2
  1. 180
      src/ImageSharp/Configuration.cs
  2. 17
      src/ImageSharp/Formats/Bmp/BmpDecoder.cs
  3. 6
      src/ImageSharp/Formats/Bmp/BmpEncoder.cs
  4. 42
      src/ImageSharp/Formats/Bmp/BmpImageFormatProvider.cs
  5. 40
      src/ImageSharp/Formats/Bmp/BmpMimeTypeDetector.cs
  6. 21
      src/ImageSharp/Formats/Gif/GifDecoder.cs
  7. 6
      src/ImageSharp/Formats/Gif/GifEncoder.cs
  8. 42
      src/ImageSharp/Formats/Gif/GifImageFormatProvider.cs
  9. 44
      src/ImageSharp/Formats/Gif/GifMimeTypeDetector.cs
  10. 26
      src/ImageSharp/Formats/IImageDecoder.cs
  11. 10
      src/ImageSharp/Formats/IImageEncoder.cs
  12. 56
      src/ImageSharp/Formats/IImageFormatProvider.cs
  13. 30
      src/ImageSharp/Formats/IMimeTypeDetector.cs
  14. 65
      src/ImageSharp/Formats/Jpeg/JpegDecoder.cs
  15. 6
      src/ImageSharp/Formats/Jpeg/JpegEncoder.cs
  16. 42
      src/ImageSharp/Formats/Jpeg/JpegImageFormatProvider.cs
  17. 88
      src/ImageSharp/Formats/Jpeg/JpegMimeTypeDetector.cs
  18. 23
      src/ImageSharp/Formats/Png/PngDecoder.cs
  19. 6
      src/ImageSharp/Formats/Png/PngEncoder.cs
  20. 42
      src/ImageSharp/Formats/Png/PngImageFormatProvider.cs
  21. 46
      src/ImageSharp/Formats/Png/PngMimeTypeDetector.cs
  22. 26
      src/ImageSharp/Image/Image.Decode.cs
  23. 24
      src/ImageSharp/Image/Image.FromBytes.cs
  24. 25
      src/ImageSharp/Image/Image.FromFile.cs
  25. 33
      src/ImageSharp/Image/Image.FromStream.cs
  26. 22
      src/ImageSharp/Image/Image{TPixel}.cs
  27. 155
      tests/ImageSharp.Tests/ConfigurationTests.cs
  28. 12
      tests/ImageSharp.Tests/Image/ImageLoadTests.cs
  29. 14
      tests/ImageSharp.Tests/Image/ImageSaveTests.cs
  30. 45
      tests/ImageSharp.Tests/TestFormat.cs
  31. 2
      tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs

180
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
/// <summary>
/// Provides initialization code which allows extending the library.
/// </summary>
public class Configuration
public class Configuration : IImageFormatHost
{
/// <summary>
/// A lazily initialized configuration default instance.
@ -30,14 +31,24 @@ namespace ImageSharp
private readonly object syncRoot = new object();
/// <summary>
/// The list of supported <see cref="IImageEncoder"/>.
/// The list of supported <see cref="IImageEncoder"/> keyed to mimestypes.
/// </summary>
private readonly List<IImageEncoder> encoders = new List<IImageEncoder>();
private readonly ConcurrentDictionary<string, IImageEncoder> mimeTypeEncoders = new ConcurrentDictionary<string, IImageEncoder>(StringComparer.OrdinalIgnoreCase);
/// <summary>
/// The list of supported <see cref="IImageDecoder"/>.
/// The list of supported <see cref="IImageEncoder"/> keyed to fiel extensions.
/// </summary>
private readonly List<IImageDecoder> decoders = new List<IImageDecoder>();
private readonly ConcurrentDictionary<string, IImageEncoder> extensionsEncoders = new ConcurrentDictionary<string, IImageEncoder>(StringComparer.OrdinalIgnoreCase);
/// <summary>
/// The list of supported <see cref="IImageEncoder"/> keyed to mimestypes.
/// </summary>
private readonly ConcurrentDictionary<string, IImageDecoder> mimeTypeDecoders = new ConcurrentDictionary<string, IImageDecoder>(StringComparer.OrdinalIgnoreCase);
/// <summary>
/// The list of supported <see cref="IMimeTypeDetector"/>s.
/// </summary>
private readonly List<IMimeTypeDetector> mimeTypeDetectors = new List<IMimeTypeDetector>();
/// <summary>
/// Initializes a new instance of the <see cref="Configuration" /> class.
@ -46,30 +57,55 @@ namespace ImageSharp
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Configuration" /> class.
/// </summary>
/// <param name="providers">A collection of providers to configure</param>
public Configuration(params IImageFormatProvider[] providers)
{
if (providers != null)
{
foreach (IImageFormatProvider p in providers)
{
p.Configure(this);
}
}
}
/// <summary>
/// Gets the default <see cref="Configuration"/> instance.
/// </summary>
public static Configuration Default { get; } = Lazy.Value;
/// <summary>
/// Gets the collection of supported <see cref="IImageEncoder"/>
/// Gets the global parallel options for processing tasks in parallel.
/// </summary>
public IReadOnlyCollection<IImageEncoder> ImageEncoders => new ReadOnlyCollection<IImageEncoder>(this.encoders);
public ParallelOptions ParallelOptions { get; } = new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount };
/// <summary>
/// Gets the collection of supported <see cref="IImageDecoder"/>
/// Gets the maximum header size of all formats.
/// </summary>
public IReadOnlyCollection<IImageDecoder> ImageDecoders => new ReadOnlyCollection<IImageDecoder>(this.decoders);
internal int MaxHeaderSize { get; private set; }
/// <summary>
/// Gets the global parallel options for processing tasks in parallel.
/// Gets the currently registerd <see cref="IMimeTypeDetector"/>s.
/// </summary>
public ParallelOptions ParallelOptions { get; } = new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount };
internal IEnumerable<IMimeTypeDetector> MimeTypeDetectors => this.mimeTypeDetectors;
/// <summary>
/// Gets the maximum header size of all formats.
/// Gets the typeof of all the current image decoders
/// </summary>
internal int MaxHeaderSize { get; private set; }
internal IEnumerable<Type> AllMimeImageDecoders => this.mimeTypeDecoders.Select(x => x.Value.GetType()).Distinct().ToList();
/// <summary>
/// Gets the typeof of all the current image decoders
/// </summary>
internal IEnumerable<Type> AllMimeImageEncoders => this.mimeTypeEncoders.Select(x => x.Value.GetType()).Distinct().ToList();
/// <summary>
/// Gets the typeof of all the current image decoders
/// </summary>
internal IEnumerable<Type> AllExtImageEncoders => this.mimeTypeEncoders.Select(x => x.Value.GetType()).Distinct().ToList();
#if !NETSTANDARD1_1
/// <summary>
@ -79,36 +115,102 @@ namespace ImageSharp
#endif
/// <summary>
/// Adds a new <see cref="IImageEncoder"/> to the collection of supported image formats.
/// Registers a new format provider.
/// </summary>
/// <param name="decoder">The new format to add.</param>
public void AddImageFormat(IImageDecoder decoder)
/// <param name="formatProvider">The format providers to call configure on.</param>
public void AddImageFormat(IImageFormatProvider formatProvider)
{
Guard.NotNull(formatProvider, nameof(formatProvider));
formatProvider.Configure(this);
}
/// <inheritdoc />
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);
}
/// <inheritdoc />
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);
}
/// <inheritdoc />
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);
}
/// <summary>
/// Removes all the registerd detectors
/// </summary>
public void ClearMimeTypeDetector()
{
this.mimeTypeDetectors.Clear();
}
/// <inheritdoc />
public void AddMimeTypeDetector(IMimeTypeDetector detector)
{
Guard.NotNull(detector, nameof(detector));
this.mimeTypeDetectors.Add(detector);
this.SetMaxHeaderSize();
}
lock (this.syncRoot)
/// <summary>
/// For the specified mimetype find the decoder.
/// </summary>
/// <param name="mimeType">the mimetype to discover</param>
/// <returns>the IImageDecoder if found othersize null </returns>
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();
/// <summary>
/// For the specified mimetype find the encoder.
/// </summary>
/// <param name="mimeType">the mimetype to discover</param>
/// <returns>the IImageEncoder if found othersize null </returns>
public IImageEncoder FindMimeTypeEncoder(string mimeType)
{
Guard.NotNullOrEmpty(mimeType, nameof(mimeType));
if (this.mimeTypeEncoders.TryGetValue(mimeType, out IImageEncoder dec))
{
return dec;
}
return null;
}
/// <summary>
/// Adds a new <see cref="IImageDecoder"/> to the collection of supported image formats.
/// For the specified mimetype find the encoder.
/// </summary>
/// <param name="encoder">The new format to add.</param>
public void AddImageFormat(IImageEncoder encoder)
/// <param name="extensions">the extensions to discover</param>
/// <returns>the IImageEncoder if found othersize null </returns>
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;
}
/// <summary>
@ -117,19 +219,11 @@ namespace ImageSharp
/// <returns>The default configuration of <see cref="Configuration"/> </returns>
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());
}
/// <summary>
@ -137,7 +231,7 @@ namespace ImageSharp
/// </summary>
private void SetMaxHeaderSize()
{
this.MaxHeaderSize = this.decoders.Max(x => x.HeaderSize);
this.MaxHeaderSize = this.mimeTypeDetectors.Max(x => x.HeaderSize);
}
}
}

17
src/ImageSharp/Formats/Bmp/BmpDecoder.cs

@ -28,23 +28,6 @@ namespace ImageSharp.Formats
/// </remarks>
public class BmpDecoder : IImageDecoder
{
/// <inheritdoc/>
public IEnumerable<string> MimeTypes => BmpConstants.MimeTypes;
/// <inheritdoc/>
public IEnumerable<string> FileExtensions => BmpConstants.FileExtensions;
/// <inheritdoc/>
public int HeaderSize => 2;
/// <inheritdoc/>
public bool IsSupportedFileFormat(Span<byte> header)
{
return header.Length >= this.HeaderSize &&
header[0] == 0x42 && // B
header[1] == 0x4D; // M
}
/// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)

6
src/ImageSharp/Formats/Bmp/BmpEncoder.cs

@ -22,12 +22,6 @@ namespace ImageSharp.Formats
/// </summary>
public BmpBitsPerPixel BitsPerPixel { get; set; } = BmpBitsPerPixel.Pixel24;
/// <inheritdoc/>
public IEnumerable<string> MimeTypes => BmpConstants.MimeTypes;
/// <inheritdoc/>
public IEnumerable<string> FileExtensions => BmpConstants.FileExtensions;
/// <inheritdoc/>
public void Encode<TPixel>(Image<TPixel> image, Stream stream)
where TPixel : struct, IPixel<TPixel>

42
src/ImageSharp/Formats/Bmp/BmpImageFormatProvider.cs

@ -0,0 +1,42 @@
// <copyright file="PngImageFormatProvider.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Formats
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using ImageSharp.PixelFormats;
/// <summary>
/// Detects gif file headers
/// </summary>
public class BmpImageFormatProvider : IImageFormatProvider
{
/// <inheritdoc/>
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());
}
}
}

40
src/ImageSharp/Formats/Bmp/BmpMimeTypeDetector.cs

@ -0,0 +1,40 @@
// <copyright file="PngMimeTypeDetector.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Formats
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using ImageSharp.PixelFormats;
/// <summary>
/// Detects bmp file headers
/// </summary>
internal class BmpMimeTypeDetector : IMimeTypeDetector
{
/// <inheritdoc/>
public int HeaderSize => 2;
/// <inheritdoc/>
public string DetectMimeType(Span<byte> header)
{
if (this.IsSupportedFileFormat(header))
{
return "image/bmp";
}
return null;
}
private bool IsSupportedFileFormat(Span<byte> header)
{
return header.Length >= this.HeaderSize &&
header[0] == 0x42 && // B
header[1] == 0x4D; // M
}
}
}

21
src/ImageSharp/Formats/Gif/GifDecoder.cs

@ -16,12 +16,6 @@ namespace ImageSharp.Formats
/// </summary>
public class GifDecoder : IImageDecoder
{
/// <inheritdoc/>
public IEnumerable<string> MimeTypes => GifConstants.MimeTypes;
/// <inheritdoc/>
public IEnumerable<string> FileExtensions => GifConstants.FileExtensions;
/// <summary>
/// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded.
/// </summary>
@ -32,21 +26,6 @@ namespace ImageSharp.Formats
/// </summary>
public Encoding TextEncoding { get; set; } = GifConstants.DefaultEncoding;
/// <inheritdoc/>
public int HeaderSize => 6;
/// <inheritdoc/>
public bool IsSupportedFileFormat(Span<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
}
/// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
where TPixel : struct, IPixel<TPixel>

6
src/ImageSharp/Formats/Gif/GifEncoder.cs

@ -17,12 +17,6 @@ namespace ImageSharp.Formats
/// </summary>
public class GifEncoder : IImageEncoder
{
/// <inheritdoc/>
public IEnumerable<string> MimeTypes => GifConstants.MimeTypes;
/// <inheritdoc/>
public IEnumerable<string> FileExtensions => GifConstants.FileExtensions;
/// <summary>
/// Gets or sets a value indicating whether the metadata should be ignored when the image is being encoded.
/// </summary>

42
src/ImageSharp/Formats/Gif/GifImageFormatProvider.cs

@ -0,0 +1,42 @@
// <copyright file="PngImageFormatProvider.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Formats
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using ImageSharp.PixelFormats;
/// <summary>
/// Detects gif file headers
/// </summary>
public class GifImageFormatProvider : IImageFormatProvider
{
/// <inheritdoc/>
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());
}
}
}

44
src/ImageSharp/Formats/Gif/GifMimeTypeDetector.cs

@ -0,0 +1,44 @@
// <copyright file="PngMimeTypeDetector.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Formats
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using ImageSharp.PixelFormats;
/// <summary>
/// Detects gif file headers
/// </summary>
public class GifMimeTypeDetector : IMimeTypeDetector
{
/// <inheritdoc/>
public int HeaderSize => 6;
/// <inheritdoc/>
public string DetectMimeType(Span<byte> header)
{
if (this.IsSupportedFileFormat(header))
{
return "image/gif";
}
return null;
}
private bool IsSupportedFileFormat(Span<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
}
}
}

26
src/ImageSharp/Formats/IImageDecoder.cs

@ -16,32 +16,6 @@ namespace ImageSharp.Formats
/// </summary>
public interface IImageDecoder
{
/// <summary>
/// Gets the collection of mime types that this decoder supports decoding on.
/// </summary>
IEnumerable<string> MimeTypes { get; }
/// <summary>
/// Gets the collection of file extensionsthis decoder supports decoding.
/// </summary>
IEnumerable<string> FileExtensions { get; }
/// <summary>
/// Gets the size of the header for this image type.
/// </summary>
/// <value>The size of the header.</value>
int HeaderSize { get; }
/// <summary>
/// Returns a value indicating whether the <see cref="IImageDecoder"/> supports the specified
/// file header.
/// </summary>
/// <param name="header">The <see cref="T:byte[]"/> containing the file header.</param>
/// <returns>
/// True if the decoder supports the file header; otherwise, false.
/// </returns>
bool IsSupportedFileFormat(Span<byte> header);
/// <summary>
/// Decodes the image from the specified stream to the <see cref="ImageBase{TPixel}"/>.
/// </summary>

10
src/ImageSharp/Formats/IImageEncoder.cs

@ -16,16 +16,6 @@ namespace ImageSharp.Formats
/// </summary>
public interface IImageEncoder
{
/// <summary>
/// Gets the collection of mime types that this decoder supports encoding for.
/// </summary>
IEnumerable<string> MimeTypes { get; }
/// <summary>
/// Gets the collection of file extensionsthis decoder supports encoding for.
/// </summary>
IEnumerable<string> FileExtensions { get; }
/// <summary>
/// Encodes the image to the specified stream from the <see cref="Image{TPixel}"/>.
/// </summary>

56
src/ImageSharp/Formats/IImageFormatProvider.cs

@ -0,0 +1,56 @@
// <copyright file="IImageFormatProvider.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Formats
{
using System;
using System.Collections.Generic;
using System.Text;
/// <summary>
/// Represents an abstract class that can register image encoders, decoders and mime type detectors
/// </summary>
public interface IImageFormatProvider
{
/// <summary>
/// Called when loaded so the provider and register its encoders, decodes and mime type detectors into an IImageFormatHost.
/// </summary>
/// <param name="host">The host that will retain the encoders, decodes and mime type detectors.</param>
void Configure(IImageFormatHost host);
}
/// <summary>
/// Represents an abstract class that can have encoders decoders and mimetype detecotrs loaded into.
/// </summary>
public interface IImageFormatHost
{
/// <summary>
/// Sets a specific image encoder as the encoder for a specific mimetype
/// </summary>
/// <param name="mimeType">the target mimetype</param>
/// <param name="encoder">the encoder to use</param>
void SetMimeTypeEncoder(string mimeType, IImageEncoder encoder); // could/should this be an Action<IImageEncoder>???
/// <summary>
/// Sets a specific image encoder as the encoder for a specific mimetype
/// </summary>
/// <param name="extension">the target mimetype</param>
/// <param name="encoder">the encoder to use</param>
void SetFileExtensionEncoder(string extension, IImageEncoder encoder);
/// <summary>
/// Sets a specific image decoder as the decoder for a specific mimetype
/// </summary>
/// <param name="mimeType">the target mimetype</param>
/// <param name="decoder">the decoder to use</param>
void SetMimeTypeDecoder(string mimeType, IImageDecoder decoder);
/// <summary>
/// Adds a new detector for detecting in mime types
/// </summary>
/// <param name="detector">The detector</param>
void AddMimeTypeDetector(IMimeTypeDetector detector);
}
}

30
src/ImageSharp/Formats/IMimeTypeDetector.cs

@ -0,0 +1,30 @@
// <copyright file="IMimeTypeDetector.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Formats
{
using System;
using System.Collections.Generic;
using System.Text;
/// <summary>
/// Used for detecting mime types from a file header
/// </summary>
public interface IMimeTypeDetector
{
/// <summary>
/// Gets the size of the header for this image type.
/// </summary>
/// <value>The size of the header.</value>
int HeaderSize { get; }
/// <summary>
/// Detect mimetype
/// </summary>
/// <param name="header">The <see cref="T:byte[]"/> containing the file header.</param>
/// <returns>returns the mime type of detected othersie returns null</returns>
string DetectMimeType(Span<byte> header);
}
}

65
src/ImageSharp/Formats/Jpeg/JpegDecoder.cs

@ -21,22 +21,6 @@ namespace ImageSharp.Formats
/// </summary>
public bool IgnoreMetadata { get; set; }
/// <inheritdoc/>
public IEnumerable<string> MimeTypes => JpegConstants.MimeTypes;
/// <inheritdoc/>
public IEnumerable<string> FileExtensions => JpegConstants.FileExtensions;
/// <inheritdoc/>
public int HeaderSize => 11;
/// <inheritdoc/>
public bool IsSupportedFileFormat(Span<byte> header)
{
return header.Length >= this.HeaderSize &&
(IsJfif(header) || IsExif(header) || IsJpeg(header));
}
/// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
where TPixel : struct, IPixel<TPixel>
@ -49,54 +33,5 @@ namespace ImageSharp.Formats
return decoder.Decode<TPixel>(stream);
}
}
/// <summary>
/// Returns a value indicating whether the given bytes identify Jfif data.
/// </summary>
/// <param name="header">The bytes representing the file header.</param>
/// <returns>The <see cref="bool"/></returns>
private static bool IsJfif(Span<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;
}
/// <summary>
/// Returns a value indicating whether the given bytes identify EXIF data.
/// </summary>
/// <param name="header">The bytes representing the file header.</param>
/// <returns>The <see cref="bool"/></returns>
private static bool IsExif(Span<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;
}
/// <summary>
/// Returns a value indicating whether the given bytes identify Jpeg data.
/// This is a last chance resort for jpegs that contain ICC information.
/// </summary>
/// <param name="header">The bytes representing the file header.</param>
/// <returns>The <see cref="bool"/></returns>
private static bool IsJpeg(Span<byte> header)
{
bool isJpg =
header[0] == 0xFF && // 255
header[1] == 0xD8; // 216
return isJpg;
}
}
}

6
src/ImageSharp/Formats/Jpeg/JpegEncoder.cs

@ -34,12 +34,6 @@ namespace ImageSharp.Formats
/// <value>The subsample ratio of the jpg image.</value>
public JpegSubsample? Subsample { get; set; }
/// <inheritdoc/>
public IEnumerable<string> MimeTypes => JpegConstants.MimeTypes;
/// <inheritdoc/>
public IEnumerable<string> FileExtensions => JpegConstants.FileExtensions;
/// <summary>
/// Encodes the image to the specified stream from the <see cref="Image{TPixel}"/>.
/// </summary>

42
src/ImageSharp/Formats/Jpeg/JpegImageFormatProvider.cs

@ -0,0 +1,42 @@
// <copyright file="PngImageFormatProvider.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Formats
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using ImageSharp.PixelFormats;
/// <summary>
/// Detects png file headers
/// </summary>
public class JpegImageFormatProvider : IImageFormatProvider
{
/// <inheritdoc/>
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());
}
}
}

88
src/ImageSharp/Formats/Jpeg/JpegMimeTypeDetector.cs

@ -0,0 +1,88 @@
// <copyright file="PngMimeTypeDetector.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Formats
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using ImageSharp.PixelFormats;
/// <summary>
/// Detects Jpeg file headers
/// </summary>
public class JpegMimeTypeDetector : IMimeTypeDetector
{
/// <inheritdoc/>
public int HeaderSize => 11;
/// <inheritdoc/>
public string DetectMimeType(Span<byte> header)
{
if (this.IsSupportedFileFormat(header))
{
return "image/jpeg";
}
return null;
}
private bool IsSupportedFileFormat(Span<byte> header)
{
return header.Length >= this.HeaderSize &&
(this.IsJfif(header) || this.IsExif(header) || this.IsJpeg(header));
}
/// <summary>
/// Returns a value indicating whether the given bytes identify Jfif data.
/// </summary>
/// <param name="header">The bytes representing the file header.</param>
/// <returns>The <see cref="bool"/></returns>
private bool IsJfif(Span<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;
}
/// <summary>
/// Returns a value indicating whether the given bytes identify EXIF data.
/// </summary>
/// <param name="header">The bytes representing the file header.</param>
/// <returns>The <see cref="bool"/></returns>
private bool IsExif(Span<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;
}
/// <summary>
/// Returns a value indicating whether the given bytes identify Jpeg data.
/// This is a last chance resort for jpegs that contain ICC information.
/// </summary>
/// <param name="header">The bytes representing the file header.</param>
/// <returns>The <see cref="bool"/></returns>
private bool IsJpeg(Span<byte> header)
{
bool isJpg =
header[0] == 0xFF && // 255
header[1] == 0xD8; // 216
return isJpg;
}
}
}

23
src/ImageSharp/Formats/Png/PngDecoder.cs

@ -38,34 +38,11 @@ namespace ImageSharp.Formats
/// </summary>
public bool IgnoreMetadata { get; set; }
/// <inheritdoc/>
public IEnumerable<string> MimeTypes => PngConstants.MimeTypes;
/// <inheritdoc/>
public IEnumerable<string> FileExtensions => PngConstants.FileExtensions;
/// <inheritdoc/>
public int HeaderSize => 8;
/// <summary>
/// Gets or sets the encoding that should be used when reading text chunks.
/// </summary>
public Encoding TextEncoding { get; set; } = PngConstants.DefaultEncoding;
/// <inheritdoc/>
public bool IsSupportedFileFormat(Span<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
}
/// <summary>
/// Decodes the image from the specified stream to the <see cref="ImageBase{TPixel}"/>.
/// </summary>

6
src/ImageSharp/Formats/Png/PngEncoder.cs

@ -16,12 +16,6 @@ namespace ImageSharp.Formats
/// </summary>
public class PngEncoder : IImageEncoder
{
/// <inheritdoc/>
public IEnumerable<string> MimeTypes => PngConstants.MimeTypes;
/// <inheritdoc/>
public IEnumerable<string> FileExtensions => PngConstants.FileExtensions;
/// <summary>
/// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded.
/// </summary>

42
src/ImageSharp/Formats/Png/PngImageFormatProvider.cs

@ -0,0 +1,42 @@
// <copyright file="PngImageFormatProvider.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Formats
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using ImageSharp.PixelFormats;
/// <summary>
/// Detects png file headers
/// </summary>
public class PngImageFormatProvider : IImageFormatProvider
{
/// <inheritdoc/>
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());
}
}
}

46
src/ImageSharp/Formats/Png/PngMimeTypeDetector.cs

@ -0,0 +1,46 @@
// <copyright file="PngMimeTypeDetector.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Formats
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using ImageSharp.PixelFormats;
/// <summary>
/// Detects png file headers
/// </summary>
public class PngMimeTypeDetector : IMimeTypeDetector
{
/// <inheritdoc/>
public int HeaderSize => 8;
/// <inheritdoc/>
public string DetectMimeType(Span<byte> header)
{
if (this.IsSupportedFileFormat(header))
{
return "image/png";
}
return null;
}
private bool IsSupportedFileFormat(Span<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
}
}
}

26
src/ImageSharp/Image/Image.Decode.cs

@ -22,8 +22,8 @@ namespace ImageSharp
/// </summary>
/// <param name="stream">The image stream to read the header from.</param>
/// <param name="config">The configuration.</param>
/// <returns>The image format or null if none found.</returns>
private static IImageDecoder DiscoverDecoder(Stream stream, Configuration config)
/// <returns>The mimetype or null if none found.</returns>
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<byte>.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<byte>.Shared.Return(header);
}
}
/// <summary>
/// By reading the header on the provided stream this calculates the images format.
/// </summary>
/// <param name="stream">The image stream to read the header from.</param>
/// <param name="config">The configuration.</param>
/// <param name="mimeType">The mimeType.</param>
/// <returns>The image format or null if none found.</returns>
private static IImageDecoder DiscoverDecoder(Stream stream, Configuration config, out string mimeType)
{
format = config.FindMimeTypeDecoder(mimeType);
return format;
}
@ -59,18 +71,18 @@ namespace ImageSharp
/// <returns>
/// A new <see cref="Image{TPixel}"/>.
/// </returns>
private static (Image<TPixel> img, IImageDecoder decoder) Decode<TPixel>(Stream stream, Configuration config)
private static (Image<TPixel> img, string mimeType) Decode<TPixel>(Stream stream, Configuration config)
#pragma warning restore SA1008 // Opening parenthesis must be spaced correctly
where TPixel : struct, IPixel<TPixel>
{
IImageDecoder decoder = DiscoverDecoder(stream, config);
IImageDecoder decoder = DiscoverDecoder(stream, config, out string mimeType);
if (decoder == null)
{
return (null, null);
}
Image<TPixel> img = decoder.Decode<TPixel>(config, stream);
return (img, decoder);
return (img, mimeType);
}
}
}

24
src/ImageSharp/Image/Image.FromBytes.cs

@ -15,6 +15,30 @@ namespace ImageSharp
/// </content>
public static partial class Image
{
/// <summary>
/// By reading the header on the provided byte array this calculates the images mimetype.
/// </summary>
/// <param name="data">The byte array containing image data to read the header from.</param>
/// <returns>The mimetype or null if none found.</returns>
public static string DiscoverMimeType(byte[] data)
{
return DiscoverMimeType(null, data);
}
/// <summary>
/// By reading the header on the provided byte array this calculates the images mimetype.
/// </summary>
/// <param name="config">The configuration.</param>
/// <param name="data">The byte array containing image data to read the header from.</param>
/// <returns>The mimetype or null if none found.</returns>
public static string DiscoverMimeType(Configuration config, byte[] data)
{
using (Stream stream = new MemoryStream(data))
{
return DiscoverMimeType(config, stream);
}
}
/// <summary>
/// Create a new instance of the <see cref="Image{Rgba32}"/> class from the given byte array.
/// </summary>

25
src/ImageSharp/Image/Image.FromFile.cs

@ -16,6 +16,31 @@ namespace ImageSharp
/// </content>
public static partial class Image
{
/// <summary>
/// By reading the header on the provided file this calculates the images mimetype.
/// </summary>
/// <param name="filePath">The image file to open and to read the header from.</param>
/// <returns>The mimetype or null if none found.</returns>
public static string DiscoverMimeType(string filePath)
{
return DiscoverMimeType(null, filePath);
}
/// <summary>
/// By reading the header on the provided file this calculates the images mimetype.
/// </summary>
/// <param name="config">The configuration.</param>
/// <param name="filePath">The image file to open and to read the header from.</param>
/// <returns>The mimetype or null if none found.</returns>
public static string DiscoverMimeType(Configuration config, string filePath)
{
config = config ?? Configuration.Default;
using (Stream file = config.FileSystem.OpenRead(filePath))
{
return DiscoverMimeType(config, file);
}
}
/// <summary>
/// Create a new instance of the <see cref="Image{Rgba32}"/> class from the given file.
/// </summary>

33
src/ImageSharp/Image/Image.FromStream.cs

@ -18,6 +18,27 @@ namespace ImageSharp
/// </content>
public static partial class Image
{
/// <summary>
/// By reading the header on the provided stream this calculates the images mimetype.
/// </summary>
/// <param name="stream">The image stream to read the header from.</param>
/// <returns>The mimetype or null if none found.</returns>
public static string DiscoverMimeType(Stream stream)
{
return DiscoverMimeType(null, stream);
}
/// <summary>
/// By reading the header on the provided stream this calculates the images mimetype.
/// </summary>
/// <param name="config">The configuration.</param>
/// <param name="stream">The image stream to read the header from.</param>
/// <returns>The mimetype or null if none found.</returns>
public static string DiscoverMimeType(Configuration config, Stream stream)
{
return WithSeekableStream(stream, s => InternalDiscoverMimeType(s, config ?? Configuration.Default));
}
/// <summary>
/// Create a new instance of the <see cref="Image{Rgba32}"/> class from the given stream.
/// </summary>
@ -169,21 +190,21 @@ namespace ImageSharp
{
config = config ?? Configuration.Default;
mimeType = null;
(Image<TPixel> img, IImageDecoder decoder) data = WithSeekableStream(stream, s => Decode<TPixel>(s, config));
(Image<TPixel> img, string mimeType) data = WithSeekableStream(stream, s => Decode<TPixel>(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;

22
src/ImageSharp/Image/Image{TPixel}.cs

@ -158,16 +158,16 @@ namespace ImageSharp
public Image<TPixel> 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
/// <returns>The <see cref="string"/></returns>
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<TPixel, TPixel2>(scaleFunc);
Image<TPixel2> target = new Image<TPixel2>(this.Configuration, this.Width, this.Height);
var target = new Image<TPixel2>(this.Configuration, this.Width, this.Height);
target.CopyProperties(this);
using (PixelAccessor<TPixel> 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;
}

155
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;
/// <summary>
@ -21,23 +21,27 @@ namespace ImageSharp.Tests
/// </summary>
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<LocalFileSystem>(fs);
Assert.IsType<LocalFileSystem>(DefaultConfiguration.FileSystem);
Assert.IsType<LocalFileSystem>(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());
}
/// <summary>
@ -68,58 +72,129 @@ namespace ImageSharp.Tests
Assert.True(Configuration.Default.ParallelOptions.MaxDegreeOfParallelism == Environment.ProcessorCount);
}
/// <summary>
/// Test that the default configuration parallel options is not null.
/// </summary>
[Fact]
public void TestDefultConfigurationImageFormatsIsNotNull()
public void AddMimeTypeDetectorNullthrows()
{
Assert.True(Configuration.Default.ImageDecoders != null);
Assert.True(Configuration.Default.ImageEncoders != null);
Assert.Throws<ArgumentNullException>(() =>
{
DefaultConfiguration.AddMimeTypeDetector(null);
});
}
/// <summary>
/// Tests the <see cref="M:Configuration.AddImageFormat"/> method throws an exception
/// when the format is null.
/// </summary>
[Fact]
public void TestAddImageFormatThrowsWithNullFormat()
public void RegisterNullMimeTypeEncoder()
{
Assert.Throws<ArgumentNullException>(() =>
{
Configuration.Default.AddImageFormat((IImageEncoder)null);
DefaultConfiguration.SetMimeTypeEncoder(null, new Mock<IImageEncoder>().Object);
});
Assert.Throws<ArgumentNullException>(() =>
{
Configuration.Default.AddImageFormat((IImageDecoder)null);
DefaultConfiguration.SetMimeTypeEncoder("sdsdsd", null);
});
Assert.Throws<ArgumentNullException>(() =>
{
DefaultConfiguration.SetMimeTypeEncoder(null, null);
});
}
[Fact]
public void RegisterNullFileExtEncoder()
{
Assert.Throws<ArgumentNullException>(() =>
{
DefaultConfiguration.SetFileExtensionEncoder(null, new Mock<IImageEncoder>().Object);
});
Assert.Throws<ArgumentNullException>(() =>
{
DefaultConfiguration.SetFileExtensionEncoder("sdsdsd", null);
});
Assert.Throws<ArgumentNullException>(() =>
{
DefaultConfiguration.SetFileExtensionEncoder(null, null);
});
}
/// <summary>
/// Test that the default image constructors use default configuration.
/// </summary>
[Fact]
public void TestImageUsesDefaultConfiguration()
public void RegisterNullMimeTypeDecoder()
{
Configuration.Default.AddImageFormat(new PngDecoder());
Assert.Throws<ArgumentNullException>(() =>
{
DefaultConfiguration.SetMimeTypeDecoder(null, new Mock<IImageDecoder>().Object);
});
Assert.Throws<ArgumentNullException>(() =>
{
DefaultConfiguration.SetMimeTypeDecoder("sdsdsd", null);
});
Assert.Throws<ArgumentNullException>(() =>
{
DefaultConfiguration.SetMimeTypeDecoder(null, null);
});
}
var image = new Image<Rgba32>(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<IImageEncoder>().Object;
ConfigurationEmpty.SetMimeTypeEncoder("test", encoder1);
var found = ConfigurationEmpty.FindMimeTypeEncoder("TEST");
Assert.Equal(encoder1, found);
var encoder2 = new Mock<IImageEncoder>().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<IImageEncoder>().Object;
ConfigurationEmpty.SetFileExtensionEncoder("TEST", encoder1);
var found = ConfigurationEmpty.FindFileExtensionsEncoder("test");
Assert.Equal(encoder1, found);
var encoder2 = new Mock<IImageEncoder>().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<IImageDecoder>().Object;
ConfigurationEmpty.SetMimeTypeDecoder("test", decoder1);
var found = ConfigurationEmpty.FindMimeTypeDecoder("TEST");
Assert.Equal(decoder1, found);
var decoder2 = new Mock<IImageDecoder>().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<IImageFormatProvider>();
var config = new Configuration(provider.Object);
provider.Verify(x => x.Configure(config));
}
/// <summary>
/// Test that the default image constructor copies the configuration.
/// </summary>
[Fact]
public void TestImageCopiesConfiguration()
public void AddFormatCallsConfig()
{
Configuration.Default.AddImageFormat(new PngDecoder());
var provider = new Mock<IImageFormatProvider>();
var config = new Configuration();
config.AddImageFormat(provider.Object);
var image = new Image<Rgba32>(1, 1);
var image2 = new Image<Rgba32>(image);
Assert.Equal(image2.Configuration.ParallelOptions, image.Configuration.ParallelOptions);
Assert.True(image2.Configuration.ImageDecoders.SequenceEqual(image.Configuration.ImageDecoders));
provider.Verify(x => x.Configure(config));
}
}
}

12
tests/ImageSharp.Tests/Image/ImageLoadTests.cs

@ -23,6 +23,7 @@ namespace ImageSharp.Tests
private Image<Rgba32> returnImage;
private Mock<IImageDecoder> localDecoder;
private readonly string FilePath;
private readonly Mock<IMimeTypeDetector> localMimeTypeDetector;
public Configuration LocalConfiguration { get; private set; }
public byte[] Marker { get; private set; }
@ -34,10 +35,9 @@ namespace ImageSharp.Tests
this.returnImage = new Image<Rgba32>(1, 1);
this.localDecoder = new Mock<IImageDecoder>();
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<Span<byte>>())).Returns(true);
this.localMimeTypeDetector = new Mock<IMimeTypeDetector>();
this.localMimeTypeDetector.Setup(x => x.HeaderSize).Returns(1);
this.localMimeTypeDetector.Setup(x => x.DetectMimeType(It.IsAny<Span<byte>>())).Returns("test");
this.localDecoder.Setup(x => x.Decode<Rgba32>(It.IsAny<Configuration>(), It.IsAny<Stream>()))
@ -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();

14
tests/ImageSharp.Tests/Image/ImageSaveTests.cs

@ -24,24 +24,26 @@ namespace ImageSharp.Tests
private readonly Mock<IFileSystem> fileSystem;
private readonly Mock<IImageEncoder> encoder;
private readonly Mock<IImageEncoder> encoderNotInFormat;
private Mock<IMimeTypeDetector> localMimeTypeDetector;
public ImageSaveTests()
{
this.encoder = new Mock<IImageEncoder>();
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<IMimeTypeDetector>();
this.localMimeTypeDetector.Setup(x => x.HeaderSize).Returns(1);
this.localMimeTypeDetector.Setup(x => x.DetectMimeType(It.IsAny<Span<byte>>())).Returns("img/test");
this.encoder = new Mock<IImageEncoder>();
this.encoderNotInFormat = new Mock<IImageEncoder>();
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<IFileSystem>();
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<Rgba32>(config, 1, 1);
}

45
tests/ImageSharp.Tests/TestFormat.cs

@ -19,14 +19,13 @@ namespace ImageSharp.Tests
/// <summary>
/// A test image file.
/// </summary>
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<TPixel>(1, 1));
}
return (Image<TPixel>)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<byte> 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;

2
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()

Loading…
Cancel
Save