Browse Source

refactor encoders/decoders

refactor encoders to remove IImageFormat and split the logic across Encoders and Decoders.

remove IXXXOptions and moved setting onto encoders/decoders directly.

add out param on Image.Load APIs to output mime type of loaded image.
af/merge-core
Scott Williams 9 years ago
parent
commit
42e0395a60
  1. 153
      src/ImageSharp/Configuration.cs
  2. 25
      src/ImageSharp/Formats/Bmp/BmpConstants.cs
  3. 20
      src/ImageSharp/Formats/Bmp/BmpDecoder.cs
  4. 28
      src/ImageSharp/Formats/Bmp/BmpEncoder.cs
  5. 20
      src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
  6. 45
      src/ImageSharp/Formats/Bmp/BmpEncoderOptions.cs
  7. 41
      src/ImageSharp/Formats/Bmp/BmpFormat.cs
  8. 18
      src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs
  9. 37
      src/ImageSharp/Formats/DecoderOptions.cs
  10. 37
      src/ImageSharp/Formats/EncoderOptions.cs
  11. 13
      src/ImageSharp/Formats/Gif/GifConstants.cs
  12. 47
      src/ImageSharp/Formats/Gif/GifDecoder.cs
  13. 27
      src/ImageSharp/Formats/Gif/GifDecoderCore.cs
  14. 47
      src/ImageSharp/Formats/Gif/GifDecoderOptions.cs
  15. 51
      src/ImageSharp/Formats/Gif/GifEncoder.cs
  16. 41
      src/ImageSharp/Formats/Gif/GifEncoderCore.cs
  17. 65
      src/ImageSharp/Formats/Gif/GifEncoderOptions.cs
  18. 45
      src/ImageSharp/Formats/Gif/GifFormat.cs
  19. 20
      src/ImageSharp/Formats/Gif/IGifDecoderOptions.cs
  20. 38
      src/ImageSharp/Formats/Gif/IGifEncoderOptions.cs
  21. 8
      src/ImageSharp/Formats/Gif/ImageExtensions.cs
  22. 18
      src/ImageSharp/Formats/IDecoderOptions.cs
  23. 18
      src/ImageSharp/Formats/IEncoderOptions.cs
  24. 30
      src/ImageSharp/Formats/IImageDecoder.cs
  25. 14
      src/ImageSharp/Formats/IImageEncoder.cs
  26. 60
      src/ImageSharp/Formats/IImageFormat.cs
  27. 26
      src/ImageSharp/Formats/Jpeg/IJpegEncoderOptions.cs
  28. 8
      src/ImageSharp/Formats/Jpeg/ImageExtensions.cs
  29. 12
      src/ImageSharp/Formats/Jpeg/JpegConstants.cs
  30. 76
      src/ImageSharp/Formats/Jpeg/JpegDecoder.cs
  31. 18
      src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
  32. 50
      src/ImageSharp/Formats/Jpeg/JpegEncoder.cs
  33. 51
      src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
  34. 56
      src/ImageSharp/Formats/Jpeg/JpegEncoderOptions.cs
  35. 89
      src/ImageSharp/Formats/Jpeg/JpegFormat.cs
  36. 20
      src/ImageSharp/Formats/Png/IPngDecoderOptions.cs
  37. 54
      src/ImageSharp/Formats/Png/IPngEncoderOptions.cs
  38. 8
      src/ImageSharp/Formats/Png/ImageExtensions.cs
  39. 30
      src/ImageSharp/Formats/Png/PngConstants.cs
  40. 43
      src/ImageSharp/Formats/Png/PngDecoder.cs
  41. 42
      src/ImageSharp/Formats/Png/PngDecoderCore.cs
  42. 49
      src/ImageSharp/Formats/Png/PngDecoderOptions.cs
  43. 71
      src/ImageSharp/Formats/Png/PngEncoder.cs
  44. 84
      src/ImageSharp/Formats/Png/PngEncoderCore.cs
  45. 82
      src/ImageSharp/Formats/Png/PngEncoderOptions.cs
  46. 47
      src/ImageSharp/Formats/Png/PngFormat.cs
  47. 5
      src/ImageSharp/Image/IImage.cs
  48. 22
      src/ImageSharp/Image/Image.Decode.cs
  49. 66
      src/ImageSharp/Image/Image.FromBytes.cs
  50. 66
      src/ImageSharp/Image/Image.FromFile.cs
  51. 60
      src/ImageSharp/Image/Image.FromStream.cs
  52. 156
      src/ImageSharp/Image/Image{TPixel}.cs
  53. 1
      src/ImageSharp/ImageSharp.csproj
  54. 6
      src/ImageSharp/MetaData/ImageMetaData.cs
  55. 4
      tests/ImageSharp.Benchmarks/BenchmarkBase.cs
  56. 12
      tests/ImageSharp.Benchmarks/Image/EncodeIndexedPng.cs
  57. 2
      tests/ImageSharp.Benchmarks/Image/EncodePng.cs
  58. 204
      tests/ImageSharp.Tests/ConfigurationTests.cs
  59. 28
      tests/ImageSharp.Tests/Drawing/BeziersTests.cs
  60. 29
      tests/ImageSharp.Tests/Drawing/DrawPathTests.cs
  61. 10
      tests/ImageSharp.Tests/Drawing/FillPatternTests.cs
  62. 34
      tests/ImageSharp.Tests/Drawing/FillSolidBrushTests.cs
  63. 55
      tests/ImageSharp.Tests/Drawing/LineComplexPolygonTests.cs
  64. 119
      tests/ImageSharp.Tests/Drawing/LineTests.cs
  65. 15
      tests/ImageSharp.Tests/Drawing/PolygonTests.cs
  66. 16
      tests/ImageSharp.Tests/Drawing/RecolorImageTest.cs
  67. 10
      tests/ImageSharp.Tests/Drawing/SolidBezierTests.cs
  68. 35
      tests/ImageSharp.Tests/Drawing/SolidComplexPolygonTests.cs
  69. 96
      tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs
  70. 2
      tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs
  71. 22
      tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs
  72. 6
      tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs
  73. 8
      tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs
  74. 13
      tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs
  75. 16
      tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs
  76. 5
      tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs
  77. 6
      tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs
  78. 4
      tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs
  79. 3
      tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs
  80. 303
      tests/ImageSharp.Tests/Image/ImageLoadTests.cs
  81. 121
      tests/ImageSharp.Tests/Image/ImageSaveTests.cs
  82. 30
      tests/ImageSharp.Tests/Image/ImageTests.cs
  83. 2
      tests/ImageSharp.Tests/MetaData/ImageMetaDataTests.cs
  84. 7
      tests/ImageSharp.Tests/TestFile.cs
  85. 34
      tests/ImageSharp.Tests/TestFormat.cs
  86. 12
      tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs

153
src/ImageSharp/Configuration.cs

@ -30,27 +30,20 @@ namespace ImageSharp
private readonly object syncRoot = new object(); private readonly object syncRoot = new object();
/// <summary> /// <summary>
/// The list of supported <see cref="IImageFormat"/>. /// The list of supported <see cref="IImageEncoder"/>.
/// </summary> /// </summary>
private readonly List<IImageFormat> imageFormatsList = new List<IImageFormat>(); private readonly List<IImageEncoder> encoders = new List<IImageEncoder>();
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Configuration" /> class. /// The list of supported <see cref="IImageDecoder"/>.
/// </summary> /// </summary>
public Configuration() private readonly List<IImageDecoder> decoders = new List<IImageDecoder>();
{
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Configuration" /> class. /// Initializes a new instance of the <see cref="Configuration" /> class.
/// </summary> /// </summary>
/// <param name="providers">The inital set of image formats.</param> public Configuration()
public Configuration(params IImageFormat[] providers)
{ {
foreach (IImageFormat p in providers)
{
this.AddImageFormat(p);
}
} }
/// <summary> /// <summary>
@ -59,9 +52,14 @@ namespace ImageSharp
public static Configuration Default { get; } = Lazy.Value; public static Configuration Default { get; } = Lazy.Value;
/// <summary> /// <summary>
/// Gets the collection of supported <see cref="IImageFormat"/> /// Gets the collection of supported <see cref="IImageEncoder"/>
/// </summary> /// </summary>
public IReadOnlyCollection<IImageFormat> ImageFormats => new ReadOnlyCollection<IImageFormat>(this.imageFormatsList); public IReadOnlyCollection<IImageEncoder> ImageEncoders => new ReadOnlyCollection<IImageEncoder>(this.encoders);
/// <summary>
/// Gets the collection of supported <see cref="IImageDecoder"/>
/// </summary>
public IReadOnlyCollection<IImageDecoder> ImageDecoders => new ReadOnlyCollection<IImageDecoder>(this.decoders);
/// <summary> /// <summary>
/// Gets the global parallel options for processing tasks in parallel. /// Gets the global parallel options for processing tasks in parallel.
@ -81,118 +79,57 @@ namespace ImageSharp
#endif #endif
/// <summary> /// <summary>
/// Adds a new <see cref="IImageFormat"/> to the collection of supported image formats. /// Adds a new <see cref="IImageEncoder"/> to the collection of supported image formats.
/// </summary>
/// <param name="format">The new format to add.</param>
public void AddImageFormat(IImageFormat format)
{
Guard.NotNull(format, nameof(format));
Guard.NotNull(format.Encoder, nameof(format), "The encoder should not be null.");
Guard.NotNull(format.Decoder, nameof(format), "The decoder should not be null.");
Guard.NotNullOrEmpty(format.MimeType, nameof(format), "The mime type should not be null or empty.");
Guard.NotNullOrEmpty(format.Extension, nameof(format), "The extension should not be null or empty.");
Guard.NotNullOrEmpty(format.SupportedExtensions, nameof(format), "The supported extensions not be null or empty.");
this.AddImageFormatLocked(format);
}
/// <summary>
/// Creates the default instance, with Png, Jpeg, Gif and Bmp preregisterd (if they have been referenced)
/// </summary> /// </summary>
/// <returns>The default configuration of <see cref="Configuration"/> </returns> /// <param name="decoder">The new format to add.</param>
internal static Configuration CreateDefaultInstance() public void AddImageFormat(IImageDecoder decoder)
{ {
Configuration config = new Configuration(); Guard.NotNull(decoder, nameof(decoder));
Guard.NotNullOrEmpty(decoder.FileExtensions, nameof(decoder.FileExtensions));
// lets try auto loading the known image formats Guard.NotNullOrEmpty(decoder.MimeTypes, nameof(decoder.MimeTypes));
config.AddImageFormat(new Formats.PngFormat());
config.AddImageFormat(new Formats.JpegFormat());
config.AddImageFormat(new Formats.GifFormat());
config.AddImageFormat(new Formats.BmpFormat());
return config;
}
/// <summary> lock (this.syncRoot)
/// Tries the add image format.
/// </summary>
/// <param name="typeName">Name of the type.</param>
/// <returns>True if type discoverd and is a valid <see cref="IImageFormat"/></returns>
internal bool TryAddImageFormat(string typeName)
{
Type type = Type.GetType(typeName, false);
if (type != null)
{ {
IImageFormat format = Activator.CreateInstance(type) as IImageFormat; this.decoders.Add(decoder);
if (format != null
&& format.Encoder != null
&& format.Decoder != null
&& !string.IsNullOrEmpty(format.MimeType)
&& format.SupportedExtensions?.Any() == true)
{
// we can use the locked version as we have already validated in the if.
this.AddImageFormatLocked(format);
return true;
}
}
return false; this.SetMaxHeaderSize();
}
} }
/// <summary> /// <summary>
/// Adds image format. The class is locked to make it thread safe. /// Adds a new <see cref="IImageDecoder"/> to the collection of supported image formats.
/// </summary> /// </summary>
/// <param name="format">The image format.</param> /// <param name="encoder">The new format to add.</param>
private void AddImageFormatLocked(IImageFormat format) public void AddImageFormat(IImageEncoder encoder)
{ {
Guard.NotNull(encoder, nameof(encoder));
Guard.NotNullOrEmpty(encoder.FileExtensions, nameof(encoder.FileExtensions));
Guard.NotNullOrEmpty(encoder.MimeTypes, nameof(encoder.MimeTypes));
lock (this.syncRoot) lock (this.syncRoot)
{ {
if (this.GuardDuplicate(format)) this.encoders.Add(encoder);
{
this.imageFormatsList.Add(format);
this.SetMaxHeaderSize();
}
} }
} }
/// <summary> /// <summary>
/// Checks to ensure duplicate image formats are not added. /// Creates the default instance, with Png, Jpeg, Gif and Bmp preregisterd (if they have been referenced)
/// </summary> /// </summary>
/// <param name="format">The image format.</param> /// <returns>The default configuration of <see cref="Configuration"/> </returns>
/// <exception cref="ArgumentException">Thrown if a duplicate is added.</exception> internal static Configuration CreateDefaultInstance()
/// <returns>
/// The <see cref="bool"/>.
/// </returns>
private bool GuardDuplicate(IImageFormat format)
{ {
if (!format.SupportedExtensions.Contains(format.Extension, StringComparer.OrdinalIgnoreCase)) Configuration config = new Configuration();
{
throw new ArgumentException("The supported extensions should contain the default extension.", nameof(format));
}
// ReSharper disable once ConvertClosureToMethodGroup
// Prevents method group allocation
if (format.SupportedExtensions.Any(e => string.IsNullOrWhiteSpace(e)))
{
throw new ArgumentException("The supported extensions should not contain empty values.", nameof(format));
}
// If there is already a format with the same extension or a format that supports that
// extension return false.
foreach (IImageFormat imageFormat in this.imageFormatsList)
{
if (imageFormat.Extension.Equals(format.Extension, StringComparison.OrdinalIgnoreCase))
{
return false;
}
if (imageFormat.SupportedExtensions.Intersect(format.SupportedExtensions, StringComparer.OrdinalIgnoreCase).Any())
{
return false;
}
}
return true; // lets try auto loading the known image formats
config.AddImageFormat(new Formats.PngEncoder());
config.AddImageFormat(new Formats.JpegEncoder());
config.AddImageFormat(new Formats.GifEncoder());
config.AddImageFormat(new Formats.BmpEncoder());
config.AddImageFormat(new Formats.PngDecoder());
config.AddImageFormat(new Formats.JpegDecoder());
config.AddImageFormat(new Formats.GifDecoder());
config.AddImageFormat(new Formats.BmpDecoder());
return config;
} }
/// <summary> /// <summary>
@ -200,7 +137,7 @@ namespace ImageSharp
/// </summary> /// </summary>
private void SetMaxHeaderSize() private void SetMaxHeaderSize()
{ {
this.MaxHeaderSize = this.imageFormatsList.Max(x => x.HeaderSize); this.MaxHeaderSize = this.decoders.Max(x => x.HeaderSize);
} }
} }
} }

25
src/ImageSharp/Formats/Bmp/BmpConstants.cs

@ -0,0 +1,25 @@
// <copyright file="BmpConstants.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.Collections.Generic;
/// <summary>
/// Defines constants relating to BMPs
/// </summary>
internal static class BmpConstants
{
/// <summary>
/// The list of mimetypes that equate to a bmp
/// </summary>
public static readonly IEnumerable<string> MimeTypes = new[] { "image/bmp", "image/x-windows-bmp" };
/// <summary>
/// The list of mimetypes that equate to a bmp
/// </summary>
public static readonly IEnumerable<string> FileExtensions = new[] { "bm", "bmp", "dip" };
}
}

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

@ -6,6 +6,7 @@
namespace ImageSharp.Formats namespace ImageSharp.Formats
{ {
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using ImageSharp.PixelFormats; using ImageSharp.PixelFormats;
@ -28,7 +29,24 @@ namespace ImageSharp.Formats
public class BmpDecoder : IImageDecoder public class BmpDecoder : IImageDecoder
{ {
/// <inheritdoc/> /// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream, IDecoderOptions options) 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)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {

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

@ -6,6 +6,7 @@
namespace ImageSharp.Formats namespace ImageSharp.Formats
{ {
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using ImageSharp.PixelFormats; using ImageSharp.PixelFormats;
@ -16,26 +17,23 @@ namespace ImageSharp.Formats
/// <remarks>The encoder can currently only write 24-bit rgb images to streams.</remarks> /// <remarks>The encoder can currently only write 24-bit rgb images to streams.</remarks>
public class BmpEncoder : IImageEncoder public class BmpEncoder : IImageEncoder
{ {
/// <summary>
/// Gets or sets the number of bits per pixel.
/// </summary>
public BmpBitsPerPixel BitsPerPixel { get; set; } = BmpBitsPerPixel.Pixel24;
/// <inheritdoc/> /// <inheritdoc/>
public void Encode<TPixel>(Image<TPixel> image, Stream stream, IEncoderOptions options) public IEnumerable<string> MimeTypes => BmpConstants.MimeTypes;
where TPixel : struct, IPixel<TPixel>
{
IBmpEncoderOptions bmpOptions = BmpEncoderOptions.Create(options);
this.Encode(image, stream, bmpOptions); /// <inheritdoc/>
} public IEnumerable<string> FileExtensions => BmpConstants.FileExtensions;
/// <summary> /// <inheritdoc/>
/// Encodes the image to the specified stream from the <see cref="Image{TPixel}"/>. public void Encode<TPixel>(Image<TPixel> image, Stream stream)
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The <see cref="Image{TPixel}"/> to encode from.</param>
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
/// <param name="options">The options for the encoder.</param>
public void Encode<TPixel>(Image<TPixel> image, Stream stream, IBmpEncoderOptions options)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
BmpEncoderCore encoder = new BmpEncoderCore(options); BmpEncoderCore encoder = new BmpEncoderCore();
encoder.BitsPerPixel = this.BitsPerPixel;
encoder.Encode(image, stream); encoder.Encode(image, stream);
} }
} }

20
src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs

@ -17,11 +17,6 @@ namespace ImageSharp.Formats
/// </summary> /// </summary>
internal sealed class BmpEncoderCore internal sealed class BmpEncoderCore
{ {
/// <summary>
/// The options for the encoder.
/// </summary>
private readonly IBmpEncoderOptions options;
/// <summary> /// <summary>
/// The amount to pad each row by. /// The amount to pad each row by.
/// </summary> /// </summary>
@ -30,12 +25,15 @@ namespace ImageSharp.Formats
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="BmpEncoderCore"/> class. /// Initializes a new instance of the <see cref="BmpEncoderCore"/> class.
/// </summary> /// </summary>
/// <param name="options">The options for the encoder.</param> public BmpEncoderCore()
public BmpEncoderCore(IBmpEncoderOptions options)
{ {
this.options = options ?? new BmpEncoderOptions();
} }
/// <summary>
/// Gets or sets the BitsPerPixel
/// </summary>
public BmpBitsPerPixel BitsPerPixel { get; internal set; } = BmpBitsPerPixel.Pixel24;
/// <summary> /// <summary>
/// Encodes the image to the specified stream from the <see cref="ImageBase{TPixel}"/>. /// Encodes the image to the specified stream from the <see cref="ImageBase{TPixel}"/>.
/// </summary> /// </summary>
@ -49,9 +47,9 @@ namespace ImageSharp.Formats
Guard.NotNull(stream, nameof(stream)); Guard.NotNull(stream, nameof(stream));
// Cast to int will get the bytes per pixel // Cast to int will get the bytes per pixel
short bpp = (short)(8 * (int)this.options.BitsPerPixel); short bpp = (short)(8 * (int)this.BitsPerPixel);
int bytesPerLine = 4 * (((image.Width * bpp) + 31) / 32); int bytesPerLine = 4 * (((image.Width * bpp) + 31) / 32);
this.padding = bytesPerLine - (image.Width * (int)this.options.BitsPerPixel); this.padding = bytesPerLine - (image.Width * (int)this.BitsPerPixel);
// Do not use IDisposable pattern here as we want to preserve the stream. // Do not use IDisposable pattern here as we want to preserve the stream.
EndianBinaryWriter writer = new EndianBinaryWriter(Endianness.LittleEndian, stream); EndianBinaryWriter writer = new EndianBinaryWriter(Endianness.LittleEndian, stream);
@ -136,7 +134,7 @@ namespace ImageSharp.Formats
{ {
using (PixelAccessor<TPixel> pixels = image.Lock()) using (PixelAccessor<TPixel> pixels = image.Lock())
{ {
switch (this.options.BitsPerPixel) switch (this.BitsPerPixel)
{ {
case BmpBitsPerPixel.Pixel32: case BmpBitsPerPixel.Pixel32:
this.Write32Bit(writer, pixels); this.Write32Bit(writer, pixels);

45
src/ImageSharp/Formats/Bmp/BmpEncoderOptions.cs

@ -1,45 +0,0 @@
// <copyright file="BmpEncoderOptions.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Formats
{
/// <summary>
/// Encapsulates the options for the <see cref="BmpEncoder"/>.
/// </summary>
public sealed class BmpEncoderOptions : EncoderOptions, IBmpEncoderOptions
{
/// <summary>
/// Initializes a new instance of the <see cref="BmpEncoderOptions"/> class.
/// </summary>
public BmpEncoderOptions()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="BmpEncoderOptions"/> class.
/// </summary>
/// <param name="options">The options for the encoder.</param>
private BmpEncoderOptions(IEncoderOptions options)
: base(options)
{
}
/// <summary>
/// Gets or sets the number of bits per pixel.
/// </summary>
public BmpBitsPerPixel BitsPerPixel { get; set; } = BmpBitsPerPixel.Pixel24;
/// <summary>
/// Converts the options to a <see cref="IBmpEncoderOptions"/> instance with a cast
/// or by creating a new instance with the specfied options.
/// </summary>
/// <param name="options">The options for the encoder.</param>
/// <returns>The options for the <see cref="BmpEncoder"/>.</returns>
internal static IBmpEncoderOptions Create(IEncoderOptions options)
{
return options as IBmpEncoderOptions ?? new BmpEncoderOptions(options);
}
}
}

41
src/ImageSharp/Formats/Bmp/BmpFormat.cs

@ -1,41 +0,0 @@
// <copyright file="BmpFormat.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.Collections.Generic;
/// <summary>
/// Encapsulates the means to encode and decode bitmap images.
/// </summary>
public class BmpFormat : IImageFormat
{
/// <inheritdoc/>
public string MimeType => "image/bmp";
/// <inheritdoc/>
public string Extension => "bmp";
/// <inheritdoc/>
public IEnumerable<string> SupportedExtensions => new string[] { "bmp", "dip" };
/// <inheritdoc/>
public IImageDecoder Decoder => new BmpDecoder();
/// <inheritdoc/>
public IImageEncoder Encoder => new BmpEncoder();
/// <inheritdoc/>
public int HeaderSize => 2;
/// <inheritdoc/>
public bool IsSupportedFileFormat(byte[] header)
{
return header.Length >= this.HeaderSize &&
header[0] == 0x42 && // B
header[1] == 0x4D; // M
}
}
}

18
src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs

@ -1,18 +0,0 @@
// <copyright file="IBmpEncoderOptions.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Formats
{
/// <summary>
/// Encapsulates the options for the <see cref="BmpEncoder"/>.
/// </summary>
public interface IBmpEncoderOptions : IEncoderOptions
{
/// <summary>
/// Gets the number of bits per pixel.
/// </summary>
BmpBitsPerPixel BitsPerPixel { get; }
}
}

37
src/ImageSharp/Formats/DecoderOptions.cs

@ -1,37 +0,0 @@
// <copyright file="DecoderOptions.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp
{
/// <summary>
/// Encapsulates the shared decoder options.
/// </summary>
public class DecoderOptions : IDecoderOptions
{
/// <summary>
/// Initializes a new instance of the <see cref="DecoderOptions"/> class.
/// </summary>
public DecoderOptions()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="DecoderOptions"/> class.
/// </summary>
/// <param name="options">The decoder options</param>
protected DecoderOptions(IDecoderOptions options)
{
if (options != null)
{
this.IgnoreMetadata = options.IgnoreMetadata;
}
}
/// <summary>
/// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded.
/// </summary>
public bool IgnoreMetadata { get; set; } = false;
}
}

37
src/ImageSharp/Formats/EncoderOptions.cs

@ -1,37 +0,0 @@
// <copyright file="EncoderOptions.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp
{
/// <summary>
/// Encapsulates the shared encoder options.
/// </summary>
public class EncoderOptions : IEncoderOptions
{
/// <summary>
/// Initializes a new instance of the <see cref="EncoderOptions"/> class.
/// </summary>
public EncoderOptions()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="EncoderOptions"/> class.
/// </summary>
/// <param name="options">The encoder options</param>
protected EncoderOptions(IEncoderOptions options)
{
if (options != null)
{
this.IgnoreMetadata = options.IgnoreMetadata;
}
}
/// <summary>
/// Gets or sets a value indicating whether the metadata should be ignored when the image is being encoded.
/// </summary>
public bool IgnoreMetadata { get; set; } = false;
}
}

13
src/ImageSharp/Formats/Gif/GifConstants.cs

@ -5,6 +5,7 @@
namespace ImageSharp.Formats namespace ImageSharp.Formats
{ {
using System.Collections.Generic;
using System.Text; using System.Text;
/// <summary> /// <summary>
@ -90,6 +91,16 @@ namespace ImageSharp.Formats
/// <summary> /// <summary>
/// Gets the default encoding to use when reading comments. /// Gets the default encoding to use when reading comments.
/// </summary> /// </summary>
public static Encoding DefaultEncoding { get; } = Encoding.GetEncoding("ASCII"); public static readonly Encoding DefaultEncoding = Encoding.GetEncoding("ASCII");
/// <summary>
/// The list of mimetypes that equate to a bmp
/// </summary>
public static readonly IEnumerable<string> MimeTypes = new[] { "image/gif" };
/// <summary>
/// The list of mimetypes that equate to a bmp
/// </summary>
public static readonly IEnumerable<string> FileExtensions = new[] { "gif" };
} }
} }

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

@ -6,8 +6,9 @@
namespace ImageSharp.Formats namespace ImageSharp.Formats
{ {
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Text;
using ImageSharp.PixelFormats; using ImageSharp.PixelFormats;
/// <summary> /// <summary>
@ -16,27 +17,43 @@ namespace ImageSharp.Formats
public class GifDecoder : IImageDecoder public class GifDecoder : IImageDecoder
{ {
/// <inheritdoc/> /// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream, IDecoderOptions options) public IEnumerable<string> MimeTypes => GifConstants.MimeTypes;
where TPixel : struct, IPixel<TPixel> /// <inheritdoc/>
{ public IEnumerable<string> FileExtensions => GifConstants.FileExtensions;
IGifDecoderOptions gifOptions = GifDecoderOptions.Create(options);
return this.Decode<TPixel>(configuration, stream, gifOptions); /// <summary>
} /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded.
/// </summary>
public bool IgnoreMetadata { get; set; } = false;
/// <summary> /// <summary>
/// Decodes the image from the specified stream to the <see cref="ImageBase{TPixel}"/>. /// Gets or sets the encoding that should be used when reading comments.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> public Encoding TextEncoding { get; set; } = GifConstants.DefaultEncoding;
/// <param name="configuration">The configuration.</param>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param> /// <inheritdoc/>
/// <param name="options">The options for the decoder.</param> public int HeaderSize => 6;
/// <returns>The image thats been decoded.</returns>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream, IGifDecoderOptions options) /// <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> where TPixel : struct, IPixel<TPixel>
{ {
return new GifDecoderCore<TPixel>(options, configuration).Decode(stream); var decoder = new GifDecoderCore<TPixel>(this.TextEncoding, configuration);
decoder.IgnoreMetadata = this.IgnoreMetadata;
return decoder.Decode(stream);
} }
} }
} }

27
src/ImageSharp/Formats/Gif/GifDecoderCore.cs

@ -25,11 +25,6 @@ namespace ImageSharp.Formats
/// </summary> /// </summary>
private readonly byte[] buffer = new byte[16]; private readonly byte[] buffer = new byte[16];
/// <summary>
/// The decoder options.
/// </summary>
private readonly IGifDecoderOptions options;
/// <summary> /// <summary>
/// The global configuration. /// The global configuration.
/// </summary> /// </summary>
@ -83,14 +78,24 @@ namespace ImageSharp.Formats
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="GifDecoderCore{TPixel}"/> class. /// Initializes a new instance of the <see cref="GifDecoderCore{TPixel}"/> class.
/// </summary> /// </summary>
/// <param name="options">The decoder options.</param> /// <param name="encoding">The decoder encoding.</param>
/// <param name="configuration">The configuration.</param> /// <param name="configuration">The configuration.</param>
public GifDecoderCore(IGifDecoderOptions options, Configuration configuration) public GifDecoderCore(Encoding encoding, Configuration configuration)
{ {
this.options = options ?? new GifDecoderOptions(); this.TextEncoding = encoding ?? GifConstants.DefaultEncoding;
this.configuration = configuration ?? Configuration.Default; this.configuration = configuration ?? Configuration.Default;
} }
/// <summary>
/// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded.
/// </summary>
public bool IgnoreMetadata { get; internal set; }
/// <summary>
/// Gets the text encoding
/// </summary>
public Encoding TextEncoding { get; private set; }
/// <summary> /// <summary>
/// Decodes the stream to the image. /// Decodes the stream to the image.
/// </summary> /// </summary>
@ -268,7 +273,7 @@ namespace ImageSharp.Formats
throw new ImageFormatException($"Gif comment length '{length}' exceeds max '{GifConstants.MaxCommentLength}'"); throw new ImageFormatException($"Gif comment length '{length}' exceeds max '{GifConstants.MaxCommentLength}'");
} }
if (this.options.IgnoreMetadata) if (this.IgnoreMetadata)
{ {
this.currentStream.Seek(length, SeekOrigin.Current); this.currentStream.Seek(length, SeekOrigin.Current);
continue; continue;
@ -279,7 +284,7 @@ namespace ImageSharp.Formats
try try
{ {
this.currentStream.Read(commentsBuffer, 0, length); this.currentStream.Read(commentsBuffer, 0, length);
string comments = this.options.TextEncoding.GetString(commentsBuffer, 0, length); string comments = this.TextEncoding.GetString(commentsBuffer, 0, length);
this.metaData.Properties.Add(new ImageProperty(GifConstants.Comments, comments)); this.metaData.Properties.Add(new ImageProperty(GifConstants.Comments, comments));
} }
finally finally
@ -363,8 +368,6 @@ namespace ImageSharp.Formats
if (this.previousFrame == null) if (this.previousFrame == null)
{ {
this.metaData.Quality = colorTableLength / 3;
// This initializes the image to become fully transparent because the alpha channel is zero. // This initializes the image to become fully transparent because the alpha channel is zero.
this.image = new Image<TPixel>(this.configuration, imageWidth, imageHeight, this.metaData); this.image = new Image<TPixel>(this.configuration, imageWidth, imageHeight, this.metaData);

47
src/ImageSharp/Formats/Gif/GifDecoderOptions.cs

@ -1,47 +0,0 @@
// <copyright file="GifDecoderOptions.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.Text;
/// <summary>
/// Encapsulates the options for the <see cref="GifDecoder"/>.
/// </summary>
public sealed class GifDecoderOptions : DecoderOptions, IGifDecoderOptions
{
/// <summary>
/// Initializes a new instance of the <see cref="GifDecoderOptions"/> class.
/// </summary>
public GifDecoderOptions()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="GifDecoderOptions"/> class.
/// </summary>
/// <param name="options">The options for the decoder.</param>
private GifDecoderOptions(IDecoderOptions options)
: base(options)
{
}
/// <summary>
/// Gets or sets the encoding that should be used when reading comments.
/// </summary>
public Encoding TextEncoding { get; set; } = GifConstants.DefaultEncoding;
/// <summary>
/// Converts the options to a <see cref="IGifDecoderOptions"/> instance with a cast
/// or by creating a new instance with the specfied options.
/// </summary>
/// <param name="options">The options for the decoder.</param>
/// <returns>The options for the <see cref="GifDecoder"/>.</returns>
internal static IGifDecoderOptions Create(IDecoderOptions options)
{
return options as IGifDecoderOptions ?? new GifDecoderOptions(options);
}
}
}

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

@ -6,9 +6,11 @@
namespace ImageSharp.Formats namespace ImageSharp.Formats
{ {
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Text;
using ImageSharp.PixelFormats; using ImageSharp.PixelFormats;
using ImageSharp.Quantizers;
/// <summary> /// <summary>
/// Image encoder for writing image data to a stream in gif format. /// Image encoder for writing image data to a stream in gif format.
@ -16,25 +18,46 @@ namespace ImageSharp.Formats
public class GifEncoder : IImageEncoder public class GifEncoder : IImageEncoder
{ {
/// <inheritdoc/> /// <inheritdoc/>
public void Encode<TPixel>(Image<TPixel> image, Stream stream, IEncoderOptions options) public IEnumerable<string> MimeTypes => GifConstants.MimeTypes;
where TPixel : struct, IPixel<TPixel>
{
IGifEncoderOptions gifOptions = GifEncoderOptions.Create(options);
this.Encode(image, stream, gifOptions); /// <inheritdoc/>
} public IEnumerable<string> FileExtensions => GifConstants.FileExtensions;
/// <summary> /// <summary>
/// Encodes the image to the specified stream from the <see cref="Image{TPixel}"/>. /// Gets or sets a value indicating whether the metadata should be ignored when the image is being encoded.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> public bool IgnoreMetadata { get; set; } = false;
/// <param name="image">The <see cref="Image{TPixel}"/> to encode from.</param>
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param> /// <summary>
/// <param name="options">The options for the encoder.</param> /// Gets or sets the encoding that should be used when writing comments.
public void Encode<TPixel>(Image<TPixel> image, Stream stream, IGifEncoderOptions options) /// </summary>
public Encoding TextEncoding { get; set; } = GifConstants.DefaultEncoding;
/// <summary>
/// Gets or sets the quality of output for images.
/// </summary>
/// <remarks>For gifs the value ranges from 1 to 256.</remarks>
public int Quality { get; set; }
/// <summary>
/// Gets or sets the transparency threshold.
/// </summary>
public byte Threshold { get; set; } = 128;
/// <summary>
/// Gets or sets the quantizer for reducing the color count.
/// </summary>
public IQuantizer Quantizer { get; set; }
/// <inheritdoc/>
public void Encode<TPixel>(Image<TPixel> image, Stream stream)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
GifEncoderCore encoder = new GifEncoderCore(options); GifEncoderCore encoder = new GifEncoderCore(this.TextEncoding);
encoder.Quantizer = this.Quantizer;
encoder.Threshold = this.Threshold;
encoder.Quality = this.Quality;
encoder.IgnoreMetadata = this.IgnoreMetadata;
encoder.Encode(image, stream); encoder.Encode(image, stream);
} }
} }

41
src/ImageSharp/Formats/Gif/GifEncoderCore.cs

@ -9,7 +9,7 @@ namespace ImageSharp.Formats
using System.Buffers; using System.Buffers;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text;
using ImageSharp.PixelFormats; using ImageSharp.PixelFormats;
using IO; using IO;
@ -25,11 +25,6 @@ namespace ImageSharp.Formats
/// </summary> /// </summary>
private readonly byte[] buffer = new byte[16]; private readonly byte[] buffer = new byte[16];
/// <summary>
/// The options for the encoder.
/// </summary>
private readonly IGifEncoderOptions options;
/// <summary> /// <summary>
/// The number of bits requires to store the image palette. /// The number of bits requires to store the image palette.
/// </summary> /// </summary>
@ -43,17 +38,37 @@ namespace ImageSharp.Formats
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="GifEncoderCore"/> class. /// Initializes a new instance of the <see cref="GifEncoderCore"/> class.
/// </summary> /// </summary>
/// <param name="options">The options for the encoder.</param> /// <param name="encoding">The encoding for the encoder.</param>
public GifEncoderCore(IGifEncoderOptions options) public GifEncoderCore(Encoding encoding)
{ {
this.options = options ?? new GifEncoderOptions(); this.TextEncoding = encoding ?? GifConstants.DefaultEncoding;
} }
/// <summary>
/// Gets the TextEncoding
/// </summary>
public Encoding TextEncoding { get; private set; }
/// <summary> /// <summary>
/// Gets or sets the quantizer for reducing the color count. /// Gets or sets the quantizer for reducing the color count.
/// </summary> /// </summary>
public IQuantizer Quantizer { get; set; } public IQuantizer Quantizer { get; set; }
/// <summary>
/// Gets or sets the threshold.
/// </summary>
public byte Threshold { get; internal set; }
/// <summary>
/// Gets or sets the quality of output for images.
/// </summary>
public int Quality { get; internal set; }
/// <summary>
/// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded.
/// </summary>
public bool IgnoreMetadata { get; internal set; }
/// <summary> /// <summary>
/// Encodes the image to the specified stream from the <see cref="Image{TPixel}"/>. /// Encodes the image to the specified stream from the <see cref="Image{TPixel}"/>.
/// </summary> /// </summary>
@ -66,13 +81,13 @@ namespace ImageSharp.Formats
Guard.NotNull(image, nameof(image)); Guard.NotNull(image, nameof(image));
Guard.NotNull(stream, nameof(stream)); Guard.NotNull(stream, nameof(stream));
this.Quantizer = this.options.Quantizer ?? new OctreeQuantizer<TPixel>(); this.Quantizer = this.Quantizer ?? new OctreeQuantizer<TPixel>();
// Do not use IDisposable pattern here as we want to preserve the stream. // Do not use IDisposable pattern here as we want to preserve the stream.
var writer = new EndianBinaryWriter(Endianness.LittleEndian, stream); var writer = new EndianBinaryWriter(Endianness.LittleEndian, stream);
// Ensure that quality can be set but has a fallback. // Ensure that quality can be set but has a fallback.
int quality = this.options.Quality > 0 ? this.options.Quality : image.MetaData.Quality; int quality = this.Quality;
quality = quality > 0 ? quality.Clamp(1, 256) : 256; quality = quality > 0 ? quality.Clamp(1, 256) : 256;
// Get the number of bits. // Get the number of bits.
@ -240,7 +255,7 @@ namespace ImageSharp.Formats
private void WriteComments<TPixel>(Image<TPixel> image, EndianBinaryWriter writer) private void WriteComments<TPixel>(Image<TPixel> image, EndianBinaryWriter writer)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
if (this.options.IgnoreMetadata) if (this.IgnoreMetadata)
{ {
return; return;
} }
@ -251,7 +266,7 @@ namespace ImageSharp.Formats
return; return;
} }
byte[] comments = this.options.TextEncoding.GetBytes(property.Value); byte[] comments = this.TextEncoding.GetBytes(property.Value);
int count = Math.Min(comments.Length, 255); int count = Math.Min(comments.Length, 255);

65
src/ImageSharp/Formats/Gif/GifEncoderOptions.cs

@ -1,65 +0,0 @@
// <copyright file="GifEncoderOptions.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.Text;
using Quantizers;
/// <summary>
/// Encapsulates the options for the <see cref="GifEncoder"/>.
/// </summary>
public sealed class GifEncoderOptions : EncoderOptions, IGifEncoderOptions
{
/// <summary>
/// Initializes a new instance of the <see cref="GifEncoderOptions"/> class.
/// </summary>
public GifEncoderOptions()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="GifEncoderOptions"/> class.
/// </summary>
/// <param name="options">The options for the encoder.</param>
private GifEncoderOptions(IEncoderOptions options)
: base(options)
{
}
/// <summary>
/// Gets or sets the encoding that should be used when writing comments.
/// </summary>
public Encoding TextEncoding { get; set; } = GifConstants.DefaultEncoding;
/// <summary>
/// Gets or sets the quality of output for images.
/// </summary>
/// <remarks>For gifs the value ranges from 1 to 256.</remarks>
public int Quality { get; set; }
/// <summary>
/// Gets or sets the transparency threshold.
/// </summary>
public byte Threshold { get; set; } = 128;
/// <summary>
/// Gets or sets the quantizer for reducing the color count.
/// </summary>
public IQuantizer Quantizer { get; set; }
/// <summary>
/// Converts the options to a <see cref="IGifEncoderOptions"/> instance with a
/// cast or by creating a new instance with the specfied options.
/// </summary>
/// <param name="options">The options for the encoder.</param>
/// <returns>The options for the <see cref="GifEncoder"/>.</returns>
internal static IGifEncoderOptions Create(IEncoderOptions options)
{
return options as IGifEncoderOptions ?? new GifEncoderOptions(options);
}
}
}

45
src/ImageSharp/Formats/Gif/GifFormat.cs

@ -1,45 +0,0 @@
// <copyright file="GifFormat.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.Collections.Generic;
/// <summary>
/// Encapsulates the means to encode and decode gif images.
/// </summary>
public class GifFormat : IImageFormat
{
/// <inheritdoc/>
public string Extension => "gif";
/// <inheritdoc/>
public string MimeType => "image/gif";
/// <inheritdoc/>
public IEnumerable<string> SupportedExtensions => new string[] { "gif" };
/// <inheritdoc/>
public IImageDecoder Decoder => new GifDecoder();
/// <inheritdoc/>
public IImageEncoder Encoder => new GifEncoder();
/// <inheritdoc/>
public int HeaderSize => 6;
/// <inheritdoc/>
public bool IsSupportedFileFormat(byte[] header)
{
return header.Length >= this.HeaderSize &&
header[0] == 0x47 && // G
header[1] == 0x49 && // I
header[2] == 0x46 && // F
header[3] == 0x38 && // 8
(header[4] == 0x39 || header[4] == 0x37) && // 9 or 7
header[5] == 0x61; // a
}
}
}

20
src/ImageSharp/Formats/Gif/IGifDecoderOptions.cs

@ -1,20 +0,0 @@
// <copyright file="IGifDecoderOptions.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.Text;
/// <summary>
/// Encapsulates the options for the <see cref="GifDecoder"/>.
/// </summary>
public interface IGifDecoderOptions : IDecoderOptions
{
/// <summary>
/// Gets the encoding that should be used when reading comments.
/// </summary>
Encoding TextEncoding { get; }
}
}

38
src/ImageSharp/Formats/Gif/IGifEncoderOptions.cs

@ -1,38 +0,0 @@
// <copyright file="IGifEncoderOptions.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.Text;
using Quantizers;
/// <summary>
/// Encapsulates the options for the <see cref="GifEncoder"/>.
/// </summary>
public interface IGifEncoderOptions : IEncoderOptions
{
/// <summary>
/// Gets the encoding that should be used when writing comments.
/// </summary>
Encoding TextEncoding { get; }
/// <summary>
/// Gets the quality of output for images.
/// </summary>
/// <remarks>For gifs the value ranges from 1 to 256.</remarks>
int Quality { get; }
/// <summary>
/// Gets the transparency threshold.
/// </summary>
byte Threshold { get; }
/// <summary>
/// Gets the quantizer for reducing the color count.
/// </summary>
IQuantizer Quantizer { get; }
}
}

8
src/ImageSharp/Formats/Gif/ImageExtensions.cs

@ -39,16 +39,16 @@ namespace ImageSharp
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param> /// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param> /// <param name="stream">The stream to save the image to.</param>
/// <param name="options">The options for the encoder.</param> /// <param name="encoder">The options for the encoder.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception> /// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <returns> /// <returns>
/// The <see cref="Image{TPixel}"/>. /// The <see cref="Image{TPixel}"/>.
/// </returns> /// </returns>
public static Image<TPixel> SaveAsGif<TPixel>(this Image<TPixel> source, Stream stream, IGifEncoderOptions options) public static Image<TPixel> SaveAsGif<TPixel>(this Image<TPixel> source, Stream stream, GifEncoder encoder)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
GifEncoder encoder = new GifEncoder(); encoder = encoder ?? new GifEncoder();
encoder.Encode(source, stream, options); encoder.Encode(source, stream);
return source; return source;
} }

18
src/ImageSharp/Formats/IDecoderOptions.cs

@ -1,18 +0,0 @@
// <copyright file="IDecoderOptions.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp
{
/// <summary>
/// Encapsulates the shared decoder options.
/// </summary>
public interface IDecoderOptions
{
/// <summary>
/// Gets a value indicating whether the metadata should be ignored when the image is being decoded.
/// </summary>
bool IgnoreMetadata { get; }
}
}

18
src/ImageSharp/Formats/IEncoderOptions.cs

@ -1,18 +0,0 @@
// <copyright file="IEncoderOptions.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp
{
/// <summary>
/// Encapsulates the shared encoder options.
/// </summary>
public interface IEncoderOptions
{
/// <summary>
/// Gets a value indicating whether the metadata should be ignored when the image is being encoded.
/// </summary>
bool IgnoreMetadata { get; }
}
}

30
src/ImageSharp/Formats/IImageDecoder.cs

@ -6,6 +6,7 @@
namespace ImageSharp.Formats namespace ImageSharp.Formats
{ {
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using ImageSharp.PixelFormats; using ImageSharp.PixelFormats;
@ -15,15 +16,40 @@ namespace ImageSharp.Formats
/// </summary> /// </summary>
public interface IImageDecoder 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> /// <summary>
/// Decodes the image from the specified stream to the <see cref="ImageBase{TPixel}"/>. /// Decodes the image from the specified stream to the <see cref="ImageBase{TPixel}"/>.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="configuration">The configuration for the image.</param> /// <param name="configuration">The configuration for the image.</param>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param> /// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <param name="options">The options for the decoder.</param>
/// <returns>The decoded image</returns> /// <returns>The decoded image</returns>
Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream, IDecoderOptions options) Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
where TPixel : struct, IPixel<TPixel>; where TPixel : struct, IPixel<TPixel>;
} }
} }

14
src/ImageSharp/Formats/IImageEncoder.cs

@ -6,6 +6,7 @@
namespace ImageSharp.Formats namespace ImageSharp.Formats
{ {
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using ImageSharp.PixelFormats; using ImageSharp.PixelFormats;
@ -15,14 +16,23 @@ namespace ImageSharp.Formats
/// </summary> /// </summary>
public interface IImageEncoder 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> /// <summary>
/// Encodes the image to the specified stream from the <see cref="Image{TPixel}"/>. /// Encodes the image to the specified stream from the <see cref="Image{TPixel}"/>.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The <see cref="Image{TPixel}"/> to encode from.</param> /// <param name="image">The <see cref="Image{TPixel}"/> to encode from.</param>
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param> /// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
/// <param name="options">The options for the encoder.</param> void Encode<TPixel>(Image<TPixel> image, Stream stream)
void Encode<TPixel>(Image<TPixel> image, Stream stream, IEncoderOptions options)
where TPixel : struct, IPixel<TPixel>; where TPixel : struct, IPixel<TPixel>;
} }
} }

60
src/ImageSharp/Formats/IImageFormat.cs

@ -1,60 +0,0 @@
// <copyright file="IImageFormat.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.Collections.Generic;
/// <summary>
/// Encapsulates a supported image format, providing means to encode and decode an image.
/// Individual formats implements in this interface must be registered in the <see cref="Configuration"/>
/// </summary>
public interface IImageFormat
{
/// <summary>
/// Gets the standard identifier used on the Internet to indicate the type of data that a file contains.
/// </summary>
string MimeType { get; }
/// <summary>
/// Gets the default file extension for this format.
/// </summary>
string Extension { get; }
/// <summary>
/// Gets the supported file extensions for this format.
/// </summary>
/// <returns>
/// The supported file extension.
/// </returns>
IEnumerable<string> SupportedExtensions { get; }
/// <summary>
/// Gets the image encoder for encoding an image from a stream.
/// </summary>
IImageEncoder Encoder { get; }
/// <summary>
/// Gets the image decoder for decoding an image from a stream.
/// </summary>
IImageDecoder Decoder { 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(byte[] header);
}
}

26
src/ImageSharp/Formats/Jpeg/IJpegEncoderOptions.cs

@ -1,26 +0,0 @@
// <copyright file="IJpegEncoderOptions.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Formats
{
/// <summary>
/// Encapsulates the options for the <see cref="JpegEncoder"/>.
/// </summary>
public interface IJpegEncoderOptions : IEncoderOptions
{
/// <summary>
/// Gets the quality, that will be used to encode the image. Quality
/// index must be between 0 and 100 (compression from max to min).
/// </summary>
/// <value>The quality of the jpg image from 0 to 100.</value>
int Quality { get; }
/// <summary>
/// Gets the subsample ration, that will be used to encode the image.
/// </summary>
/// <value>The subsample ratio of the jpg image.</value>
JpegSubsample? Subsample { get; }
}
}

8
src/ImageSharp/Formats/Jpeg/ImageExtensions.cs

@ -39,16 +39,16 @@ namespace ImageSharp
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param> /// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param> /// <param name="stream">The stream to save the image to.</param>
/// <param name="options">The options for the encoder.</param> /// <param name="encoder">The options for the encoder.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception> /// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <returns> /// <returns>
/// The <see cref="Image{TPixel}"/>. /// The <see cref="Image{TPixel}"/>.
/// </returns> /// </returns>
public static Image<TPixel> SaveAsJpeg<TPixel>(this Image<TPixel> source, Stream stream, IJpegEncoderOptions options) public static Image<TPixel> SaveAsJpeg<TPixel>(this Image<TPixel> source, Stream stream, JpegEncoder encoder)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
JpegEncoder encoder = new JpegEncoder(); encoder = encoder ?? new JpegEncoder();
encoder.Encode(source, stream, options); encoder.Encode(source, stream);
return source; return source;
} }

12
src/ImageSharp/Formats/Jpeg/JpegConstants.cs

@ -5,6 +5,8 @@
namespace ImageSharp.Formats namespace ImageSharp.Formats
{ {
using System.Collections.Generic;
/// <summary> /// <summary>
/// Defines jpeg constants defined in the specification. /// Defines jpeg constants defined in the specification.
/// </summary> /// </summary>
@ -15,6 +17,16 @@ namespace ImageSharp.Formats
/// </summary> /// </summary>
public const ushort MaxLength = 65535; public const ushort MaxLength = 65535;
/// <summary>
/// The list of mimetypes that equate to a jpeg
/// </summary>
public static readonly IEnumerable<string> MimeTypes = new[] { "image/jpeg", "image/pjpeg" };
/// <summary>
/// The list of mimetypes that equate to a jpeg
/// </summary>
public static readonly IEnumerable<string> FileExtensions = new[] { "jpg", "jpeg", "jfif" };
/// <summary> /// <summary>
/// Represents high detail chroma horizontal subsampling. /// Represents high detail chroma horizontal subsampling.
/// </summary> /// </summary>

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

@ -6,6 +6,7 @@
namespace ImageSharp.Formats namespace ImageSharp.Formats
{ {
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using ImageSharp.PixelFormats; using ImageSharp.PixelFormats;
@ -15,16 +16,87 @@ namespace ImageSharp.Formats
/// </summary> /// </summary>
public class JpegDecoder : IImageDecoder public class JpegDecoder : IImageDecoder
{ {
/// <summary>
/// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded.
/// </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/> /// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream, IDecoderOptions options) public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
Guard.NotNull(stream, "stream"); Guard.NotNull(stream, "stream");
using (JpegDecoderCore decoder = new JpegDecoderCore(options, configuration)) using (JpegDecoderCore decoder = new JpegDecoderCore(configuration))
{ {
decoder.IgnoreMetadata = this.IgnoreMetadata;
return decoder.Decode<TPixel>(stream); 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;
}
} }
} }

18
src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs

@ -45,11 +45,6 @@ namespace ImageSharp.Formats
/// </summary> /// </summary>
private static YCbCrToRgbTables yCbCrToRgbTables = YCbCrToRgbTables.Create(); private static YCbCrToRgbTables yCbCrToRgbTables = YCbCrToRgbTables.Create();
/// <summary>
/// The decoder options.
/// </summary>
private readonly IDecoderOptions options;
/// <summary> /// <summary>
/// The global configuration /// The global configuration
/// </summary> /// </summary>
@ -103,12 +98,10 @@ namespace ImageSharp.Formats
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="JpegDecoderCore" /> class. /// Initializes a new instance of the <see cref="JpegDecoderCore" /> class.
/// </summary> /// </summary>
/// <param name="options">The decoder options.</param>
/// <param name="configuration">The configuration.</param> /// <param name="configuration">The configuration.</param>
public JpegDecoderCore(IDecoderOptions options, Configuration configuration) public JpegDecoderCore(Configuration configuration)
{ {
this.configuration = configuration ?? Configuration.Default; this.configuration = configuration ?? Configuration.Default;
this.options = options ?? new DecoderOptions();
this.HuffmanTrees = HuffmanTree.CreateHuffmanTrees(); this.HuffmanTrees = HuffmanTree.CreateHuffmanTrees();
this.QuantizationTables = new Block8x8F[MaxTq + 1]; this.QuantizationTables = new Block8x8F[MaxTq + 1];
this.Temp = new byte[2 * Block8x8F.ScalarCount]; this.Temp = new byte[2 * Block8x8F.ScalarCount];
@ -190,6 +183,11 @@ namespace ImageSharp.Formats
/// </summary> /// </summary>
public int TotalMCUCount => this.MCUCountX * this.MCUCountY; public int TotalMCUCount => this.MCUCountX * this.MCUCountY;
/// <summary>
/// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded.
/// </summary>
public bool IgnoreMetadata { get; internal set; }
/// <summary> /// <summary>
/// Decodes the image from the specified <see cref="Stream"/> and sets /// Decodes the image from the specified <see cref="Stream"/> and sets
/// the data to image. /// the data to image.
@ -938,7 +936,7 @@ namespace ImageSharp.Formats
/// <param name="metadata">The image.</param> /// <param name="metadata">The image.</param>
private void ProcessApp1Marker(int remaining, ImageMetaData metadata) private void ProcessApp1Marker(int remaining, ImageMetaData metadata)
{ {
if (remaining < 6 || this.options.IgnoreMetadata) if (remaining < 6 || this.IgnoreMetadata)
{ {
this.InputProcessor.Skip(remaining); this.InputProcessor.Skip(remaining);
return; return;
@ -968,7 +966,7 @@ namespace ImageSharp.Formats
{ {
// Length is 14 though we only need to check 12. // Length is 14 though we only need to check 12.
const int Icclength = 14; const int Icclength = 14;
if (remaining < Icclength || this.options.IgnoreMetadata) if (remaining < Icclength || this.IgnoreMetadata)
{ {
this.InputProcessor.Skip(remaining); this.InputProcessor.Skip(remaining);
return; return;

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

@ -5,6 +5,8 @@
namespace ImageSharp.Formats namespace ImageSharp.Formats
{ {
using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using ImageSharp.PixelFormats; using ImageSharp.PixelFormats;
@ -14,14 +16,29 @@ namespace ImageSharp.Formats
/// </summary> /// </summary>
public class JpegEncoder : IImageEncoder public class JpegEncoder : IImageEncoder
{ {
/// <summary>
/// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded.
/// </summary>
public bool IgnoreMetadata { get; set; }
/// <summary>
/// Gets or sets the quality, that will be used to encode the image. Quality
/// index must be between 0 and 100 (compression from max to min).
/// </summary>
/// <value>The quality of the jpg image from 0 to 100.</value>
public int Quality { get; set; }
/// <summary>
/// Gets or sets the subsample ration, that will be used to encode the image.
/// </summary>
/// <value>The subsample ratio of the jpg image.</value>
public JpegSubsample? Subsample { get; set; }
/// <inheritdoc/> /// <inheritdoc/>
public void Encode<TPixel>(Image<TPixel> image, Stream stream, IEncoderOptions options) public IEnumerable<string> MimeTypes => JpegConstants.MimeTypes;
where TPixel : struct, IPixel<TPixel>
{
IJpegEncoderOptions gifOptions = JpegEncoderOptions.Create(options);
this.Encode(image, stream, gifOptions); /// <inheritdoc/>
} public IEnumerable<string> FileExtensions => JpegConstants.FileExtensions;
/// <summary> /// <summary>
/// Encodes the image to the specified stream from the <see cref="Image{TPixel}"/>. /// Encodes the image to the specified stream from the <see cref="Image{TPixel}"/>.
@ -29,12 +46,23 @@ namespace ImageSharp.Formats
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The <see cref="Image{TPixel}"/> to encode from.</param> /// <param name="image">The <see cref="Image{TPixel}"/> to encode from.</param>
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param> /// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
/// <param name="options">The options for the encoder.</param> public void Encode<TPixel>(Image<TPixel> image, Stream stream)
public void Encode<TPixel>(Image<TPixel> image, Stream stream, IJpegEncoderOptions options) where TPixel : struct, IPixel<TPixel>
where TPixel : struct, IPixel<TPixel>
{ {
JpegEncoderCore encode = new JpegEncoderCore(options); JpegEncoderCore encoder = new JpegEncoderCore();
encode.Encode(image, stream);
var quality = this.Quality;
if (quality == 0)
{
quality = 75;
}
encoder.Quality = quality;
encoder.Subsample = this.Subsample ?? (quality >= 91 ? JpegSubsample.Ratio444 : JpegSubsample.Ratio420);
encoder.IgnoreMetadata = this.IgnoreMetadata;
encoder.Encode(image, stream);
} }
} }
} }

51
src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs

@ -124,11 +124,6 @@ namespace ImageSharp.Formats
/// </summary> /// </summary>
private readonly byte[] huffmanBuffer = new byte[179]; private readonly byte[] huffmanBuffer = new byte[179];
/// <summary>
/// The options for the encoder.
/// </summary>
private readonly IJpegEncoderOptions options;
/// <summary> /// <summary>
/// The accumulated bits to write to the stream. /// The accumulated bits to write to the stream.
/// </summary> /// </summary>
@ -154,20 +149,30 @@ namespace ImageSharp.Formats
/// </summary> /// </summary>
private Stream outputStream; private Stream outputStream;
/// <summary>
/// The subsampling method to use.
/// </summary>
private JpegSubsample subsample;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="JpegEncoderCore"/> class. /// Initializes a new instance of the <see cref="JpegEncoderCore"/> class.
/// </summary> /// </summary>
/// <param name="options">The options for the encoder.</param> public JpegEncoderCore()
public JpegEncoderCore(IJpegEncoderOptions options)
{ {
this.options = options ?? new JpegEncoderOptions();
} }
/// <summary>
/// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded.
/// </summary>
public bool IgnoreMetadata { get; internal set; } = false;
/// <summary>
/// Gets or sets the quality, that will be used to encode the image. Quality
/// index must be between 0 and 100 (compression from max to min).
/// </summary>
/// <value>The quality of the jpg image from 0 to 100.</value>
public int Quality { get; internal set; }
/// <summary>
/// Gets or sets the subsampling method to use.
/// </summary>
public JpegSubsample? Subsample { get; internal set; }
/// <summary> /// <summary>
/// Encode writes the image to the jpeg baseline format with the given options. /// Encode writes the image to the jpeg baseline format with the given options.
/// </summary> /// </summary>
@ -186,21 +191,13 @@ namespace ImageSharp.Formats
throw new ImageFormatException($"Image is too large to encode at {image.Width}x{image.Height}."); throw new ImageFormatException($"Image is too large to encode at {image.Width}x{image.Height}.");
} }
// Ensure that quality can be set but has a fallback.
int quality = this.options.Quality > 0 ? this.options.Quality : image.MetaData.Quality;
if (quality == 0)
{
quality = 75;
}
quality = quality.Clamp(1, 100);
this.outputStream = stream; this.outputStream = stream;
this.subsample = this.options.Subsample ?? (quality >= 91 ? JpegSubsample.Ratio444 : JpegSubsample.Ratio420);
int quality = this.Quality.Clamp(1, 100);
// Convert from a quality rating to a scaling factor. // Convert from a quality rating to a scaling factor.
int scale; int scale;
if (quality < 50) if (this.Quality < 50)
{ {
scale = 5000 / quality; scale = 5000 / quality;
} }
@ -788,7 +785,7 @@ namespace ImageSharp.Formats
private void WriteProfiles<TPixel>(Image<TPixel> image) private void WriteProfiles<TPixel>(Image<TPixel> image)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
if (this.options.IgnoreMetadata) if (this.IgnoreMetadata)
{ {
return; return;
} }
@ -810,7 +807,7 @@ namespace ImageSharp.Formats
byte[] subsamples = { 0x22, 0x11, 0x11 }; byte[] subsamples = { 0x22, 0x11, 0x11 };
byte[] chroma = { 0x00, 0x01, 0x01 }; byte[] chroma = { 0x00, 0x01, 0x01 };
switch (this.subsample) switch (this.Subsample)
{ {
case JpegSubsample.Ratio444: case JpegSubsample.Ratio444:
subsamples = new byte[] { 0x11, 0x11, 0x11 }; subsamples = new byte[] { 0x11, 0x11, 0x11 };
@ -866,7 +863,7 @@ namespace ImageSharp.Formats
// TODO: We should allow grayscale writing. // TODO: We should allow grayscale writing.
this.outputStream.Write(SosHeaderYCbCr, 0, SosHeaderYCbCr.Length); this.outputStream.Write(SosHeaderYCbCr, 0, SosHeaderYCbCr.Length);
switch (this.subsample) switch (this.Subsample)
{ {
case JpegSubsample.Ratio444: case JpegSubsample.Ratio444:
this.Encode444(pixels); this.Encode444(pixels);

56
src/ImageSharp/Formats/Jpeg/JpegEncoderOptions.cs

@ -1,56 +0,0 @@
// <copyright file="JpegEncoderOptions.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Formats
{
/// <summary>
/// Encapsulates the options for the <see cref="JpegEncoder"/>.
/// </summary>
public sealed class JpegEncoderOptions : EncoderOptions, IJpegEncoderOptions
{
/// <summary>
/// Initializes a new instance of the <see cref="JpegEncoderOptions"/> class.
/// </summary>
public JpegEncoderOptions()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="JpegEncoderOptions"/> class.
/// </summary>
/// <param name="options">The options for the encoder.</param>
private JpegEncoderOptions(IEncoderOptions options)
: base(options)
{
}
/// <summary>
/// Gets or sets the quality, that will be used to encode the image. Quality
/// index must be between 0 and 100 (compression from max to min).
/// </summary>
/// <remarks>
/// If the quality is less than or equal to 90, the subsampling ratio will switch to <see cref="JpegSubsample.Ratio420"/>
/// </remarks>
/// <value>The quality of the jpg image from 0 to 100.</value>
public int Quality { get; set; }
/// <summary>
/// Gets or sets the subsample ration, that will be used to encode the image.
/// </summary>
/// <value>The subsample ratio of the jpg image.</value>
public JpegSubsample? Subsample { get; set; }
/// <summary>
/// Converts the options to a <see cref="IJpegEncoderOptions"/> instance with a
/// cast or by creating a new instance with the specfied options.
/// </summary>
/// <param name="options">The options for the encoder.</param>
/// <returns>The options for the <see cref="JpegEncoder"/>.</returns>
internal static IJpegEncoderOptions Create(IEncoderOptions options)
{
return options as IJpegEncoderOptions ?? new JpegEncoderOptions(options);
}
}
}

89
src/ImageSharp/Formats/Jpeg/JpegFormat.cs

@ -1,89 +0,0 @@
// <copyright file="JpegFormat.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.Collections.Generic;
/// <summary>
/// Encapsulates the means to encode and decode jpeg images.
/// </summary>
public class JpegFormat : IImageFormat
{
/// <inheritdoc/>
public string MimeType => "image/jpeg";
/// <inheritdoc/>
public string Extension => "jpg";
/// <inheritdoc/>
public IEnumerable<string> SupportedExtensions => new string[] { "jpg", "jpeg", "jfif" };
/// <inheritdoc/>
public IImageDecoder Decoder => new JpegDecoder();
/// <inheritdoc/>
public IImageEncoder Encoder => new JpegEncoder();
/// <inheritdoc/>
public int HeaderSize => 11;
/// <inheritdoc/>
public bool IsSupportedFileFormat(byte[] header)
{
return header.Length >= this.HeaderSize &&
(IsJfif(header) || IsExif(header) || 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 static bool IsJfif(byte[] header)
{
bool isJfif =
header[6] == 0x4A && // J
header[7] == 0x46 && // F
header[8] == 0x49 && // I
header[9] == 0x46 && // F
header[10] == 0x00;
return isJfif;
}
/// <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(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(byte[] header)
{
bool isJpg =
header[0] == 0xFF && // 255
header[1] == 0xD8; // 216
return isJpg;
}
}
}

20
src/ImageSharp/Formats/Png/IPngDecoderOptions.cs

@ -1,20 +0,0 @@
// <copyright file="IPngDecoderOptions.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.Text;
/// <summary>
/// Encapsulates the options for the <see cref="PngDecoder"/>.
/// </summary>
public interface IPngDecoderOptions : IDecoderOptions
{
/// <summary>
/// Gets the encoding that should be used when reading text chunks.
/// </summary>
Encoding TextEncoding { get; }
}
}

54
src/ImageSharp/Formats/Png/IPngEncoderOptions.cs

@ -1,54 +0,0 @@
// <copyright file="IPngEncoderOptions.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 Quantizers;
/// <summary>
/// Encapsulates the options for the <see cref="PngEncoder"/>.
/// </summary>
public interface IPngEncoderOptions : IEncoderOptions
{
/// <summary>
/// Gets the quality of output for images.
/// </summary>
int Quality { get; }
/// <summary>
/// Gets the png color type
/// </summary>
PngColorType PngColorType { get; }
/// <summary>
/// Gets the compression level 1-9.
/// </summary>
int CompressionLevel { get; }
/// <summary>
/// Gets the gamma value, that will be written
/// the the stream, when the <see cref="WriteGamma"/> property
/// is set to true.
/// </summary>
/// <value>The gamma value of the image.</value>
float Gamma { get; }
/// <summary>
/// Gets quantizer for reducing the color count.
/// </summary>
IQuantizer Quantizer { get; }
/// <summary>
/// Gets the transparency threshold.
/// </summary>
byte Threshold { get; }
/// <summary>
/// Gets a value indicating whether this instance should write
/// gamma information to the stream.
/// </summary>
bool WriteGamma { get; }
}
}

8
src/ImageSharp/Formats/Png/ImageExtensions.cs

@ -38,16 +38,16 @@ namespace ImageSharp
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param> /// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param> /// <param name="stream">The stream to save the image to.</param>
/// <param name="options">The options for the encoder.</param> /// <param name="encoder">The options for the encoder.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception> /// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <returns> /// <returns>
/// The <see cref="Image{TPixel}"/>. /// The <see cref="Image{TPixel}"/>.
/// </returns> /// </returns>
public static Image<TPixel> SaveAsPng<TPixel>(this Image<TPixel> source, Stream stream, IPngEncoderOptions options) public static Image<TPixel> SaveAsPng<TPixel>(this Image<TPixel> source, Stream stream, PngEncoder encoder)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
PngEncoder encoder = new PngEncoder(); encoder = encoder ?? new PngEncoder();
encoder.Encode(source, stream, options); encoder.Encode(source, stream);
return source; return source;
} }

30
src/ImageSharp/Formats/Png/PngConstants.cs

@ -0,0 +1,30 @@
// <copyright file="PngConstants.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.Collections.Generic;
using System.Text;
/// <summary>
/// Defines png constants defined in the specification.
/// </summary>
internal static class PngConstants
{
/// <summary>
/// The default encoding for text metadata
/// </summary>
public static readonly Encoding DefaultEncoding = Encoding.GetEncoding("ASCII");
/// <summary>
/// The list of mimetypes that equate to a jpeg
/// </summary>
public static readonly IEnumerable<string> MimeTypes = new[] { "image/png" };
/// <summary>
/// The list of mimetypes that equate to a jpeg
/// </summary>
public static readonly IEnumerable<string> FileExtensions = new[] { "png" };
}
}

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

@ -6,8 +6,9 @@
namespace ImageSharp.Formats namespace ImageSharp.Formats
{ {
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Text;
using ImageSharp.PixelFormats; using ImageSharp.PixelFormats;
/// <summary> /// <summary>
@ -32,14 +33,37 @@ namespace ImageSharp.Formats
/// </remarks> /// </remarks>
public class PngDecoder : IImageDecoder public class PngDecoder : IImageDecoder
{ {
/// <summary>
/// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded.
/// </summary>
public bool IgnoreMetadata { get; set; }
/// <inheritdoc/> /// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream, IDecoderOptions options) public IEnumerable<string> MimeTypes => PngConstants.MimeTypes;
where TPixel : struct, IPixel<TPixel> /// <inheritdoc/>
{ public IEnumerable<string> FileExtensions => PngConstants.FileExtensions;
IPngDecoderOptions pngOptions = PngDecoderOptions.Create(options);
return this.Decode<TPixel>(configuration, stream, pngOptions); /// <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> /// <summary>
@ -48,12 +72,13 @@ namespace ImageSharp.Formats
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="configuration">The configuration for the image.</param> /// <param name="configuration">The configuration for the image.</param>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param> /// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <param name="options">The options for the decoder.</param>
/// <returns>The decoded image.</returns> /// <returns>The decoded image.</returns>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream, IPngDecoderOptions options) public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
return new PngDecoderCore(options, configuration).Decode<TPixel>(stream); var decoder = new PngDecoderCore(configuration, this.TextEncoding);
decoder.IgnoreMetadata = this.IgnoreMetadata;
return decoder.Decode<TPixel>(stream);
} }
} }
} }

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

@ -11,7 +11,7 @@ namespace ImageSharp.Formats
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Text;
using ImageSharp.Memory; using ImageSharp.Memory;
using ImageSharp.PixelFormats; using ImageSharp.PixelFormats;
@ -74,11 +74,6 @@ namespace ImageSharp.Formats
/// </summary> /// </summary>
private readonly char[] chars = new char[4]; private readonly char[] chars = new char[4];
/// <summary>
/// The decoder options.
/// </summary>
private readonly IPngDecoderOptions options;
/// <summary> /// <summary>
/// Reusable crc for validating chunks. /// Reusable crc for validating chunks.
/// </summary> /// </summary>
@ -154,21 +149,31 @@ namespace ImageSharp.Formats
/// </summary> /// </summary>
private int currentRowBytesRead; private int currentRowBytesRead;
/// <summary>
/// Gets or sets the png color type
/// </summary>
private PngColorType pngColorType;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="PngDecoderCore"/> class. /// Initializes a new instance of the <see cref="PngDecoderCore"/> class.
/// </summary> /// </summary>
/// <param name="options">The decoder options.</param>
/// <param name="configuration">The configuration.</param> /// <param name="configuration">The configuration.</param>
public PngDecoderCore(IPngDecoderOptions options, Configuration configuration) /// <param name="encoding">The text encoding.</param>
public PngDecoderCore(Configuration configuration, Encoding encoding)
{ {
this.configuration = configuration ?? Configuration.Default; this.configuration = configuration ?? Configuration.Default;
this.options = options ?? new PngDecoderOptions(); this.TextEncoding = encoding ?? PngConstants.DefaultEncoding;
} }
/// <summary> /// <summary>
/// Gets or sets the png color type /// Gets the encoding to use
/// </summary>
public Encoding TextEncoding { get; private set; }
/// <summary>
/// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded.
/// </summary> /// </summary>
public PngColorType PngColorType { get; set; } public bool IgnoreMetadata { get; internal set; }
/// <summary> /// <summary>
/// Decodes the stream to the image. /// Decodes the stream to the image.
@ -221,7 +226,6 @@ namespace ImageSharp.Formats
byte[] pal = new byte[currentChunk.Length]; byte[] pal = new byte[currentChunk.Length];
Buffer.BlockCopy(currentChunk.Data, 0, pal, 0, currentChunk.Length); Buffer.BlockCopy(currentChunk.Data, 0, pal, 0, currentChunk.Length);
this.palette = pal; this.palette = pal;
metadata.Quality = pal.Length / 3;
break; break;
case PngChunkTypes.PaletteAlpha: case PngChunkTypes.PaletteAlpha:
byte[] alpha = new byte[currentChunk.Length]; byte[] alpha = new byte[currentChunk.Length];
@ -344,7 +348,7 @@ namespace ImageSharp.Formats
/// <returns>The <see cref="int"/></returns> /// <returns>The <see cref="int"/></returns>
private int CalculateBytesPerPixel() private int CalculateBytesPerPixel()
{ {
switch (this.PngColorType) switch (this.pngColorType)
{ {
case PngColorType.Grayscale: case PngColorType.Grayscale:
return 1; return 1;
@ -572,7 +576,7 @@ namespace ImageSharp.Formats
Span<TPixel> rowSpan = pixels.GetRowSpan(this.currentRow); Span<TPixel> rowSpan = pixels.GetRowSpan(this.currentRow);
var scanlineBuffer = new Span<byte>(defilteredScanline, 1); var scanlineBuffer = new Span<byte>(defilteredScanline, 1);
switch (this.PngColorType) switch (this.pngColorType)
{ {
case PngColorType.Grayscale: case PngColorType.Grayscale:
int factor = 255 / ((int)Math.Pow(2, this.header.BitDepth) - 1); int factor = 255 / ((int)Math.Pow(2, this.header.BitDepth) - 1);
@ -731,7 +735,7 @@ namespace ImageSharp.Formats
{ {
var color = default(TPixel); var color = default(TPixel);
switch (this.PngColorType) switch (this.pngColorType)
{ {
case PngColorType.Grayscale: case PngColorType.Grayscale:
int factor = 255 / ((int)Math.Pow(2, this.header.BitDepth) - 1); int factor = 255 / ((int)Math.Pow(2, this.header.BitDepth) - 1);
@ -896,7 +900,7 @@ namespace ImageSharp.Formats
/// <param name="length">The maximum length to read.</param> /// <param name="length">The maximum length to read.</param>
private void ReadTextChunk(ImageMetaData metadata, byte[] data, int length) private void ReadTextChunk(ImageMetaData metadata, byte[] data, int length)
{ {
if (this.options.IgnoreMetadata) if (this.IgnoreMetadata)
{ {
return; return;
} }
@ -912,8 +916,8 @@ namespace ImageSharp.Formats
} }
} }
string name = this.options.TextEncoding.GetString(data, 0, zeroIndex); string name = this.TextEncoding.GetString(data, 0, zeroIndex);
string value = this.options.TextEncoding.GetString(data, zeroIndex + 1, length - zeroIndex - 1); string value = this.TextEncoding.GetString(data, zeroIndex + 1, length - zeroIndex - 1);
metadata.Properties.Add(new ImageProperty(name, value)); metadata.Properties.Add(new ImageProperty(name, value));
} }
@ -967,7 +971,7 @@ namespace ImageSharp.Formats
throw new NotSupportedException("The png specification only defines 'None' and 'Adam7' as interlaced methods."); throw new NotSupportedException("The png specification only defines 'None' and 'Adam7' as interlaced methods.");
} }
this.PngColorType = this.header.ColorType; this.pngColorType = this.header.ColorType;
} }
/// <summary> /// <summary>

49
src/ImageSharp/Formats/Png/PngDecoderOptions.cs

@ -1,49 +0,0 @@
// <copyright file="PngDecoderOptions.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.Text;
/// <summary>
/// Encapsulates the options for the <see cref="PngDecoder"/>.
/// </summary>
public sealed class PngDecoderOptions : DecoderOptions, IPngDecoderOptions
{
private static readonly Encoding DefaultEncoding = Encoding.GetEncoding("ASCII");
/// <summary>
/// Initializes a new instance of the <see cref="PngDecoderOptions"/> class.
/// </summary>
public PngDecoderOptions()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="PngDecoderOptions"/> class.
/// </summary>
/// <param name="options">The options for the decoder.</param>
private PngDecoderOptions(IDecoderOptions options)
: base(options)
{
}
/// <summary>
/// Gets or sets the encoding that should be used when reading text chunks.
/// </summary>
public Encoding TextEncoding { get; set; } = DefaultEncoding;
/// <summary>
/// Converts the options to a <see cref="IPngDecoderOptions"/> instance with a cast
/// or by creating a new instance with the specfied options.
/// </summary>
/// <param name="options">The options for the decoder.</param>
/// <returns>The options for the <see cref="PngDecoder"/>.</returns>
internal static IPngDecoderOptions Create(IDecoderOptions options)
{
return options as IPngDecoderOptions ?? new PngDecoderOptions(options);
}
}
}

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

@ -5,9 +5,11 @@
namespace ImageSharp.Formats namespace ImageSharp.Formats
{ {
using System.Collections.Generic;
using System.IO; using System.IO;
using ImageSharp.PixelFormats; using ImageSharp.PixelFormats;
using ImageSharp.Quantizers;
/// <summary> /// <summary>
/// Image encoder for writing image data to a stream in png format. /// Image encoder for writing image data to a stream in png format.
@ -15,13 +17,55 @@ namespace ImageSharp.Formats
public class PngEncoder : IImageEncoder public class PngEncoder : IImageEncoder
{ {
/// <inheritdoc/> /// <inheritdoc/>
public void Encode<TPixel>(Image<TPixel> image, Stream stream, IEncoderOptions options) public IEnumerable<string> MimeTypes => PngConstants.MimeTypes;
where TPixel : struct, IPixel<TPixel>
{
IPngEncoderOptions pngOptions = PngEncoderOptions.Create(options);
this.Encode(image, stream, pngOptions); /// <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>
public bool IgnoreMetadata { get; set; }
/// <summary>
/// Gets or sets the quality of output for images.
/// </summary>
public int Quality { get; set; }
/// <summary>
/// Gets or sets the png color type
/// </summary>
public PngColorType PngColorType { get; set; } = PngColorType.RgbWithAlpha;
/// <summary>
/// Gets or sets the compression level 1-9.
/// <remarks>Defaults to 6.</remarks>
/// </summary>
public int CompressionLevel { get; set; } = 6;
/// <summary>
/// Gets or sets the gamma value, that will be written
/// the the stream, when the <see cref="WriteGamma"/> property
/// is set to true. The default value is 2.2F.
/// </summary>
/// <value>The gamma value of the image.</value>
public float Gamma { get; set; } = 2.2F;
/// <summary>
/// Gets or sets quantizer for reducing the color count.
/// </summary>
public IQuantizer Quantizer { get; set; }
/// <summary>
/// Gets or sets the transparency threshold.
/// </summary>
public byte Threshold { get; set; } = 255;
/// <summary>
/// Gets or sets a value indicating whether this instance should write
/// gamma information to the stream. The default value is false.
/// </summary>
public bool WriteGamma { get; set; }
/// <summary> /// <summary>
/// Encodes the image to the specified stream from the <see cref="Image{TPixel}"/>. /// Encodes the image to the specified stream from the <see cref="Image{TPixel}"/>.
@ -29,12 +73,21 @@ namespace ImageSharp.Formats
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The <see cref="Image{TPixel}"/> to encode from.</param> /// <param name="image">The <see cref="Image{TPixel}"/> to encode from.</param>
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param> /// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
/// <param name="options">The options for the encoder.</param> public void Encode<TPixel>(Image<TPixel> image, Stream stream)
public void Encode<TPixel>(Image<TPixel> image, Stream stream, IPngEncoderOptions options)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
using (var encode = new PngEncoderCore(options)) using (var encode = new PngEncoderCore())
{ {
encode.IgnoreMetadata = this.IgnoreMetadata;
encode.Quality = this.Quality > 0 ? this.Quality.Clamp(1, int.MaxValue) : int.MaxValue;
encode.PngColorType = this.PngColorType;
encode.CompressionLevel = this.CompressionLevel;
encode.Gamma = this.Gamma;
encode.Quantizer = this.Quantizer;
encode.Threshold = this.Threshold;
encode.WriteGamma = this.WriteGamma;
encode.Encode(image, stream); encode.Encode(image, stream);
} }
} }

84
src/ImageSharp/Formats/Png/PngEncoderCore.cs

@ -43,11 +43,6 @@ namespace ImageSharp.Formats
/// </summary> /// </summary>
private readonly Crc32 crc = new Crc32(); private readonly Crc32 crc = new Crc32();
/// <summary>
/// The options for the encoder.
/// </summary>
private readonly IPngEncoderOptions options;
/// <summary> /// <summary>
/// Contains the raw pixel data from an indexed image. /// Contains the raw pixel data from an indexed image.
/// </summary> /// </summary>
@ -113,11 +108,6 @@ namespace ImageSharp.Formats
/// </summary> /// </summary>
private Buffer<byte> paeth; private Buffer<byte> paeth;
/// <summary>
/// The quality of output for images.
/// </summary>
private int quality;
/// <summary> /// <summary>
/// The png color type. /// The png color type.
/// </summary> /// </summary>
@ -131,12 +121,50 @@ namespace ImageSharp.Formats
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="PngEncoderCore"/> class. /// Initializes a new instance of the <see cref="PngEncoderCore"/> class.
/// </summary> /// </summary>
/// <param name="options">The options for the encoder.</param> public PngEncoderCore()
public PngEncoderCore(IPngEncoderOptions options)
{ {
this.options = options ?? new PngEncoderOptions();
} }
/// <summary>
/// Gets or sets a value indicating whether to ignore metadata
/// </summary>
public bool IgnoreMetadata { get; internal set; }
/// <summary>
/// Gets or sets the Quality value
/// </summary>
public int Quality { get; internal set; }
/// <summary>
/// Gets or sets the Quality value
/// </summary>
public PngColorType PngColorType { get; internal set; }
/// <summary>
/// Gets or sets the CompressionLevel value
/// </summary>
public int CompressionLevel { get; internal set; }
/// <summary>
/// Gets or sets the Gamma value
/// </summary>
public float Gamma { get; internal set; }
/// <summary>
/// Gets or sets the Quantizer value
/// </summary>
public IQuantizer Quantizer { get; internal set; }
/// <summary>
/// Gets or sets the Threshold value
/// </summary>
public byte Threshold { get; internal set; }
/// <summary>
/// Gets or sets a value indicating whether to Write Gamma
/// </summary>
public bool WriteGamma { get; internal set; }
/// <summary> /// <summary>
/// Encodes the image to the specified stream from the <see cref="Image{TPixel}"/>. /// Encodes the image to the specified stream from the <see cref="Image{TPixel}"/>.
/// </summary> /// </summary>
@ -164,27 +192,23 @@ namespace ImageSharp.Formats
stream.Write(this.chunkDataBuffer, 0, 8); stream.Write(this.chunkDataBuffer, 0, 8);
// Ensure that quality can be set but has a fallback. this.pngColorType = this.PngColorType;
this.quality = this.options.Quality > 0 ? this.options.Quality : image.MetaData.Quality; this.quantizer = this.Quantizer;
this.quality = this.quality > 0 ? this.quality.Clamp(1, int.MaxValue) : int.MaxValue;
this.pngColorType = this.options.PngColorType;
this.quantizer = this.options.Quantizer;
// Set correct color type if the color count is 256 or less. // Set correct color type if the color count is 256 or less.
if (this.quality <= 256) if (this.Quality <= 256)
{ {
this.pngColorType = PngColorType.Palette; this.pngColorType = PngColorType.Palette;
} }
if (this.pngColorType == PngColorType.Palette && this.quality > 256) if (this.pngColorType == PngColorType.Palette && this.Quality > 256)
{ {
this.quality = 256; this.Quality = 256;
} }
// Set correct bit depth. // Set correct bit depth.
this.bitDepth = this.quality <= 256 this.bitDepth = this.Quality <= 256
? (byte)ImageMaths.GetBitsNeededForColorDepth(this.quality).Clamp(1, 8) ? (byte)ImageMaths.GetBitsNeededForColorDepth(this.Quality).Clamp(1, 8)
: (byte)8; : (byte)8;
// Png only supports in four pixel depths: 1, 2, 4, and 8 bits when using the PLTE chunk // Png only supports in four pixel depths: 1, 2, 4, and 8 bits when using the PLTE chunk
@ -514,7 +538,7 @@ namespace ImageSharp.Formats
private QuantizedImage<TPixel> WritePaletteChunk<TPixel>(Stream stream, PngHeader header, ImageBase<TPixel> image) private QuantizedImage<TPixel> WritePaletteChunk<TPixel>(Stream stream, PngHeader header, ImageBase<TPixel> image)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
if (this.quality > 256) if (this.Quality > 256)
{ {
return null; return null;
} }
@ -525,7 +549,7 @@ namespace ImageSharp.Formats
} }
// Quantize the image returning a palette. This boxing is icky. // Quantize the image returning a palette. This boxing is icky.
QuantizedImage<TPixel> quantized = ((IQuantizer<TPixel>)this.quantizer).Quantize(image, this.quality); QuantizedImage<TPixel> quantized = ((IQuantizer<TPixel>)this.quantizer).Quantize(image, this.Quality);
// Grab the palette and write it to the stream. // Grab the palette and write it to the stream.
TPixel[] palette = quantized.Palette; TPixel[] palette = quantized.Palette;
@ -552,7 +576,7 @@ namespace ImageSharp.Formats
colorTable[offset + 1] = bytes[1]; colorTable[offset + 1] = bytes[1];
colorTable[offset + 2] = bytes[2]; colorTable[offset + 2] = bytes[2];
if (alpha > this.options.Threshold) if (alpha > this.Threshold)
{ {
alpha = 255; alpha = 255;
} }
@ -610,9 +634,9 @@ namespace ImageSharp.Formats
/// <param name="stream">The <see cref="Stream"/> containing image data.</param> /// <param name="stream">The <see cref="Stream"/> containing image data.</param>
private void WriteGammaChunk(Stream stream) private void WriteGammaChunk(Stream stream)
{ {
if (this.options.WriteGamma) if (this.WriteGamma)
{ {
int gammaValue = (int)(this.options.Gamma * 100000F); int gammaValue = (int)(this.Gamma * 100000F);
byte[] size = BitConverter.GetBytes(gammaValue); byte[] size = BitConverter.GetBytes(gammaValue);
@ -655,7 +679,7 @@ namespace ImageSharp.Formats
try try
{ {
memoryStream = new MemoryStream(); memoryStream = new MemoryStream();
using (var deflateStream = new ZlibDeflateStream(memoryStream, this.options.CompressionLevel)) using (var deflateStream = new ZlibDeflateStream(memoryStream, this.CompressionLevel))
{ {
for (int y = 0; y < this.height; y++) for (int y = 0; y < this.height; y++)
{ {

82
src/ImageSharp/Formats/Png/PngEncoderOptions.cs

@ -1,82 +0,0 @@
// <copyright file="PngEncoderOptions.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 Quantizers;
/// <summary>
/// Encapsulates the options for the <see cref="PngEncoder"/>.
/// </summary>
public sealed class PngEncoderOptions : EncoderOptions, IPngEncoderOptions
{
/// <summary>
/// Initializes a new instance of the <see cref="PngEncoderOptions"/> class.
/// </summary>
public PngEncoderOptions()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="PngEncoderOptions"/> class.
/// </summary>
/// <param name="options">The options for the encoder.</param>
private PngEncoderOptions(IEncoderOptions options)
: base(options)
{
}
/// <summary>
/// Gets or sets the quality of output for images.
/// </summary>
public int Quality { get; set; }
/// <summary>
/// Gets or sets the png color type
/// </summary>
public PngColorType PngColorType { get; set; } = PngColorType.RgbWithAlpha;
/// <summary>
/// Gets or sets the compression level 1-9.
/// <remarks>Defaults to 6.</remarks>
/// </summary>
public int CompressionLevel { get; set; } = 6;
/// <summary>
/// Gets or sets the gamma value, that will be written
/// the the stream, when the <see cref="WriteGamma"/> property
/// is set to true. The default value is 2.2F.
/// </summary>
/// <value>The gamma value of the image.</value>
public float Gamma { get; set; } = 2.2F;
/// <summary>
/// Gets or sets quantizer for reducing the color count.
/// </summary>
public IQuantizer Quantizer { get; set; }
/// <summary>
/// Gets or sets the transparency threshold.
/// </summary>
public byte Threshold { get; set; } = 255;
/// <summary>
/// Gets or sets a value indicating whether this instance should write
/// gamma information to the stream. The default value is false.
/// </summary>
public bool WriteGamma { get; set; }
/// <summary>
/// Converts the options to a <see cref="IPngEncoderOptions"/> instance with a
/// cast or by creating a new instance with the specfied options.
/// </summary>
/// <param name="options">The options for the encoder.</param>
/// <returns>The options for the <see cref="PngEncoder"/>.</returns>
internal static IPngEncoderOptions Create(IEncoderOptions options)
{
return options as IPngEncoderOptions ?? new PngEncoderOptions(options);
}
}
}

47
src/ImageSharp/Formats/Png/PngFormat.cs

@ -1,47 +0,0 @@
// <copyright file="PngFormat.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.Collections.Generic;
/// <summary>
/// Encapsulates the means to encode and decode png images.
/// </summary>
public class PngFormat : IImageFormat
{
/// <inheritdoc/>
public string MimeType => "image/png";
/// <inheritdoc/>
public string Extension => "png";
/// <inheritdoc/>
public IEnumerable<string> SupportedExtensions => new string[] { "png" };
/// <inheritdoc/>
public IImageDecoder Decoder => new PngDecoder();
/// <inheritdoc/>
public IImageEncoder Encoder => new PngEncoder();
/// <inheritdoc/>
public int HeaderSize => 8;
/// <inheritdoc/>
public bool IsSupportedFileFormat(byte[] header)
{
return header.Length >= this.HeaderSize &&
header[0] == 0x89 &&
header[1] == 0x50 && // P
header[2] == 0x4E && // N
header[3] == 0x47 && // G
header[4] == 0x0D && // CR
header[5] == 0x0A && // LF
header[6] == 0x1A && // EOF
header[7] == 0x0A; // LF
}
}
}

5
src/ImageSharp/Image/IImage.cs

@ -12,11 +12,6 @@ namespace ImageSharp
/// </summary> /// </summary>
internal interface IImage : IImageBase internal interface IImage : IImageBase
{ {
/// <summary>
/// Gets the currently loaded image format.
/// </summary>
IImageFormat CurrentImageFormat { get; }
/// <summary> /// <summary>
/// Gets the meta data of the image. /// Gets the meta data of the image.
/// </summary> /// </summary>

22
src/ImageSharp/Image/Image.Decode.cs

@ -23,7 +23,7 @@ namespace ImageSharp
/// <param name="stream">The image stream to read the header from.</param> /// <param name="stream">The image stream to read the header from.</param>
/// <param name="config">The configuration.</param> /// <param name="config">The configuration.</param>
/// <returns>The image format or null if none found.</returns> /// <returns>The image format or null if none found.</returns>
private static IImageFormat DiscoverFormat(Stream stream, Configuration config) private static IImageDecoder DiscoverDecoder(Stream stream, Configuration config)
{ {
// This is probably a candidate for making into a public API in the future! // This is probably a candidate for making into a public API in the future!
int maxHeaderSize = config.MaxHeaderSize; int maxHeaderSize = config.MaxHeaderSize;
@ -32,14 +32,14 @@ namespace ImageSharp
return null; return null;
} }
IImageFormat format; IImageDecoder format;
byte[] header = ArrayPool<byte>.Shared.Rent(maxHeaderSize); byte[] header = ArrayPool<byte>.Shared.Rent(maxHeaderSize);
try try
{ {
long startPosition = stream.Position; long startPosition = stream.Position;
stream.Read(header, 0, maxHeaderSize); stream.Read(header, 0, maxHeaderSize);
stream.Position = startPosition; stream.Position = startPosition;
format = config.ImageFormats.FirstOrDefault(x => x.IsSupportedFileFormat(header)); format = config.ImageDecoders.LastOrDefault(x => x.IsSupportedFileFormat(header)); // we should use last in case user has registerd a new one with their own settings
} }
finally finally
{ {
@ -49,28 +49,28 @@ namespace ImageSharp
return format; return format;
} }
#pragma warning disable SA1008 // Opening parenthesis must be spaced correctly
/// <summary> /// <summary>
/// Decodes the image stream to the current image. /// Decodes the image stream to the current image.
/// </summary> /// </summary>
/// <param name="stream">The stream.</param> /// <param name="stream">The stream.</param>
/// <param name="options">The options for the decoder.</param>
/// <param name="config">the configuration.</param> /// <param name="config">the configuration.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns> /// <returns>
/// A new <see cref="Image{TPixel}"/>. /// A new <see cref="Image{TPixel}"/>.
/// </returns> /// </returns>
private static Image<TPixel> Decode<TPixel>(Stream stream, IDecoderOptions options, Configuration config) private static (Image<TPixel> img, IImageDecoder decoder) Decode<TPixel>(Stream stream, Configuration config)
#pragma warning restore SA1008 // Opening parenthesis must be spaced correctly
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
IImageFormat format = DiscoverFormat(stream, config); IImageDecoder decoder = DiscoverDecoder(stream, config);
if (format == null) if (decoder == null)
{ {
return null; return (null, null);
} }
Image<TPixel> img = format.Decoder.Decode<TPixel>(config, stream, options); Image<TPixel> img = decoder.Decode<TPixel>(config, stream);
img.CurrentImageFormat = format; return (img, decoder);
return img;
} }
} }
} }

66
src/ImageSharp/Image/Image.FromBytes.cs

@ -20,15 +20,15 @@ namespace ImageSharp
/// </summary> /// </summary>
/// <param name="data">The byte array containing image data.</param> /// <param name="data">The byte array containing image data.</param>
/// <returns>A new <see cref="Image{Rgba32}"/>.</returns> /// <returns>A new <see cref="Image{Rgba32}"/>.</returns>
public static Image<Rgba32> Load(byte[] data) => Load<Rgba32>(null, data, null); public static Image<Rgba32> Load(byte[] data) => Load<Rgba32>(null, data);
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image{Rgba32}"/> class from the given byte array. /// Create a new instance of the <see cref="Image{Rgba32}"/> class from the given byte array.
/// </summary> /// </summary>
/// <param name="data">The byte array containing image data.</param> /// <param name="data">The byte array containing image data.</param>
/// <param name="options">The options for the decoder.</param> /// <param name="mimeType">the mime type of the decoded image.</param>
/// <returns>A new <see cref="Image{Rgba32}"/>.</returns> /// <returns>A new <see cref="Image{Rgba32}"/>.</returns>
public static Image<Rgba32> Load(byte[] data, IDecoderOptions options) => Load<Rgba32>(null, data, options); public static Image<Rgba32> Load(byte[] data, out string mimeType) => Load<Rgba32>(null, data, out mimeType);
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given byte array. /// Create a new instance of the <see cref="Image{TPixel}"/> class from the given byte array.
@ -36,33 +36,33 @@ namespace ImageSharp
/// <param name="config">The config for the decoder.</param> /// <param name="config">The config for the decoder.</param>
/// <param name="data">The byte array containing image data.</param> /// <param name="data">The byte array containing image data.</param>
/// <returns>A new <see cref="Image{Rgba32}"/>.</returns> /// <returns>A new <see cref="Image{Rgba32}"/>.</returns>
public static Image<Rgba32> Load(Configuration config, byte[] data) => Load<Rgba32>(config, data, null); public static Image<Rgba32> Load(Configuration config, byte[] data) => Load<Rgba32>(config, data);
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image{Rgba32}"/> class from the given byte array. /// Create a new instance of the <see cref="Image{TPixel}"/> class from the given byte array.
/// </summary> /// </summary>
/// <param name="config">The config for the decoder.</param>
/// <param name="data">The byte array containing image data.</param> /// <param name="data">The byte array containing image data.</param>
/// <param name="decoder">The decoder.</param> /// <param name="mimeType">the mime type of the decoded image.</param>
/// <returns>A new <see cref="Image{Rgba32}"/>.</returns> /// <returns>A new <see cref="Image{Rgba32}"/>.</returns>
public static Image<Rgba32> Load(byte[] data, IImageDecoder decoder) => Load<Rgba32>(data, decoder, null); public static Image<Rgba32> Load(Configuration config, byte[] data, out string mimeType) => Load<Rgba32>(config, data, out mimeType);
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given byte array. /// Create a new instance of the <see cref="Image{Rgba32}"/> class from the given byte array.
/// </summary> /// </summary>
/// <param name="config">The configuration options.</param>
/// <param name="data">The byte array containing image data.</param> /// <param name="data">The byte array containing image data.</param>
/// <param name="options">The options for the decoder.</param> /// <param name="decoder">The decoder.</param>
/// <returns>A new <see cref="Image{Rgba32}"/>.</returns> /// <returns>A new <see cref="Image{Rgba32}"/>.</returns>
public static Image<Rgba32> Load(Configuration config, byte[] data, IDecoderOptions options) => Load<Rgba32>(config, data, options); public static Image<Rgba32> Load(byte[] data, IImageDecoder decoder) => Load<Rgba32>(data, decoder);
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given byte array. /// Create a new instance of the <see cref="Image{Rgba32}"/> class from the given byte array.
/// </summary> /// </summary>
/// <param name="config">The config for the decoder.</param>
/// <param name="data">The byte array containing image data.</param> /// <param name="data">The byte array containing image data.</param>
/// <param name="decoder">The decoder.</param> /// <param name="decoder">The decoder.</param>
/// <param name="options">The options for the decoder.</param>
/// <returns>A new <see cref="Image{Rgba32}"/>.</returns> /// <returns>A new <see cref="Image{Rgba32}"/>.</returns>
public static Image<Rgba32> Load(byte[] data, IImageDecoder decoder, IDecoderOptions options) => Load<Rgba32>(data, decoder, options); public static Image<Rgba32> Load(Configuration config, byte[] data, IImageDecoder decoder) => Load<Rgba32>(config, data, decoder);
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given byte array. /// Create a new instance of the <see cref="Image{TPixel}"/> class from the given byte array.
@ -73,79 +73,85 @@ namespace ImageSharp
public static Image<TPixel> Load<TPixel>(byte[] data) public static Image<TPixel> Load<TPixel>(byte[] data)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
return Load<TPixel>(null, data, null); return Load<TPixel>(null, data);
} }
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given byte array. /// Create a new instance of the <see cref="Image{TPixel}"/> class from the given byte array.
/// </summary> /// </summary>
/// <param name="data">The byte array containing image data.</param> /// <param name="data">The byte array containing image data.</param>
/// <param name="options">The options for the decoder.</param> /// <param name="mimeType">the mime type of the decoded image.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(byte[] data, IDecoderOptions options) public static Image<TPixel> Load<TPixel>(byte[] data, out string mimeType)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
return Load<TPixel>(null, data, options); return Load<TPixel>(null, data, out mimeType);
} }
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given byte array. /// Create a new instance of the <see cref="Image{TPixel}"/> class from the given byte array.
/// </summary> /// </summary>
/// <param name="config">The config for the decoder.</param> /// <param name="config">The configuration options.</param>
/// <param name="data">The byte array containing image data.</param> /// <param name="data">The byte array containing image data.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(Configuration config, byte[] data) public static Image<TPixel> Load<TPixel>(Configuration config, byte[] data)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
return Load<TPixel>(config, data, null); using (MemoryStream ms = new MemoryStream(data))
{
return Load<TPixel>(config, ms);
}
} }
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given byte array. /// Create a new instance of the <see cref="Image{TPixel}"/> class from the given byte array.
/// </summary> /// </summary>
/// <param name="config">The configuration options.</param>
/// <param name="data">The byte array containing image data.</param> /// <param name="data">The byte array containing image data.</param>
/// <param name="decoder">The decoder.</param> /// <param name="mimeType">the mime type of the decoded image.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(byte[] data, IImageDecoder decoder) public static Image<TPixel> Load<TPixel>(Configuration config, byte[] data, out string mimeType)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
return Load<TPixel>(data, decoder, null); using (MemoryStream ms = new MemoryStream(data))
{
return Load<TPixel>(config, ms, out mimeType);
}
} }
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given byte array. /// Create a new instance of the <see cref="Image{TPixel}"/> class from the given byte array.
/// </summary> /// </summary>
/// <param name="config">The configuration options.</param>
/// <param name="data">The byte array containing image data.</param> /// <param name="data">The byte array containing image data.</param>
/// <param name="options">The options for the decoder.</param> /// <param name="decoder">The decoder.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(Configuration config, byte[] data, IDecoderOptions options) public static Image<TPixel> Load<TPixel>(byte[] data, IImageDecoder decoder)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
using (MemoryStream ms = new MemoryStream(data)) using (MemoryStream ms = new MemoryStream(data))
{ {
return Load<TPixel>(config, ms, options); return Load<TPixel>(ms, decoder);
} }
} }
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given byte array. /// Create a new instance of the <see cref="Image{TPixel}"/> class from the given byte array.
/// </summary> /// </summary>
/// <param name="config">The Configuration.</param>
/// <param name="data">The byte array containing image data.</param> /// <param name="data">The byte array containing image data.</param>
/// <param name="decoder">The decoder.</param> /// <param name="decoder">The decoder.</param>
/// <param name="options">The options for the decoder.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(byte[] data, IImageDecoder decoder, IDecoderOptions options) public static Image<TPixel> Load<TPixel>(Configuration config, byte[] data, IImageDecoder decoder)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
using (MemoryStream ms = new MemoryStream(data)) using (MemoryStream ms = new MemoryStream(data))
{ {
return Load<TPixel>(ms, decoder, options); return Load<TPixel>(config, ms, decoder);
} }
} }
} }

66
src/ImageSharp/Image/Image.FromFile.cs

@ -30,12 +30,12 @@ namespace ImageSharp
/// Create a new instance of the <see cref="Image{Rgba32}"/> class from the given file. /// Create a new instance of the <see cref="Image{Rgba32}"/> class from the given file.
/// </summary> /// </summary>
/// <param name="path">The file path to the image.</param> /// <param name="path">The file path to the image.</param>
/// <param name="options">The options for the decoder.</param> /// <param name="mimeType">the mime type of the decoded image.</param>
/// <exception cref="NotSupportedException"> /// <exception cref="NotSupportedException">
/// Thrown if the stream is not readable nor seekable. /// Thrown if the stream is not readable nor seekable.
/// </exception> /// </exception>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <returns>A new <see cref="Image{Rgba32}"/>.</returns>
public static Image<Rgba32> Load(string path, IDecoderOptions options) => Load<Rgba32>(path, options); public static Image<Rgba32> Load(string path, out string mimeType) => Load<Rgba32>(path, out mimeType);
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image{Rgba32}"/> class from the given file. /// Create a new instance of the <see cref="Image{Rgba32}"/> class from the given file.
@ -51,37 +51,37 @@ namespace ImageSharp
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image{Rgba32}"/> class from the given file. /// Create a new instance of the <see cref="Image{Rgba32}"/> class from the given file.
/// </summary> /// </summary>
/// <param name="config">The config for the decoder.</param>
/// <param name="path">The file path to the image.</param> /// <param name="path">The file path to the image.</param>
/// <param name="decoder">The decoder.</param> /// <param name="mimeType">the mime type of the decoded image.</param>
/// <exception cref="NotSupportedException"> /// <exception cref="NotSupportedException">
/// Thrown if the stream is not readable nor seekable. /// Thrown if the stream is not readable nor seekable.
/// </exception> /// </exception>
/// <returns>A new <see cref="Image{Rgba32}"/>.</returns> /// <returns>A new <see cref="Image{Rgba32}"/>.</returns>
public static Image<Rgba32> Load(string path, IImageDecoder decoder) => Load<Rgba32>(path, decoder); public static Image<Rgba32> Load(Configuration config, string path, out string mimeType) => Load<Rgba32>(config, path, out mimeType);
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image{Rgba32}"/> class from the given file. /// Create a new instance of the <see cref="Image{Rgba32}"/> class from the given file.
/// </summary> /// </summary>
/// <param name="config">The configuration options.</param> /// <param name="config">The Configuration.</param>
/// <param name="path">The file path to the image.</param> /// <param name="path">The file path to the image.</param>
/// <param name="options">The options for the decoder.</param> /// <param name="decoder">The decoder.</param>
/// <exception cref="NotSupportedException"> /// <exception cref="NotSupportedException">
/// Thrown if the stream is not readable nor seekable. /// Thrown if the stream is not readable nor seekable.
/// </exception> /// </exception>
/// <returns>A new <see cref="Image{Rgba32}"/>.</returns> /// <returns>A new <see cref="Image{Rgba32}"/>.</returns>
public static Image<Rgba32> Load(Configuration config, string path, IDecoderOptions options) => Load<Rgba32>(config, path, options); public static Image<Rgba32> Load(Configuration config, string path, IImageDecoder decoder) => Load<Rgba32>(config, path, decoder);
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image{Rgba32}"/> class from the given file. /// Create a new instance of the <see cref="Image{Rgba32}"/> class from the given file.
/// </summary> /// </summary>
/// <param name="path">The file path to the image.</param> /// <param name="path">The file path to the image.</param>
/// <param name="decoder">The decoder.</param> /// <param name="decoder">The decoder.</param>
/// <param name="options">The options for the decoder.</param>
/// <exception cref="NotSupportedException"> /// <exception cref="NotSupportedException">
/// Thrown if the stream is not readable nor seekable. /// Thrown if the stream is not readable nor seekable.
/// </exception> /// </exception>
/// <returns>A new <see cref="Image{Rgba32}"/>.</returns> /// <returns>A new <see cref="Image{Rgba32}"/>.</returns>
public static Image<Rgba32> Load(string path, IImageDecoder decoder, IDecoderOptions options) => Load<Rgba32>(path, decoder, options); public static Image<Rgba32> Load(string path, IImageDecoder decoder) => Load<Rgba32>(path, decoder);
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given file. /// Create a new instance of the <see cref="Image{TPixel}"/> class from the given file.
@ -95,29 +95,29 @@ namespace ImageSharp
public static Image<TPixel> Load<TPixel>(string path) public static Image<TPixel> Load<TPixel>(string path)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
return Load<TPixel>(null, path, null); return Load<TPixel>(null, path);
} }
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given file. /// Create a new instance of the <see cref="Image{TPixel}"/> class from the given file.
/// </summary> /// </summary>
/// <param name="path">The file path to the image.</param> /// <param name="path">The file path to the image.</param>
/// <param name="options">The options for the decoder.</param> /// <param name="mimeType">the mime type of the decoded image.</param>
/// <exception cref="NotSupportedException"> /// <exception cref="NotSupportedException">
/// Thrown if the stream is not readable nor seekable. /// Thrown if the stream is not readable nor seekable.
/// </exception> /// </exception>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(string path, IDecoderOptions options) public static Image<TPixel> Load<TPixel>(string path, out string mimeType)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
return Load<TPixel>(null, path, options); return Load<TPixel>(null, path, out mimeType);
} }
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given file. /// Create a new instance of the <see cref="Image{TPixel}"/> class from the given file.
/// </summary> /// </summary>
/// <param name="config">The config for the decoder.</param> /// <param name="config">The configuration options.</param>
/// <param name="path">The file path to the image.</param> /// <param name="path">The file path to the image.</param>
/// <exception cref="NotSupportedException"> /// <exception cref="NotSupportedException">
/// Thrown if the stream is not readable nor seekable. /// Thrown if the stream is not readable nor seekable.
@ -127,64 +127,68 @@ namespace ImageSharp
public static Image<TPixel> Load<TPixel>(Configuration config, string path) public static Image<TPixel> Load<TPixel>(Configuration config, string path)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
return Load<TPixel>(config, path, null); config = config ?? Configuration.Default;
using (Stream s = config.FileSystem.OpenRead(path))
{
return Load<TPixel>(config, s);
}
} }
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given file. /// Create a new instance of the <see cref="Image{TPixel}"/> class from the given file.
/// </summary> /// </summary>
/// <param name="config">The configuration options.</param>
/// <param name="path">The file path to the image.</param> /// <param name="path">The file path to the image.</param>
/// <param name="decoder">The decoder.</param> /// <param name="mimeType">the mime type of the decoded image.</param>
/// <exception cref="NotSupportedException"> /// <exception cref="NotSupportedException">
/// Thrown if the stream is not readable nor seekable. /// Thrown if the stream is not readable nor seekable.
/// </exception> /// </exception>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(string path, IImageDecoder decoder) public static Image<TPixel> Load<TPixel>(Configuration config, string path, out string mimeType)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
return Load<TPixel>(path, decoder, null); config = config ?? Configuration.Default;
using (Stream s = config.FileSystem.OpenRead(path))
{
return Load<TPixel>(config, s, out mimeType);
}
} }
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given file. /// Create a new instance of the <see cref="Image{TPixel}"/> class from the given file.
/// </summary> /// </summary>
/// <param name="config">The configuration options.</param>
/// <param name="path">The file path to the image.</param> /// <param name="path">The file path to the image.</param>
/// <param name="options">The options for the decoder.</param> /// <param name="decoder">The decoder.</param>
/// <exception cref="NotSupportedException"> /// <exception cref="NotSupportedException">
/// Thrown if the stream is not readable nor seekable. /// Thrown if the stream is not readable nor seekable.
/// </exception> /// </exception>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(Configuration config, string path, IDecoderOptions options) public static Image<TPixel> Load<TPixel>(string path, IImageDecoder decoder)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
config = config ?? Configuration.Default; return Load<TPixel>(null, path, decoder);
using (Stream s = config.FileSystem.OpenRead(path))
{
return Load<TPixel>(config, s, options);
}
} }
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given file. /// Create a new instance of the <see cref="Image{TPixel}"/> class from the given file.
/// </summary> /// </summary>
/// <param name="config">The Configuration.</param>
/// <param name="path">The file path to the image.</param> /// <param name="path">The file path to the image.</param>
/// <param name="decoder">The decoder.</param> /// <param name="decoder">The decoder.</param>
/// <param name="options">The options for the decoder.</param>
/// <exception cref="NotSupportedException"> /// <exception cref="NotSupportedException">
/// Thrown if the stream is not readable nor seekable. /// Thrown if the stream is not readable nor seekable.
/// </exception> /// </exception>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(string path, IImageDecoder decoder, IDecoderOptions options) public static Image<TPixel> Load<TPixel>(Configuration config, string path, IImageDecoder decoder)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
Configuration config = Configuration.Default; config = config ?? Configuration.Default;
using (Stream s = config.FileSystem.OpenRead(path)) using (Stream s = config.FileSystem.OpenRead(path))
{ {
return Load<TPixel>(s, decoder, options); return Load<TPixel>(config, s, decoder);
} }
} }
} }

60
src/ImageSharp/Image/Image.FromStream.cs

@ -7,6 +7,7 @@ namespace ImageSharp
{ {
using System; using System;
using System.IO; using System.IO;
using System.Linq;
using System.Text; using System.Text;
using Formats; using Formats;
@ -21,22 +22,22 @@ namespace ImageSharp
/// Create a new instance of the <see cref="Image{Rgba32}"/> class from the given stream. /// Create a new instance of the <see cref="Image{Rgba32}"/> class from the given stream.
/// </summary> /// </summary>
/// <param name="stream">The stream containing image information.</param> /// <param name="stream">The stream containing image information.</param>
/// <param name="mimeType">the mime type of the decoded image.</param>
/// <exception cref="NotSupportedException"> /// <exception cref="NotSupportedException">
/// Thrown if the stream is not readable nor seekable. /// Thrown if the stream is not readable nor seekable.
/// </exception> /// </exception>
/// <returns>A new <see cref="Image{Rgba32}"/>.</returns>> /// <returns>A new <see cref="Image{Rgba32}"/>.</returns>>
public static Image<Rgba32> Load(Stream stream) => Load<Rgba32>(stream); public static Image<Rgba32> Load(Stream stream, out string mimeType) => Load<Rgba32>(stream, out mimeType);
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image{Rgba32}"/> class from the given stream. /// Create a new instance of the <see cref="Image{Rgba32}"/> class from the given stream.
/// </summary> /// </summary>
/// <param name="stream">The stream containing image information.</param> /// <param name="stream">The stream containing image information.</param>
/// <param name="options">The options for the decoder.</param>
/// <exception cref="NotSupportedException"> /// <exception cref="NotSupportedException">
/// Thrown if the stream is not readable nor seekable. /// Thrown if the stream is not readable nor seekable.
/// </exception> /// </exception>
/// <returns>A new <see cref="Image{Rgba32}"/>.</returns>> /// <returns>A new <see cref="Image{Rgba32}"/>.</returns>>
public static Image<Rgba32> Load(Stream stream, IDecoderOptions options) => Load<Rgba32>(stream, options); public static Image<Rgba32> Load(Stream stream) => Load<Rgba32>(stream);
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image{Rgba32}"/> class from the given stream. /// Create a new instance of the <see cref="Image{Rgba32}"/> class from the given stream.
@ -63,14 +64,14 @@ namespace ImageSharp
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image{Rgba32}"/> class from the given stream. /// Create a new instance of the <see cref="Image{Rgba32}"/> class from the given stream.
/// </summary> /// </summary>
/// <param name="config">The config for the decoder.</param>
/// <param name="stream">The stream containing image information.</param> /// <param name="stream">The stream containing image information.</param>
/// <param name="decoder">The decoder.</param> /// <param name="mimeType">the mime type of the decoded image.</param>
/// <param name="options">The options for the decoder.</param>
/// <exception cref="NotSupportedException"> /// <exception cref="NotSupportedException">
/// Thrown if the stream is not readable nor seekable. /// Thrown if the stream is not readable nor seekable.
/// </exception> /// </exception>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>> /// <returns>A new <see cref="Image{Rgba32}"/>.</returns>>
public static Image<Rgba32> Load(Stream stream, IImageDecoder decoder, IDecoderOptions options) => Load<Rgba32>(stream, decoder, options); public static Image<Rgba32> Load(Configuration config, Stream stream, out string mimeType) => Load<Rgba32>(config, stream, out mimeType);
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given stream. /// Create a new instance of the <see cref="Image{TPixel}"/> class from the given stream.
@ -84,44 +85,45 @@ namespace ImageSharp
public static Image<TPixel> Load<TPixel>(Stream stream) public static Image<TPixel> Load<TPixel>(Stream stream)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
return Load<TPixel>(null, stream, null); return Load<TPixel>(null, stream);
} }
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given stream. /// Create a new instance of the <see cref="Image{TPixel}"/> class from the given stream.
/// </summary> /// </summary>
/// <param name="stream">The stream containing image information.</param> /// <param name="stream">The stream containing image information.</param>
/// <param name="options">The options for the decoder.</param> /// <param name="mimeType">the mime type of the decoded image.</param>
/// <exception cref="NotSupportedException"> /// <exception cref="NotSupportedException">
/// Thrown if the stream is not readable nor seekable. /// Thrown if the stream is not readable nor seekable.
/// </exception> /// </exception>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>>
public static Image<TPixel> Load<TPixel>(Stream stream, IDecoderOptions options) public static Image<TPixel> Load<TPixel>(Stream stream, out string mimeType)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
return Load<TPixel>(null, stream, options); return Load<TPixel>(null, stream, out mimeType);
} }
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given stream. /// Create a new instance of the <see cref="Image{TPixel}"/> class from the given stream.
/// </summary> /// </summary>
/// <param name="config">The config for the decoder.</param>
/// <param name="stream">The stream containing image information.</param> /// <param name="stream">The stream containing image information.</param>
/// <param name="decoder">The decoder.</param>
/// <exception cref="NotSupportedException"> /// <exception cref="NotSupportedException">
/// Thrown if the stream is not readable nor seekable. /// Thrown if the stream is not readable nor seekable.
/// </exception> /// </exception>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>>
public static Image<TPixel> Load<TPixel>(Configuration config, Stream stream) public static Image<TPixel> Load<TPixel>(Stream stream, IImageDecoder decoder)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
return Load<TPixel>(config, stream, null); return WithSeekableStream(stream, s => decoder.Decode<TPixel>(Configuration.Default, s));
} }
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given stream. /// Create a new instance of the <see cref="Image{TPixel}"/> class from the given stream.
/// </summary> /// </summary>
/// <param name="config">The Configuration.</param>
/// <param name="stream">The stream containing image information.</param> /// <param name="stream">The stream containing image information.</param>
/// <param name="decoder">The decoder.</param> /// <param name="decoder">The decoder.</param>
/// <exception cref="NotSupportedException"> /// <exception cref="NotSupportedException">
@ -129,27 +131,26 @@ namespace ImageSharp
/// </exception> /// </exception>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>>
public static Image<TPixel> Load<TPixel>(Stream stream, IImageDecoder decoder) public static Image<TPixel> Load<TPixel>(Configuration config, Stream stream, IImageDecoder decoder)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
return Load<TPixel>(stream, decoder, null); return WithSeekableStream(stream, s => decoder.Decode<TPixel>(config, s));
} }
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given stream. /// Create a new instance of the <see cref="Image{TPixel}"/> class from the given stream.
/// </summary> /// </summary>
/// <param name="config">The configuration options.</param>
/// <param name="stream">The stream containing image information.</param> /// <param name="stream">The stream containing image information.</param>
/// <param name="decoder">The decoder.</param>
/// <param name="options">The options for the decoder.</param>
/// <exception cref="NotSupportedException"> /// <exception cref="NotSupportedException">
/// Thrown if the stream is not readable nor seekable. /// Thrown if the stream is not readable nor seekable.
/// </exception> /// </exception>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>>
public static Image<TPixel> Load<TPixel>(Stream stream, IImageDecoder decoder, IDecoderOptions options) public static Image<TPixel> Load<TPixel>(Configuration config, Stream stream)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
return WithSeekableStream(stream, s => decoder.Decode<TPixel>(Configuration.Default, s, options)); return Load<TPixel>(config, stream, out var _);
} }
/// <summary> /// <summary>
@ -157,27 +158,30 @@ namespace ImageSharp
/// </summary> /// </summary>
/// <param name="config">The configuration options.</param> /// <param name="config">The configuration options.</param>
/// <param name="stream">The stream containing image information.</param> /// <param name="stream">The stream containing image information.</param>
/// <param name="options">The options for the decoder.</param> /// <param name="mimeType">the mime type of the decoded image.</param>
/// <exception cref="NotSupportedException"> /// <exception cref="NotSupportedException">
/// Thrown if the stream is not readable nor seekable. /// Thrown if the stream is not readable nor seekable.
/// </exception> /// </exception>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>>
public static Image<TPixel> Load<TPixel>(Configuration config, Stream stream, IDecoderOptions options) public static Image<TPixel> Load<TPixel>(Configuration config, Stream stream, out string mimeType)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
config = config ?? Configuration.Default; config = config ?? Configuration.Default;
Image<TPixel> img = WithSeekableStream(stream, s => Decode<TPixel>(s, options, config)); mimeType = null;
(Image<TPixel> img, IImageDecoder decoder) data = WithSeekableStream(stream, s => Decode<TPixel>(s, config));
mimeType = data.decoder?.MimeTypes.FirstOrDefault();
if (img != null) if (data.img != null)
{ {
return img; return data.img;
} }
StringBuilder stringBuilder = new StringBuilder(); StringBuilder stringBuilder = new StringBuilder();
stringBuilder.AppendLine("Image cannot be loaded. Available formats:"); stringBuilder.AppendLine("Image cannot be loaded. Available decoders:");
foreach (IImageFormat format in config.ImageFormats) foreach (IImageDecoder format in config.ImageDecoders)
{ {
stringBuilder.AppendLine("-" + format); stringBuilder.AppendLine("-" + format);
} }

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

@ -11,6 +11,7 @@ namespace ImageSharp
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Numerics; using System.Numerics;
using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Formats; using Formats;
@ -94,13 +95,7 @@ namespace ImageSharp
internal Image(Configuration configuration, int width, int height, ImageMetaData metadata) internal Image(Configuration configuration, int width, int height, ImageMetaData metadata)
: base(configuration, width, height) : base(configuration, width, height)
{ {
if (!this.Configuration.ImageFormats.Any())
{
throw new InvalidOperationException("No image formats have been configured.");
}
this.MetaData = metadata ?? new ImageMetaData(); this.MetaData = metadata ?? new ImageMetaData();
this.CurrentImageFormat = this.Configuration.ImageFormats.First();
} }
/// <summary> /// <summary>
@ -138,11 +133,6 @@ namespace ImageSharp
/// <value>The list of frame images.</value> /// <value>The list of frame images.</value>
public IList<ImageFrame<TPixel>> Frames { get; } = new List<ImageFrame<TPixel>>(); public IList<ImageFrame<TPixel>> Frames { get; } = new List<ImageFrame<TPixel>>();
/// <summary>
/// Gets the currently loaded image format.
/// </summary>
public IImageFormat CurrentImageFormat { get; internal set; }
/// <summary> /// <summary>
/// Applies the processor to the image. /// Applies the processor to the image.
/// </summary> /// </summary>
@ -162,48 +152,28 @@ namespace ImageSharp
/// Saves the image to the given stream using the currently loaded image format. /// Saves the image to the given stream using the currently loaded image format.
/// </summary> /// </summary>
/// <param name="stream">The stream to save the image to.</param> /// <param name="stream">The stream to save the image to.</param>
/// <param name="mimeType">The mime type to save the image to.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception> /// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <returns>The <see cref="Image{TPixel}"/></returns> /// <returns>The <see cref="Image{TPixel}"/></returns>
public Image<TPixel> Save(Stream stream) public Image<TPixel> Save(Stream stream, string mimeType)
{ {
return this.Save(stream, (IEncoderOptions)null); Guard.NotNullOrEmpty(mimeType, nameof(mimeType));
} IImageEncoder encoder = this.Configuration.ImageEncoders?.LastOrDefault(x => x?.MimeTypes?.Contains(mimeType, StringComparer.OrdinalIgnoreCase) == true);
/// <summary> if (encoder == null)
/// Saves the image to the given stream using the currently loaded image format. {
/// </summary> StringBuilder stringBuilder = new StringBuilder();
/// <param name="stream">The stream to save the image to.</param> stringBuilder.AppendLine("Can't find encoder for provided mime type. Available encoded:");
/// <param name="options">The options for the encoder.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <returns>The <see cref="Image{TPixel}"/></returns>
public Image<TPixel> Save(Stream stream, IEncoderOptions options)
{
return this.Save(stream, this.CurrentImageFormat?.Encoder, options);
}
/// <summary> foreach (IImageEncoder format in this.Configuration.ImageEncoders)
/// Saves the image to the given stream using the given image format. {
/// </summary> stringBuilder.AppendLine("-" + format);
/// <param name="stream">The stream to save the image to.</param> }
/// <param name="format">The format to save the image as.</param>
/// <returns>The <see cref="Image{TPixel}"/></returns>
public Image<TPixel> Save(Stream stream, IImageFormat format)
{
return this.Save(stream, format, null);
}
/// <summary> throw new NotSupportedException(stringBuilder.ToString());
/// Saves the image to the given stream using the given image format. }
/// </summary>
/// <param name="stream">The stream to save the image to.</param>
/// <param name="format">The format to save the image as.</param>
/// <param name="options">The options for the encoder.</param>
/// <returns>The <see cref="Image{TPixel}"/></returns>
public Image<TPixel> Save(Stream stream, IImageFormat format, IEncoderOptions options)
{
Guard.NotNull(format, nameof(format));
return this.Save(stream, format.Encoder, options); return this.Save(stream, encoder);
} }
/// <summary> /// <summary>
@ -216,26 +186,11 @@ namespace ImageSharp
/// The <see cref="Image{TPixel}"/>. /// The <see cref="Image{TPixel}"/>.
/// </returns> /// </returns>
public Image<TPixel> Save(Stream stream, IImageEncoder encoder) public Image<TPixel> Save(Stream stream, IImageEncoder encoder)
{
return this.Save(stream, encoder, null);
}
/// <summary>
/// Saves the image to the given stream using the given image encoder.
/// </summary>
/// <param name="stream">The stream to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param>
/// <param name="options">The options for the encoder.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream or encoder is null.</exception>
/// <returns>
/// The <see cref="Image{TPixel}"/>.
/// </returns>
public Image<TPixel> Save(Stream stream, IImageEncoder encoder, IEncoderOptions options)
{ {
Guard.NotNull(stream, nameof(stream)); Guard.NotNull(stream, nameof(stream));
Guard.NotNull(encoder, nameof(encoder)); Guard.NotNull(encoder, nameof(encoder));
encoder.Encode(this, stream, options); encoder.Encode(this, stream);
return this; return this;
} }
@ -249,52 +204,24 @@ namespace ImageSharp
/// <returns>The <see cref="Image{TPixel}"/></returns> /// <returns>The <see cref="Image{TPixel}"/></returns>
public Image<TPixel> Save(string filePath) public Image<TPixel> Save(string filePath)
{ {
return this.Save(filePath, (IEncoderOptions)null); Guard.NotNullOrEmpty(filePath, nameof(filePath));
}
/// <summary>
/// Saves the image to the given stream using the currently loaded image format.
/// </summary>
/// <param name="filePath">The file path to save the image to.</param>
/// <param name="options">The options for the encoder.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <returns>The <see cref="Image{TPixel}"/></returns>
public Image<TPixel> Save(string filePath, IEncoderOptions options)
{
string ext = Path.GetExtension(filePath).Trim('.'); string ext = Path.GetExtension(filePath).Trim('.');
IImageFormat format = this.Configuration.ImageFormats.SingleOrDefault(f => f.SupportedExtensions.Contains(ext, StringComparer.OrdinalIgnoreCase)); IImageEncoder encoder = this.Configuration.ImageEncoders?.LastOrDefault(x => x?.FileExtensions?.Contains(ext, StringComparer.OrdinalIgnoreCase) == true);
if (format == null) if (encoder == null)
{ {
throw new InvalidOperationException($"No image formats have been registered for the file extension '{ext}'."); StringBuilder stringBuilder = new StringBuilder();
} stringBuilder.AppendLine($"Can't find encoder for file extention '{ext}'. Available encoded:");
return this.Save(filePath, format, options); foreach (IImageEncoder format in this.Configuration.ImageEncoders)
} {
stringBuilder.AppendLine("-" + format);
}
/// <summary> throw new NotSupportedException(stringBuilder.ToString());
/// Saves the image to the given stream using the currently loaded image format. }
/// </summary>
/// <param name="filePath">The file path to save the image to.</param>
/// <param name="format">The format to save the image as.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the format is null.</exception>
/// <returns>The <see cref="Image{TPixel}"/></returns>
public Image<TPixel> Save(string filePath, IImageFormat format)
{
return this.Save(filePath, format, null);
}
/// <summary> return this.Save(filePath, encoder);
/// Saves the image to the given stream using the currently loaded image format.
/// </summary>
/// <param name="filePath">The file path to save the image to.</param>
/// <param name="format">The format to save the image as.</param>
/// <param name="options">The options for the encoder.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the format is null.</exception>
/// <returns>The <see cref="Image{TPixel}"/></returns>
public Image<TPixel> Save(string filePath, IImageFormat format, IEncoderOptions options)
{
Guard.NotNull(format, nameof(format));
return this.Save(filePath, format.Encoder, options);
} }
/// <summary> /// <summary>
@ -305,24 +232,11 @@ namespace ImageSharp
/// <exception cref="System.ArgumentNullException">Thrown if the encoder is null.</exception> /// <exception cref="System.ArgumentNullException">Thrown if the encoder is null.</exception>
/// <returns>The <see cref="Image{TPixel}"/></returns> /// <returns>The <see cref="Image{TPixel}"/></returns>
public Image<TPixel> Save(string filePath, IImageEncoder encoder) public Image<TPixel> Save(string filePath, IImageEncoder encoder)
{
return this.Save(filePath, encoder, null);
}
/// <summary>
/// Saves the image to the given stream using the currently loaded image format.
/// </summary>
/// <param name="filePath">The file path to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param>
/// <param name="options">The options for the encoder.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the encoder is null.</exception>
/// <returns>The <see cref="Image{TPixel}"/></returns>
public Image<TPixel> Save(string filePath, IImageEncoder encoder, IEncoderOptions options)
{ {
Guard.NotNull(encoder, nameof(encoder)); Guard.NotNull(encoder, nameof(encoder));
using (Stream fs = this.Configuration.FileSystem.Create(filePath)) using (Stream fs = this.Configuration.FileSystem.Create(filePath))
{ {
return this.Save(fs, encoder, options); return this.Save(fs, encoder);
} }
} }
#endif #endif
@ -330,21 +244,22 @@ namespace ImageSharp
/// <inheritdoc/> /// <inheritdoc/>
public override string ToString() public override string ToString()
{ {
return $"Image: {this.Width}x{this.Height}"; return $"Image<{typeof(TPixel).Name}>: {this.Width}x{this.Height}";
} }
/// <summary> /// <summary>
/// Returns a Base64 encoded string from the given image. /// Returns a Base64 encoded string from the given image.
/// </summary> /// </summary>
/// <example><see href="data:image/gif;base64,R0lGODlhAQABAIABAEdJRgAAACwAAAAAAQABAAACAkQBAA=="/></example> /// <example><see href="data:image/gif;base64,R0lGODlhAQABAIABAEdJRgAAACwAAAAAAQABAAACAkQBAA=="/></example>
/// <param name="mimeType">The mimeType.</param>
/// <returns>The <see cref="string"/></returns> /// <returns>The <see cref="string"/></returns>
public string ToBase64String() public string ToBase64String(string mimeType)
{ {
using (MemoryStream stream = new MemoryStream()) using (MemoryStream stream = new MemoryStream())
{ {
this.Save(stream); this.Save(stream, mimeType);
stream.Flush(); stream.Flush();
return $"data:{this.CurrentImageFormat.MimeType};base64,{Convert.ToBase64String(stream.ToArray())}"; return $"data:{mimeType};base64,{Convert.ToBase64String(stream.ToArray())}";
} }
} }
@ -417,7 +332,6 @@ namespace ImageSharp
/// </param> /// </param>
private void CopyProperties(IImage other) private void CopyProperties(IImage other)
{ {
this.CurrentImageFormat = other.CurrentImageFormat;
this.MetaData = new ImageMetaData(other.MetaData); this.MetaData = new ImageMetaData(other.MetaData);
} }
} }

1
src/ImageSharp/ImageSharp.csproj

@ -43,6 +43,7 @@
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="4.4.0-preview1-25305-02" /> <PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="4.4.0-preview1-25305-02" />
<PackageReference Include="System.Numerics.Vectors" Version="4.3.0" /> <PackageReference Include="System.Numerics.Vectors" Version="4.3.0" />
<PackageReference Include="System.IO.Compression" Version="4.3.0" /> <PackageReference Include="System.IO.Compression" Version="4.3.0" />
<PackageReference Include="System.ValueTuple" Version="4.4.0-preview1-25305-02" />
</ItemGroup> </ItemGroup>
<PropertyGroup> <PropertyGroup>
<CodeAnalysisRuleSet>..\..\ImageSharp.ruleset</CodeAnalysisRuleSet> <CodeAnalysisRuleSet>..\..\ImageSharp.ruleset</CodeAnalysisRuleSet>

6
src/ImageSharp/MetaData/ImageMetaData.cs

@ -50,7 +50,6 @@ namespace ImageSharp
this.HorizontalResolution = other.HorizontalResolution; this.HorizontalResolution = other.HorizontalResolution;
this.VerticalResolution = other.VerticalResolution; this.VerticalResolution = other.VerticalResolution;
this.Quality = other.Quality;
this.FrameDelay = other.FrameDelay; this.FrameDelay = other.FrameDelay;
this.DisposalMethod = other.DisposalMethod; this.DisposalMethod = other.DisposalMethod;
this.RepeatCount = other.RepeatCount; this.RepeatCount = other.RepeatCount;
@ -127,11 +126,6 @@ namespace ImageSharp
/// <value>A list of image properties.</value> /// <value>A list of image properties.</value>
public IList<ImageProperty> Properties { get; } = new List<ImageProperty>(); public IList<ImageProperty> Properties { get; } = new List<ImageProperty>();
/// <summary>
/// Gets or sets the quality of the image. This affects the output quality of lossy image formats.
/// </summary>
public int Quality { get; set; }
/// <summary> /// <summary>
/// Gets or sets the number of times any animation is repeated. /// Gets or sets the number of times any animation is repeated.
/// <remarks>0 means to repeat indefinitely.</remarks> /// <remarks>0 means to repeat indefinitely.</remarks>

4
tests/ImageSharp.Benchmarks/BenchmarkBase.cs

@ -13,10 +13,6 @@
protected BenchmarkBase() protected BenchmarkBase()
{ {
// Add Image Formats // Add Image Formats
Configuration.Default.AddImageFormat(new JpegFormat());
Configuration.Default.AddImageFormat(new PngFormat());
Configuration.Default.AddImageFormat(new BmpFormat());
Configuration.Default.AddImageFormat(new GifFormat());
} }
} }
} }

12
tests/ImageSharp.Benchmarks/Image/EncodeIndexedPng.cs

@ -53,9 +53,9 @@ namespace ImageSharp.Benchmarks.Image
{ {
using (MemoryStream memoryStream = new MemoryStream()) using (MemoryStream memoryStream = new MemoryStream())
{ {
PngEncoderOptions options = new PngEncoderOptions() { Quantizer = new OctreeQuantizer<Rgba32>(), Quality = 256 }; PngEncoder encoder = new PngEncoder() { Quantizer = new OctreeQuantizer<Rgba32>(), Quality = 256 };
this.bmpCore.SaveAsPng(memoryStream, options); this.bmpCore.SaveAsPng(memoryStream, encoder);
} }
} }
@ -64,7 +64,7 @@ namespace ImageSharp.Benchmarks.Image
{ {
using (MemoryStream memoryStream = new MemoryStream()) using (MemoryStream memoryStream = new MemoryStream())
{ {
PngEncoderOptions options = new PngEncoderOptions { Quantizer = new OctreeQuantizer<Rgba32> { Dither = false }, Quality = 256 }; PngEncoder options = new PngEncoder { Quantizer = new OctreeQuantizer<Rgba32> { Dither = false }, Quality = 256 };
this.bmpCore.SaveAsPng(memoryStream, options); this.bmpCore.SaveAsPng(memoryStream, options);
} }
@ -75,7 +75,7 @@ namespace ImageSharp.Benchmarks.Image
{ {
using (MemoryStream memoryStream = new MemoryStream()) using (MemoryStream memoryStream = new MemoryStream())
{ {
PngEncoderOptions options = new PngEncoderOptions { Quantizer = new PaletteQuantizer<Rgba32>(), Quality = 256 }; PngEncoder options = new PngEncoder { Quantizer = new PaletteQuantizer<Rgba32>(), Quality = 256 };
this.bmpCore.SaveAsPng(memoryStream, options); this.bmpCore.SaveAsPng(memoryStream, options);
} }
@ -86,7 +86,7 @@ namespace ImageSharp.Benchmarks.Image
{ {
using (MemoryStream memoryStream = new MemoryStream()) using (MemoryStream memoryStream = new MemoryStream())
{ {
PngEncoderOptions options = new PngEncoderOptions { Quantizer = new PaletteQuantizer<Rgba32> { Dither = false }, Quality = 256 }; PngEncoder options = new PngEncoder { Quantizer = new PaletteQuantizer<Rgba32> { Dither = false }, Quality = 256 };
this.bmpCore.SaveAsPng(memoryStream, options); this.bmpCore.SaveAsPng(memoryStream, options);
} }
@ -97,7 +97,7 @@ namespace ImageSharp.Benchmarks.Image
{ {
using (MemoryStream memoryStream = new MemoryStream()) using (MemoryStream memoryStream = new MemoryStream())
{ {
PngEncoderOptions options = new PngEncoderOptions() { Quantizer = new WuQuantizer<Rgba32>(), Quality = 256 }; PngEncoder options = new PngEncoder() { Quantizer = new WuQuantizer<Rgba32>(), Quality = 256 };
this.bmpCore.SaveAsPng(memoryStream, options); this.bmpCore.SaveAsPng(memoryStream, options);
} }

2
tests/ImageSharp.Benchmarks/Image/EncodePng.cs

@ -71,7 +71,7 @@ namespace ImageSharp.Benchmarks.Image
new OctreeQuantizer<Rgba32>() new OctreeQuantizer<Rgba32>()
: new PaletteQuantizer<Rgba32>(); : new PaletteQuantizer<Rgba32>();
PngEncoderOptions options = new PngEncoderOptions() { Quantizer = quantizer }; PngEncoder options = new PngEncoder() { Quantizer = quantizer };
this.bmpCore.SaveAsPng(memoryStream, options); this.bmpCore.SaveAsPng(memoryStream, options);
} }
} }

204
tests/ImageSharp.Tests/ConfigurationTests.cs

@ -36,7 +36,8 @@ namespace ImageSharp.Tests
{ {
var configuration = Configuration.CreateDefaultInstance(); var configuration = Configuration.CreateDefaultInstance();
Assert.Equal(4, configuration.ImageFormats.Count); Assert.Equal(4, configuration.ImageDecoders.Count);
Assert.Equal(4, configuration.ImageDecoders.Count);
} }
/// <summary> /// <summary>
@ -73,7 +74,8 @@ namespace ImageSharp.Tests
[Fact] [Fact]
public void TestDefultConfigurationImageFormatsIsNotNull() public void TestDefultConfigurationImageFormatsIsNotNull()
{ {
Assert.True(Configuration.Default.ImageFormats != null); Assert.True(Configuration.Default.ImageDecoders != null);
Assert.True(Configuration.Default.ImageEncoders != null);
} }
/// <summary> /// <summary>
@ -85,163 +87,25 @@ namespace ImageSharp.Tests
{ {
Assert.Throws<ArgumentNullException>(() => Assert.Throws<ArgumentNullException>(() =>
{ {
Configuration.Default.AddImageFormat(null); Configuration.Default.AddImageFormat((IImageEncoder)null);
}); });
}
/// <summary>
/// Tests the <see cref="M:Configuration.AddImageFormat"/> method throws an exception
/// when the encoder is null.
/// </summary>
[Fact]
public void TestAddImageFormatThrowsWithNullEncoder()
{
var format = new TestFormat { Encoder = null };
Assert.Throws<ArgumentNullException>(() =>
{
Configuration.Default.AddImageFormat(format);
});
}
/// <summary>
/// Tests the <see cref="M:Configuration.AddImageFormat"/> method throws an exception
/// when the decoder is null.
/// </summary>
[Fact]
public void TestAddImageFormatThrowsWithNullDecoder()
{
var format = new TestFormat { Decoder = null };
Assert.Throws<ArgumentNullException>(() =>
{
Configuration.Default.AddImageFormat(format);
});
}
/// <summary>
/// Tests the <see cref="M:Configuration.AddImageFormat"/> method throws an exception
/// when the mime type is null or an empty string.
/// </summary>
[Fact]
public void TestAddImageFormatThrowsWithNullOrEmptyMimeType()
{
var format = new TestFormat { MimeType = null };
Assert.Throws<ArgumentNullException>(() => Assert.Throws<ArgumentNullException>(() =>
{ {
Configuration.Default.AddImageFormat(format); Configuration.Default.AddImageFormat((IImageDecoder)null);
});
format = new TestFormat { MimeType = string.Empty };
Assert.Throws<ArgumentException>(() =>
{
Configuration.Default.AddImageFormat(format);
}); });
} }
/// <summary>
/// Tests the <see cref="M:Configuration.AddImageFormat"/> method throws an exception
/// when the extension is null or an empty string.
/// </summary>
[Fact]
public void TestAddImageFormatThrowsWithNullOrEmptyExtension()
{
var format = new TestFormat { Extension = null };
Assert.Throws<ArgumentNullException>(() =>
{
Configuration.Default.AddImageFormat(format);
});
format = new TestFormat { Extension = string.Empty };
Assert.Throws<ArgumentException>(() =>
{
Configuration.Default.AddImageFormat(format);
});
}
/// <summary>
/// Tests the <see cref="M:Configuration.AddImageFormat"/> method throws an exception
/// when the supported extensions list is null or empty.
/// </summary>
[Fact]
public void TestAddImageFormatThrowsWenSupportedExtensionsIsNullOrEmpty()
{
var format = new TestFormat { SupportedExtensions = null };
Assert.Throws<ArgumentNullException>(() =>
{
Configuration.Default.AddImageFormat(format);
});
format = new TestFormat { SupportedExtensions = Enumerable.Empty<string>() };
Assert.Throws<ArgumentException>(() =>
{
Configuration.Default.AddImageFormat(format);
});
}
/// <summary>
/// Tests the <see cref="M:Configuration.AddImageFormat"/> method throws an exception
/// when the supported extensions list does not contain the default extension.
/// </summary>
[Fact]
public void TestAddImageFormatThrowsWithoutDefaultExtension()
{
var format = new TestFormat { Extension = "test" };
Assert.Throws<ArgumentException>(() =>
{
Configuration.Default.AddImageFormat(format);
});
}
/// <summary>
/// Tests the <see cref="M:Configuration.AddImageFormat"/> method throws an exception
/// when the supported extensions list contains an empty string.
/// </summary>
[Fact]
public void TestAddImageFormatThrowsWithEmptySupportedExtension()
{
var format = new TestFormat
{
Extension = "test",
SupportedExtensions = new[] { "test", string.Empty }
};
Assert.Throws<ArgumentException>(() =>
{
Configuration.Default.AddImageFormat(format);
});
}
/// <summary>
/// Test that the <see cref="M:Configuration.AddImageFormat"/> method ignores adding duplicate image formats.
/// </summary>
[Fact]
public void TestConfigurationIgnoresDuplicateImageFormats()
{
Configuration.Default.AddImageFormat(new PngFormat());
Configuration.Default.AddImageFormat(new PngFormat());
Assert.True(Configuration.Default.ImageFormats.Count(i => i.GetType() == typeof(PngFormat)) == 1);
}
/// <summary> /// <summary>
/// Test that the default image constructors use default configuration. /// Test that the default image constructors use default configuration.
/// </summary> /// </summary>
[Fact] [Fact]
public void TestImageUsesDefaultConfiguration() public void TestImageUsesDefaultConfiguration()
{ {
Configuration.Default.AddImageFormat(new PngFormat()); Configuration.Default.AddImageFormat(new PngDecoder());
var image = new Image<Rgba32>(1, 1); var image = new Image<Rgba32>(1, 1);
Assert.Equal(image.Configuration.ParallelOptions, Configuration.Default.ParallelOptions); Assert.Equal(image.Configuration.ParallelOptions, Configuration.Default.ParallelOptions);
Assert.Equal(image.Configuration.ImageFormats, Configuration.Default.ImageFormats); Assert.Equal(image.Configuration.ImageDecoders, Configuration.Default.ImageDecoders);
} }
/// <summary> /// <summary>
@ -250,60 +114,12 @@ namespace ImageSharp.Tests
[Fact] [Fact]
public void TestImageCopiesConfiguration() public void TestImageCopiesConfiguration()
{ {
Configuration.Default.AddImageFormat(new PngFormat()); Configuration.Default.AddImageFormat(new PngDecoder());
var image = new Image<Rgba32>(1, 1); var image = new Image<Rgba32>(1, 1);
var image2 = new Image<Rgba32>(image); var image2 = new Image<Rgba32>(image);
Assert.Equal(image2.Configuration.ParallelOptions, image.Configuration.ParallelOptions); Assert.Equal(image2.Configuration.ParallelOptions, image.Configuration.ParallelOptions);
Assert.True(image2.Configuration.ImageFormats.SequenceEqual(image.Configuration.ImageFormats)); Assert.True(image2.Configuration.ImageDecoders.SequenceEqual(image.Configuration.ImageDecoders));
}
/// <summary>
/// A test image format for testing the configuration.
/// </summary>
private class TestFormat : IImageFormat
{
/// <summary>
/// Initializes a new instance of the <see cref="TestFormat"/> class.
/// </summary>
public TestFormat()
{
this.Decoder = new JpegDecoder();
this.Encoder = new JpegEncoder();
this.Extension = "jpg";
this.MimeType = "image/test";
this.SupportedExtensions = new[] { "jpg" };
}
/// <inheritdoc />
public IImageDecoder Decoder { get; set; }
/// <inheritdoc />
public IImageEncoder Encoder { get; set; }
/// <inheritdoc />
public string MimeType { get; set; }
/// <inheritdoc />
public string Extension { get; set; }
/// <inheritdoc />
public IEnumerable<string> SupportedExtensions { get; set; }
/// <inheritdoc />
public int HeaderSize
{
get
{
throw new NotImplementedException();
}
}
/// <inheritdoc />
public bool IsSupportedFileFormat(byte[] header)
{
throw new NotImplementedException();
}
} }
} }
} }

28
tests/ImageSharp.Tests/Drawing/BeziersTests.cs

@ -24,18 +24,15 @@ namespace ImageSharp.Tests.Drawing
string path = this.CreateOutputDirectory("Drawing", "BezierLine"); string path = this.CreateOutputDirectory("Drawing", "BezierLine");
using (Image<Rgba32> image = new Image<Rgba32>(500, 500)) using (Image<Rgba32> image = new Image<Rgba32>(500, 500))
{ {
using (FileStream output = File.OpenWrite($"{path}/Simple.png")) image.BackgroundColor(Rgba32.Blue)
{ .DrawBeziers(Rgba32.HotPink, 5,
image.BackgroundColor(Rgba32.Blue) new[] {
.DrawBeziers(Rgba32.HotPink, 5,
new[] {
new Vector2(10, 400), new Vector2(10, 400),
new Vector2(30, 10), new Vector2(30, 10),
new Vector2(240, 30), new Vector2(240, 30),
new Vector2(300, 400) new Vector2(300, 400)
}) })
.Save(output); .Save($"{path}/Simple.png");
}
using (PixelAccessor<Rgba32> sourcePixels = image.Lock()) using (PixelAccessor<Rgba32> sourcePixels = image.Lock())
{ {
@ -66,19 +63,16 @@ namespace ImageSharp.Tests.Drawing
using (Image<Rgba32> image = new Image<Rgba32>(500, 500)) using (Image<Rgba32> image = new Image<Rgba32>(500, 500))
{ {
using (FileStream output = File.OpenWrite($"{path}/Opacity.png")) image.BackgroundColor(Rgba32.Blue)
{ .DrawBeziers(color,
image.BackgroundColor(Rgba32.Blue) 10,
.DrawBeziers(color, new[] {
10,
new[] {
new Vector2(10, 400), new Vector2(10, 400),
new Vector2(30, 10), new Vector2(30, 10),
new Vector2(240, 30), new Vector2(240, 30),
new Vector2(300, 400) new Vector2(300, 400)
}) })
.Save(output); .Save($"{path}/Opacity.png");
}
//shift background color towards forground color by the opacity amount //shift background color towards forground color by the opacity amount
Rgba32 mergedColor = new Rgba32(Vector4.Lerp(Rgba32.Blue.ToVector4(), Rgba32.HotPink.ToVector4(), 150f / 255f)); Rgba32 mergedColor = new Rgba32(Vector4.Lerp(Rgba32.Blue.ToVector4(), Rgba32.HotPink.ToVector4(), 150f / 255f));

29
tests/ImageSharp.Tests/Drawing/DrawPathTests.cs

@ -34,13 +34,10 @@ namespace ImageSharp.Tests.Drawing
ShapePath p = new ShapePath(linerSegemnt, bazierSegment); ShapePath p = new ShapePath(linerSegemnt, bazierSegment);
using (FileStream output = File.OpenWrite($"{path}/Simple.png")) image
{ .BackgroundColor(Rgba32.Blue)
image .Draw(Rgba32.HotPink, 5, p)
.BackgroundColor(Rgba32.Blue) .Save($"{path}/Simple.png");
.Draw(Rgba32.HotPink, 5, p)
.Save(output);
}
using (PixelAccessor<Rgba32> sourcePixels = image.Lock()) using (PixelAccessor<Rgba32> sourcePixels = image.Lock())
{ {
@ -77,13 +74,10 @@ namespace ImageSharp.Tests.Drawing
using (Image<Rgba32> image = new Image<Rgba32>(500, 500)) using (Image<Rgba32> image = new Image<Rgba32>(500, 500))
{ {
using (FileStream output = File.OpenWrite($"{path}/Opacity.png")) image
{ .BackgroundColor(Rgba32.Blue)
image .Draw(color, 10, p)
.BackgroundColor(Rgba32.Blue) .Save($"{path}/Opacity.png");
.Draw(color, 10, p)
.Save(output);
}
//shift background color towards forground color by the opacity amount //shift background color towards forground color by the opacity amount
Rgba32 mergedColor = new Rgba32(Vector4.Lerp(Rgba32.Blue.ToVector4(), Rgba32.HotPink.ToVector4(), 150f / 255f)); Rgba32 mergedColor = new Rgba32(Vector4.Lerp(Rgba32.Blue.ToVector4(), Rgba32.HotPink.ToVector4(), 150f / 255f));
@ -115,11 +109,8 @@ namespace ImageSharp.Tests.Drawing
image.DrawLines(pen, new Vector2[] { new Vector2(100, 2), new Vector2(-10, i) }); image.DrawLines(pen, new Vector2[] { new Vector2(100, 2), new Vector2(-10, i) });
} }
using (FileStream output = File.OpenWrite($"{path}/ClippedLines.png")) image
{ .Save($"{path}/ClippedLines.png");
image
.Save(output);
}
using (PixelAccessor<Rgba32> sourcePixels = image.Lock()) using (PixelAccessor<Rgba32> sourcePixels = image.Lock())
{ {
Assert.Equal(Rgba32.White, sourcePixels[0, 90]); Assert.Equal(Rgba32.White, sourcePixels[0, 90]);

10
tests/ImageSharp.Tests/Drawing/FillPatternTests.cs

@ -25,10 +25,7 @@ namespace ImageSharp.Tests.Drawing
.Fill(background) .Fill(background)
.Fill(brush); .Fill(brush);
using (FileStream output = File.OpenWrite($"{path}/{name}.png")) image.Save($"{path}/{name}.png");
{
image.Save(output);
}
using (PixelAccessor<Rgba32> sourcePixels = image.Lock()) using (PixelAccessor<Rgba32> sourcePixels = image.Lock())
{ {
@ -54,10 +51,7 @@ namespace ImageSharp.Tests.Drawing
} }
} }
} }
using (FileStream output = File.OpenWrite($"{path}/{name}x4.png")) image.Resize(80, 80).Save($"{path}/{name}x4.png");
{
image.Resize(80, 80).Save(output);
}
} }
} }

34
tests/ImageSharp.Tests/Drawing/FillSolidBrushTests.cs

@ -16,7 +16,7 @@ namespace ImageSharp.Tests.Drawing
using Xunit; using Xunit;
public class FillSolidBrushTests: FileTestBase public class FillSolidBrushTests : FileTestBase
{ {
[Fact] [Fact]
public void ImageShouldBeFloodFilledWithColorOnDefaultBackground() public void ImageShouldBeFloodFilledWithColorOnDefaultBackground()
@ -24,12 +24,9 @@ namespace ImageSharp.Tests.Drawing
string path = this.CreateOutputDirectory("Fill", "SolidBrush"); string path = this.CreateOutputDirectory("Fill", "SolidBrush");
using (Image<Rgba32> image = new Image<Rgba32>(500, 500)) using (Image<Rgba32> image = new Image<Rgba32>(500, 500))
{ {
using (FileStream output = File.OpenWrite($"{path}/DefaultBack.png")) image
{ .Fill(Rgba32.HotPink)
image .Save($"{path}/DefaultBack.png");
.Fill(Rgba32.HotPink)
.Save(output);
}
using (PixelAccessor<Rgba32> sourcePixels = image.Lock()) using (PixelAccessor<Rgba32> sourcePixels = image.Lock())
{ {
@ -46,13 +43,10 @@ namespace ImageSharp.Tests.Drawing
string path = this.CreateOutputDirectory("Fill", "SolidBrush"); string path = this.CreateOutputDirectory("Fill", "SolidBrush");
using (Image<Rgba32> image = new Image<Rgba32>(500, 500)) using (Image<Rgba32> image = new Image<Rgba32>(500, 500))
{ {
using (FileStream output = File.OpenWrite($"{path}/Simple.png")) image
{ .BackgroundColor(Rgba32.Blue)
image .Fill(Rgba32.HotPink)
.BackgroundColor(Rgba32.Blue) .Save($"{path}/Simple.png");
.Fill(Rgba32.HotPink)
.Save(output);
}
using (PixelAccessor<Rgba32> sourcePixels = image.Lock()) using (PixelAccessor<Rgba32> sourcePixels = image.Lock())
{ {
@ -71,13 +65,11 @@ namespace ImageSharp.Tests.Drawing
{ {
Rgba32 color = new Rgba32(Rgba32.HotPink.R, Rgba32.HotPink.G, Rgba32.HotPink.B, 150); Rgba32 color = new Rgba32(Rgba32.HotPink.R, Rgba32.HotPink.G, Rgba32.HotPink.B, 150);
using (FileStream output = File.OpenWrite($"{path}/Opacity.png")) image
{ .BackgroundColor(Rgba32.Blue)
image .Fill(color)
.BackgroundColor(Rgba32.Blue) .Save($"{path}/Opacity.png");
.Fill(color)
.Save(output);
}
//shift background color towards forground color by the opacity amount //shift background color towards forground color by the opacity amount
Rgba32 mergedColor = new Rgba32(Vector4.Lerp(Rgba32.Blue.ToVector4(), Rgba32.HotPink.ToVector4(), 150f / 255f)); Rgba32 mergedColor = new Rgba32(Vector4.Lerp(Rgba32.Blue.ToVector4(), Rgba32.HotPink.ToVector4(), 150f / 255f));

55
tests/ImageSharp.Tests/Drawing/LineComplexPolygonTests.cs

@ -34,13 +34,10 @@ namespace ImageSharp.Tests.Drawing
using (Image<Rgba32> image = new Image<Rgba32>(500, 500)) using (Image<Rgba32> image = new Image<Rgba32>(500, 500))
{ {
using (FileStream output = File.OpenWrite($"{path}/Simple.png")) image
{ .BackgroundColor(Rgba32.Blue)
image .Draw(Rgba32.HotPink, 5, simplePath.Clip(hole1))
.BackgroundColor(Rgba32.Blue) .Save($"{path}/Simple.png");
.Draw(Rgba32.HotPink, 5, simplePath.Clip(hole1))
.Save(output);
}
using (PixelAccessor<Rgba32> sourcePixels = image.Lock()) using (PixelAccessor<Rgba32> sourcePixels = image.Lock())
{ {
@ -84,13 +81,10 @@ namespace ImageSharp.Tests.Drawing
using (Image<Rgba32> image = new Image<Rgba32>(500, 500)) using (Image<Rgba32> image = new Image<Rgba32>(500, 500))
{ {
using (FileStream output = File.OpenWrite($"{path}/SimpleVanishHole.png")) image
{ .BackgroundColor(Rgba32.Blue)
image .Draw(Rgba32.HotPink, 5, simplePath.Clip(hole1))
.BackgroundColor(Rgba32.Blue) .Save($"{path}/SimpleVanishHole.png");
.Draw(Rgba32.HotPink, 5, simplePath.Clip(hole1))
.Save(output);
}
using (PixelAccessor<Rgba32> sourcePixels = image.Lock()) using (PixelAccessor<Rgba32> sourcePixels = image.Lock())
{ {
@ -135,13 +129,10 @@ namespace ImageSharp.Tests.Drawing
using (Image<Rgba32> image = new Image<Rgba32>(500, 500)) using (Image<Rgba32> image = new Image<Rgba32>(500, 500))
{ {
using (FileStream output = File.OpenWrite($"{path}/SimpleOverlapping.png")) image
{ .BackgroundColor(Rgba32.Blue)
image .Draw(Rgba32.HotPink, 5, simplePath.Clip(hole1))
.BackgroundColor(Rgba32.Blue) .Save($"{path}/SimpleOverlapping.png");
.Draw(Rgba32.HotPink, 5, simplePath.Clip(hole1))
.Save(output);
}
using (PixelAccessor<Rgba32> sourcePixels = image.Lock()) using (PixelAccessor<Rgba32> sourcePixels = image.Lock())
{ {
@ -181,13 +172,10 @@ namespace ImageSharp.Tests.Drawing
using (Image<Rgba32> image = new Image<Rgba32>(500, 500)) using (Image<Rgba32> image = new Image<Rgba32>(500, 500))
{ {
using (FileStream output = File.OpenWrite($"{path}/Dashed.png")) image
{ .BackgroundColor(Rgba32.Blue)
image .Draw(Pens.Dash(Rgba32.HotPink, 5), simplePath.Clip(hole1))
.BackgroundColor(Rgba32.Blue) .Save($"{path}/Dashed.png");
.Draw(Pens.Dash(Rgba32.HotPink, 5), simplePath.Clip(hole1))
.Save(output);
}
} }
} }
@ -209,13 +197,10 @@ namespace ImageSharp.Tests.Drawing
using (Image<Rgba32> image = new Image<Rgba32>(500, 500)) using (Image<Rgba32> image = new Image<Rgba32>(500, 500))
{ {
using (FileStream output = File.OpenWrite($"{path}/Opacity.png")) image
{ .BackgroundColor(Rgba32.Blue)
image .Draw(color, 5, simplePath.Clip(hole1))
.BackgroundColor(Rgba32.Blue) .Save($"{path}/Opacity.png");
.Draw(color, 5, simplePath.Clip(hole1))
.Save(output);
}
//shift background color towards forground color by the opacity amount //shift background color towards forground color by the opacity amount
Rgba32 mergedColor = new Rgba32(Vector4.Lerp(Rgba32.Blue.ToVector4(), Rgba32.HotPink.ToVector4(), 150f / 255f)); Rgba32 mergedColor = new Rgba32(Vector4.Lerp(Rgba32.Blue.ToVector4(), Rgba32.HotPink.ToVector4(), 150f / 255f));

119
tests/ImageSharp.Tests/Drawing/LineTests.cs

@ -23,18 +23,15 @@ namespace ImageSharp.Tests.Drawing
string path = this.CreateOutputDirectory("Drawing", "Lines"); string path = this.CreateOutputDirectory("Drawing", "Lines");
using (Image<Rgba32> image = new Image<Rgba32>(500, 500)) using (Image<Rgba32> image = new Image<Rgba32>(500, 500))
{ {
using (FileStream output = File.OpenWrite($"{path}/Simple.png")) image
{ .BackgroundColor(Rgba32.Blue)
image .DrawLines(Rgba32.HotPink, 5,
.BackgroundColor(Rgba32.Blue) new[] {
.DrawLines(Rgba32.HotPink, 5,
new[] {
new Vector2(10, 10), new Vector2(10, 10),
new Vector2(200, 150), new Vector2(200, 150),
new Vector2(50, 300) new Vector2(50, 300)
}) })
.Save(output); .Save($"{path}/Simple.png");
}
using (PixelAccessor<Rgba32> sourcePixels = image.Lock()) using (PixelAccessor<Rgba32> sourcePixels = image.Lock())
{ {
@ -53,19 +50,16 @@ namespace ImageSharp.Tests.Drawing
string path = this.CreateOutputDirectory("Drawing", "Lines"); string path = this.CreateOutputDirectory("Drawing", "Lines");
using (Image<Rgba32> image = new Image<Rgba32>(500, 500)) using (Image<Rgba32> image = new Image<Rgba32>(500, 500))
{ {
using (FileStream output = File.OpenWrite($"{path}/Simple_noantialias.png")) image
{ .BackgroundColor(Rgba32.Blue)
image .DrawLines(Rgba32.HotPink, 5,
.BackgroundColor(Rgba32.Blue) new[] {
.DrawLines(Rgba32.HotPink, 5,
new[] {
new Vector2(10, 10), new Vector2(10, 10),
new Vector2(200, 150), new Vector2(200, 150),
new Vector2(50, 300) new Vector2(50, 300)
}, },
new GraphicsOptions(false)) new GraphicsOptions(false))
.Save(output); .Save($"{path}/Simple_noantialias.png");
}
using (PixelAccessor<Rgba32> sourcePixels = image.Lock()) using (PixelAccessor<Rgba32> sourcePixels = image.Lock())
{ {
@ -84,18 +78,15 @@ namespace ImageSharp.Tests.Drawing
string path = this.CreateOutputDirectory("Drawing", "Lines"); string path = this.CreateOutputDirectory("Drawing", "Lines");
using (Image<Rgba32> image = new Image<Rgba32>(500, 500)) using (Image<Rgba32> image = new Image<Rgba32>(500, 500))
{ {
using (FileStream output = File.OpenWrite($"{path}/Dashed.png")) image
{ .BackgroundColor(Rgba32.Blue)
image .DrawLines(Pens.Dash(Rgba32.HotPink, 5),
.BackgroundColor(Rgba32.Blue) new[] {
.DrawLines(Pens.Dash(Rgba32.HotPink, 5),
new[] {
new Vector2(10, 10), new Vector2(10, 10),
new Vector2(200, 150), new Vector2(200, 150),
new Vector2(50, 300) new Vector2(50, 300)
}) })
.Save(output); .Save($"{path}/Dashed.png");
}
} }
} }
@ -105,18 +96,15 @@ namespace ImageSharp.Tests.Drawing
string path = this.CreateOutputDirectory("Drawing", "Lines"); string path = this.CreateOutputDirectory("Drawing", "Lines");
using (Image<Rgba32> image = new Image<Rgba32>(500, 500)) using (Image<Rgba32> image = new Image<Rgba32>(500, 500))
{ {
using (FileStream output = File.OpenWrite($"{path}/Dot.png")) image
{ .BackgroundColor(Rgba32.Blue)
image .DrawLines(Pens.Dot(Rgba32.HotPink, 5),
.BackgroundColor(Rgba32.Blue) new[] {
.DrawLines(Pens.Dot(Rgba32.HotPink, 5),
new[] {
new Vector2(10, 10), new Vector2(10, 10),
new Vector2(200, 150), new Vector2(200, 150),
new Vector2(50, 300) new Vector2(50, 300)
}) })
.Save(output); .Save($"{path}/Dot.png");
}
} }
} }
@ -126,18 +114,15 @@ namespace ImageSharp.Tests.Drawing
string path = this.CreateOutputDirectory("Drawing", "Lines"); string path = this.CreateOutputDirectory("Drawing", "Lines");
using (Image<Rgba32> image = new Image<Rgba32>(500, 500)) using (Image<Rgba32> image = new Image<Rgba32>(500, 500))
{ {
using (FileStream output = File.OpenWrite($"{path}/DashDot.png")) image
{ .BackgroundColor(Rgba32.Blue)
image .DrawLines(Pens.DashDot(Rgba32.HotPink, 5),
.BackgroundColor(Rgba32.Blue) new[] {
.DrawLines(Pens.DashDot(Rgba32.HotPink, 5),
new[] {
new Vector2(10, 10), new Vector2(10, 10),
new Vector2(200, 150), new Vector2(200, 150),
new Vector2(50, 300) new Vector2(50, 300)
}) })
.Save(output); .Save($"{path}/DashDot.png");
}
} }
} }
@ -147,17 +132,14 @@ namespace ImageSharp.Tests.Drawing
string path = this.CreateOutputDirectory("Drawing", "Lines"); string path = this.CreateOutputDirectory("Drawing", "Lines");
Image<Rgba32> image = new Image<Rgba32>(500, 500); Image<Rgba32> image = new Image<Rgba32>(500, 500);
using (FileStream output = File.OpenWrite($"{path}/DashDotDot.png")) image
{ .BackgroundColor(Rgba32.Blue)
image .DrawLines(Pens.DashDotDot(Rgba32.HotPink, 5), new[] {
.BackgroundColor(Rgba32.Blue)
.DrawLines(Pens.DashDotDot(Rgba32.HotPink, 5), new[] {
new Vector2(10, 10), new Vector2(10, 10),
new Vector2(200, 150), new Vector2(200, 150),
new Vector2(50, 300) new Vector2(50, 300)
}) })
.Save(output); .Save($"{path}/DashDotDot.png");
}
} }
[Fact] [Fact]
@ -169,21 +151,17 @@ namespace ImageSharp.Tests.Drawing
Image<Rgba32> image = new Image<Rgba32>(500, 500); Image<Rgba32> image = new Image<Rgba32>(500, 500);
image
using (FileStream output = File.OpenWrite($"{path}/Opacity.png")) .BackgroundColor(Rgba32.Blue)
{ .DrawLines(color, 10, new[] {
image
.BackgroundColor(Rgba32.Blue)
.DrawLines(color, 10, new[] {
new Vector2(10, 10), new Vector2(10, 10),
new Vector2(200, 150), new Vector2(200, 150),
new Vector2(50, 300) new Vector2(50, 300)
}) })
.Save(output); .Save($"{path}/Opacity.png");
}
//shift background color towards forground color by the opacity amount //shift background color towards forground color by the opacity amount
Rgba32 mergedColor = new Rgba32(Vector4.Lerp(Rgba32.Blue.ToVector4(), Rgba32.HotPink.ToVector4(), 150f/255f)); Rgba32 mergedColor = new Rgba32(Vector4.Lerp(Rgba32.Blue.ToVector4(), Rgba32.HotPink.ToVector4(), 150f / 255f));
using (PixelAccessor<Rgba32> sourcePixels = image.Lock()) using (PixelAccessor<Rgba32> sourcePixels = image.Lock())
{ {
@ -202,18 +180,15 @@ namespace ImageSharp.Tests.Drawing
Image<Rgba32> image = new Image<Rgba32>(500, 500); Image<Rgba32> image = new Image<Rgba32>(500, 500);
using (FileStream output = File.OpenWrite($"{path}/Rectangle.png")) image
{ .BackgroundColor(Rgba32.Blue)
image .DrawLines(Rgba32.HotPink, 10, new[] {
.BackgroundColor(Rgba32.Blue)
.DrawLines(Rgba32.HotPink, 10, new[] {
new Vector2(10, 10), new Vector2(10, 10),
new Vector2(200, 10), new Vector2(200, 10),
new Vector2(200, 150), new Vector2(200, 150),
new Vector2(10, 150) new Vector2(10, 150)
}) })
.Save(output); .Save($"{path}/Rectangle.png");
}
using (PixelAccessor<Rgba32> sourcePixels = image.Lock()) using (PixelAccessor<Rgba32> sourcePixels = image.Lock())
{ {

15
tests/ImageSharp.Tests/Drawing/PolygonTests.cs

@ -24,8 +24,6 @@ namespace ImageSharp.Tests.Drawing
using (Image<Rgba32> image = new Image<Rgba32>(500, 500)) using (Image<Rgba32> image = new Image<Rgba32>(500, 500))
{ {
using (FileStream output = File.OpenWrite($"{path}/Simple.png"))
{
image image
.BackgroundColor(Rgba32.Blue) .BackgroundColor(Rgba32.Blue)
.DrawPolygon(Rgba32.HotPink, 5, .DrawPolygon(Rgba32.HotPink, 5,
@ -34,8 +32,7 @@ namespace ImageSharp.Tests.Drawing
new Vector2(200, 150), new Vector2(200, 150),
new Vector2(50, 300) new Vector2(50, 300)
}) })
.Save(output); .Save($"{path}/Simple.png");
}
using (PixelAccessor<Rgba32> sourcePixels = image.Lock()) using (PixelAccessor<Rgba32> sourcePixels = image.Lock())
{ {
@ -64,13 +61,10 @@ namespace ImageSharp.Tests.Drawing
using (Image<Rgba32> image = new Image<Rgba32>(500, 500)) using (Image<Rgba32> image = new Image<Rgba32>(500, 500))
{ {
using (FileStream output = File.OpenWrite($"{path}/Opacity.png"))
{
image image
.BackgroundColor(Rgba32.Blue) .BackgroundColor(Rgba32.Blue)
.DrawPolygon(color, 10, simplePath) .DrawPolygon(color, 10, simplePath)
.Save(output); .Save($"{path}/Opacity.png");
}
//shift background color towards forground color by the opacity amount //shift background color towards forground color by the opacity amount
Rgba32 mergedColor = new Rgba32(Vector4.Lerp(Rgba32.Blue.ToVector4(), Rgba32.HotPink.ToVector4(), 150f / 255f)); Rgba32 mergedColor = new Rgba32(Vector4.Lerp(Rgba32.Blue.ToVector4(), Rgba32.HotPink.ToVector4(), 150f / 255f));
@ -95,13 +89,10 @@ namespace ImageSharp.Tests.Drawing
using (Image<Rgba32> image = new Image<Rgba32>(500, 500)) using (Image<Rgba32> image = new Image<Rgba32>(500, 500))
{ {
using (FileStream output = File.OpenWrite($"{path}/Rectangle.png"))
{
image image
.BackgroundColor(Rgba32.Blue) .BackgroundColor(Rgba32.Blue)
.Draw(Rgba32.HotPink, 10, new Rectangle(10, 10, 190, 140)) .Draw(Rgba32.HotPink, 10, new Rectangle(10, 10, 190, 140))
.Save(output); .Save($"{path}/Rectangle.png");
}
using (PixelAccessor<Rgba32> sourcePixels = image.Lock()) using (PixelAccessor<Rgba32> sourcePixels = image.Lock())
{ {

16
tests/ImageSharp.Tests/Drawing/RecolorImageTest.cs

@ -26,11 +26,8 @@ namespace ImageSharp.Tests
{ {
using (Image<Rgba32> image = file.CreateImage()) using (Image<Rgba32> image = file.CreateImage())
{ {
using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) image.Fill(brush)
{ .Save($"{path}/{file.FileName}");
image.Fill(brush)
.Save(output);
}
} }
} }
} }
@ -46,12 +43,9 @@ namespace ImageSharp.Tests
{ {
using (Image<Rgba32> image = file.CreateImage()) using (Image<Rgba32> image = file.CreateImage())
{ {
using (FileStream output = File.OpenWrite($"{path}/Shaped_{file.FileName}")) int imageHeight = image.Height;
{ image.Fill(brush, new Rectangle(0, imageHeight / 2 - imageHeight / 4, image.Width, imageHeight / 2))
int imageHeight = image.Height; .Save($"{path}/Shaped_{file.FileName}");
image.Fill(brush, new Rectangle(0, imageHeight/2 - imageHeight/4, image.Width, imageHeight/2))
.Save(output);
}
} }
} }
} }

10
tests/ImageSharp.Tests/Drawing/SolidBezierTests.cs

@ -28,13 +28,10 @@ namespace ImageSharp.Tests.Drawing
}; };
using (Image<Rgba32> image = new Image<Rgba32>(500, 500)) using (Image<Rgba32> image = new Image<Rgba32>(500, 500))
{ {
using (FileStream output = File.OpenWrite($"{path}/Simple.png"))
{
image image
.BackgroundColor(Rgba32.Blue) .BackgroundColor(Rgba32.Blue)
.Fill(Rgba32.HotPink, new Polygon(new BezierLineSegment(simplePath))) .Fill(Rgba32.HotPink, new Polygon(new BezierLineSegment(simplePath)))
.Save(output); .Save($"{path}/Simple.png");
}
using (PixelAccessor<Rgba32> sourcePixels = image.Lock()) using (PixelAccessor<Rgba32> sourcePixels = image.Lock())
{ {
@ -63,13 +60,10 @@ namespace ImageSharp.Tests.Drawing
using (Image<Rgba32> image = new Image<Rgba32>(500, 500)) using (Image<Rgba32> image = new Image<Rgba32>(500, 500))
{ {
using (FileStream output = File.OpenWrite($"{path}/Opacity.png"))
{
image image
.BackgroundColor(Rgba32.Blue) .BackgroundColor(Rgba32.Blue)
.Fill(color, new Polygon(new BezierLineSegment(simplePath))) .Fill(color, new Polygon(new BezierLineSegment(simplePath)))
.Save(output); .Save($"{path}/Opacity.png");
}
//shift background color towards forground color by the opacity amount //shift background color towards forground color by the opacity amount
Rgba32 mergedColor = new Rgba32(Vector4.Lerp(Rgba32.Blue.ToVector4(), Rgba32.HotPink.ToVector4(), 150f / 255f)); Rgba32 mergedColor = new Rgba32(Vector4.Lerp(Rgba32.Blue.ToVector4(), Rgba32.HotPink.ToVector4(), 150f / 255f));

35
tests/ImageSharp.Tests/Drawing/SolidComplexPolygonTests.cs

@ -31,16 +31,13 @@ namespace ImageSharp.Tests.Drawing
new Vector2(93, 85), new Vector2(93, 85),
new Vector2(65, 137))); new Vector2(65, 137)));
IPath clipped = simplePath.Clip(hole1); IPath clipped = simplePath.Clip(hole1);
// var clipped = new Rectangle(10, 10, 100, 100).Clip(new Rectangle(20, 0, 20, 20)); // var clipped = new Rectangle(10, 10, 100, 100).Clip(new Rectangle(20, 0, 20, 20));
using (Image<Rgba32> image = new Image<Rgba32>(500, 500)) using (Image<Rgba32> image = new Image<Rgba32>(500, 500))
{ {
using (FileStream output = File.OpenWrite($"{path}/Simple.png")) image
{ .BackgroundColor(Rgba32.Blue)
image .Fill(Rgba32.HotPink, clipped)
.BackgroundColor(Rgba32.Blue) .Save($"{path}/Simple.png");
.Fill(Rgba32.HotPink, clipped)
.Save(output);
}
using (PixelAccessor<Rgba32> sourcePixels = image.Lock()) using (PixelAccessor<Rgba32> sourcePixels = image.Lock())
{ {
@ -69,13 +66,10 @@ namespace ImageSharp.Tests.Drawing
using (Image<Rgba32> image = new Image<Rgba32>(500, 500)) using (Image<Rgba32> image = new Image<Rgba32>(500, 500))
{ {
using (FileStream output = File.OpenWrite($"{path}/SimpleOverlapping.png")) image
{ .BackgroundColor(Rgba32.Blue)
image .Fill(Rgba32.HotPink, simplePath.Clip(hole1))
.BackgroundColor(Rgba32.Blue) .Save($"{path}/SimpleOverlapping.png");
.Fill(Rgba32.HotPink, simplePath.Clip(hole1))
.Save(output);
}
using (PixelAccessor<Rgba32> sourcePixels = image.Lock()) using (PixelAccessor<Rgba32> sourcePixels = image.Lock())
{ {
@ -104,13 +98,10 @@ namespace ImageSharp.Tests.Drawing
using (Image<Rgba32> image = new Image<Rgba32>(500, 500)) using (Image<Rgba32> image = new Image<Rgba32>(500, 500))
{ {
using (FileStream output = File.OpenWrite($"{path}/Opacity.png")) image
{ .BackgroundColor(Rgba32.Blue)
image .Fill(color, simplePath.Clip(hole1))
.BackgroundColor(Rgba32.Blue) .Save($"{path}/Opacity.png");
.Fill(color, simplePath.Clip(hole1))
.Save(output);
}
//shift background color towards forground color by the opacity amount //shift background color towards forground color by the opacity amount
Rgba32 mergedColor = new Rgba32(Vector4.Lerp(Rgba32.Blue.ToVector4(), Rgba32.HotPink.ToVector4(), 150f / 255f)); Rgba32 mergedColor = new Rgba32(Vector4.Lerp(Rgba32.Blue.ToVector4(), Rgba32.HotPink.ToVector4(), 150f / 255f));

96
tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs

@ -31,12 +31,9 @@ namespace ImageSharp.Tests.Drawing
using (Image<Rgba32> image = new Image<Rgba32>(500, 500)) using (Image<Rgba32> image = new Image<Rgba32>(500, 500))
{ {
using (FileStream output = File.OpenWrite($"{path}/Simple.png")) image
{ .FillPolygon(Rgba32.HotPink, simplePath, new GraphicsOptions(true))
image .Save($"{path}/Simple.png");
.FillPolygon(Rgba32.HotPink, simplePath, new GraphicsOptions(true))
.Save(output);
}
using (PixelAccessor<Rgba32> sourcePixels = image.Lock()) using (PixelAccessor<Rgba32> sourcePixels = image.Lock())
{ {
@ -57,12 +54,9 @@ namespace ImageSharp.Tests.Drawing
using (Image<Rgba32> image = new Image<Rgba32>(500, 500)) using (Image<Rgba32> image = new Image<Rgba32>(500, 500))
{ {
using (FileStream output = File.OpenWrite($"{path}/Pattern.png")) image
{ .FillPolygon(Brushes.Horizontal(Rgba32.HotPink), simplePath, new GraphicsOptions(true))
image .Save($"{path}/Pattern.png");
.FillPolygon(Brushes.Horizontal(Rgba32.HotPink), simplePath, new GraphicsOptions(true))
.Save(output);
}
using (PixelAccessor<Rgba32> sourcePixels = image.Lock()) using (PixelAccessor<Rgba32> sourcePixels = image.Lock())
{ {
@ -82,12 +76,11 @@ namespace ImageSharp.Tests.Drawing
}; };
using (Image<Rgba32> image = new Image<Rgba32>(500, 500)) using (Image<Rgba32> image = new Image<Rgba32>(500, 500))
using (FileStream output = File.OpenWrite($"{path}/Simple_NoAntialias.png"))
{ {
image image
.BackgroundColor(Rgba32.Blue) .BackgroundColor(Rgba32.Blue)
.FillPolygon(Rgba32.HotPink, simplePath, new GraphicsOptions(false)) .FillPolygon(Rgba32.HotPink, simplePath, new GraphicsOptions(false))
.Save(output); .Save($"{path}/Simple_NoAntialias.png");
using (PixelAccessor<Rgba32> sourcePixels = image.Lock()) using (PixelAccessor<Rgba32> sourcePixels = image.Lock())
{ {
@ -114,14 +107,13 @@ namespace ImageSharp.Tests.Drawing
using (Image<Rgba32> brushImage = TestFile.Create(TestImages.Bmp.Car).CreateImage()) using (Image<Rgba32> brushImage = TestFile.Create(TestImages.Bmp.Car).CreateImage())
using (Image<Rgba32> image = new Image<Rgba32>(500, 500)) using (Image<Rgba32> image = new Image<Rgba32>(500, 500))
using (FileStream output = File.OpenWrite($"{path}/Image.png"))
{ {
ImageBrush<Rgba32> brush = new ImageBrush<Rgba32>(brushImage); ImageBrush<Rgba32> brush = new ImageBrush<Rgba32>(brushImage);
image image
.BackgroundColor(Rgba32.Blue) .BackgroundColor(Rgba32.Blue)
.FillPolygon(brush, simplePath) .FillPolygon(brush, simplePath)
.Save(output); .Save($"{path}/Image.png");
} }
} }
@ -138,13 +130,10 @@ namespace ImageSharp.Tests.Drawing
using (Image<Rgba32> image = new Image<Rgba32>(500, 500)) using (Image<Rgba32> image = new Image<Rgba32>(500, 500))
{ {
using (FileStream output = File.OpenWrite($"{path}/Opacity.png")) image
{ .BackgroundColor(Rgba32.Blue)
image .FillPolygon(color, simplePath)
.BackgroundColor(Rgba32.Blue) .Save($"{path}/Opacity.png");
.FillPolygon(color, simplePath)
.Save(output);
}
//shift background color towards forground color by the opacity amount //shift background color towards forground color by the opacity amount
Rgba32 mergedColor = new Rgba32(Vector4.Lerp(Rgba32.Blue.ToVector4(), Rgba32.HotPink.ToVector4(), 150f / 255f)); Rgba32 mergedColor = new Rgba32(Vector4.Lerp(Rgba32.Blue.ToVector4(), Rgba32.HotPink.ToVector4(), 150f / 255f));
@ -163,13 +152,10 @@ namespace ImageSharp.Tests.Drawing
using (Image<Rgba32> image = new Image<Rgba32>(500, 500)) using (Image<Rgba32> image = new Image<Rgba32>(500, 500))
{ {
using (FileStream output = File.OpenWrite($"{path}/Rectangle.png")) image
{ .BackgroundColor(Rgba32.Blue)
image .Fill(Rgba32.HotPink, new SixLabors.Shapes.Rectangle(10, 10, 190, 140))
.BackgroundColor(Rgba32.Blue) .Save($"{path}/Rectangle.png");
.Fill(Rgba32.HotPink, new SixLabors.Shapes.Rectangle(10, 10, 190, 140))
.Save(output);
}
using (PixelAccessor<Rgba32> sourcePixels = image.Lock()) using (PixelAccessor<Rgba32> sourcePixels = image.Lock())
{ {
@ -193,13 +179,10 @@ namespace ImageSharp.Tests.Drawing
using (Image<Rgba32> image = new Image<Rgba32>(100, 100)) using (Image<Rgba32> image = new Image<Rgba32>(100, 100))
{ {
using (FileStream output = File.OpenWrite($"{path}/Triangle.png")) image
{ .BackgroundColor(Rgba32.Blue)
image .Fill(Rgba32.HotPink, new RegularPolygon(50, 50, 3, 30))
.BackgroundColor(Rgba32.Blue) .Save($"{path}/Triangle.png");
.Fill(Rgba32.HotPink, new RegularPolygon(50, 50, 3, 30))
.Save(output);
}
using (PixelAccessor<Rgba32> sourcePixels = image.Lock()) using (PixelAccessor<Rgba32> sourcePixels = image.Lock())
{ {
@ -219,13 +202,10 @@ namespace ImageSharp.Tests.Drawing
config.ParallelOptions.MaxDegreeOfParallelism = 1; config.ParallelOptions.MaxDegreeOfParallelism = 1;
using (Image<Rgba32> image = new Image<Rgba32>(config, 100, 100)) using (Image<Rgba32> image = new Image<Rgba32>(config, 100, 100))
{ {
using (FileStream output = File.OpenWrite($"{path}/Septagon.png")) image
{ .BackgroundColor(Rgba32.Blue)
image .Fill(Rgba32.HotPink, new RegularPolygon(50, 50, 7, 30, -(float)Math.PI))
.BackgroundColor(Rgba32.Blue) .Save($"{path}/Septagon.png");
.Fill(Rgba32.HotPink, new RegularPolygon(50, 50, 7, 30, -(float)Math.PI))
.Save(output);
}
} }
} }
@ -238,14 +218,11 @@ namespace ImageSharp.Tests.Drawing
config.ParallelOptions.MaxDegreeOfParallelism = 1; config.ParallelOptions.MaxDegreeOfParallelism = 1;
using (Image<Rgba32> image = new Image<Rgba32>(config, 100, 100)) using (Image<Rgba32> image = new Image<Rgba32>(config, 100, 100))
{ {
using (FileStream output = File.OpenWrite($"{path}/ellipse.png")) image
{ .BackgroundColor(Rgba32.Blue)
image .Fill(Rgba32.HotPink, new Ellipse(50, 50, 30, 50)
.BackgroundColor(Rgba32.Blue) .Rotate((float)(Math.PI / 3)))
.Fill(Rgba32.HotPink, new Ellipse(50, 50, 30, 50) .Save($"{path}/ellipse.png");
.Rotate((float)(Math.PI / 3)))
.Save(output);
}
} }
} }
@ -258,21 +235,18 @@ namespace ImageSharp.Tests.Drawing
config.ParallelOptions.MaxDegreeOfParallelism = 1; config.ParallelOptions.MaxDegreeOfParallelism = 1;
using (Image<Rgba32> image = new Image<Rgba32>(config, 200, 200)) using (Image<Rgba32> image = new Image<Rgba32>(config, 200, 200))
{ {
using (FileStream output = File.OpenWrite($"{path}/clipped-corner.png")) image
{ .Fill(Rgba32.Blue)
image .FillPolygon(Rgba32.HotPink, new[]
.Fill(Rgba32.Blue) {
.FillPolygon(Rgba32.HotPink, new[]
{
new Vector2( 8, 8 ), new Vector2( 8, 8 ),
new Vector2( 64, 8 ), new Vector2( 64, 8 ),
new Vector2( 64, 64 ), new Vector2( 64, 64 ),
new Vector2( 120, 64 ), new Vector2( 120, 64 ),
new Vector2( 120, 120 ), new Vector2( 120, 120 ),
new Vector2( 8, 120 ) new Vector2( 8, 120 )
} ) })
.Save(output); .Save($"{path}/clipped-corner.png");
}
} }
} }
} }

2
tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs

@ -31,7 +31,7 @@ namespace ImageSharp.Tests
string filename = file.GetFileNameWithoutExtension(bitsPerPixel); string filename = file.GetFileNameWithoutExtension(bitsPerPixel);
using (Image<Rgba32> image = file.CreateImage()) using (Image<Rgba32> image = file.CreateImage())
{ {
image.Save($"{path}/{filename}.bmp", new BmpEncoderOptions { BitsPerPixel = bitsPerPixel }); image.Save($"{path}/{filename}.bmp", new BmpEncoder { BitsPerPixel = bitsPerPixel });
} }
} }
} }

22
tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs

@ -36,7 +36,7 @@ namespace ImageSharp.Tests
using (Image<Rgba32> image = file.CreateImage()) using (Image<Rgba32> image = file.CreateImage())
{ {
string filename = path + "/" + file.FileNameWithoutExtension + ".txt"; string filename = path + "/" + file.FileNameWithoutExtension + ".txt";
File.WriteAllText(filename, image.ToBase64String()); File.WriteAllText(filename, image.ToBase64String("image/png"));
} }
} }
} }
@ -50,10 +50,7 @@ namespace ImageSharp.Tests
{ {
using (Image<Rgba32> image = file.CreateImage()) using (Image<Rgba32> image = file.CreateImage())
{ {
using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) image.Save($"{path}/{file.FileName}");
{
image.Save(output);
}
} }
} }
} }
@ -65,14 +62,14 @@ namespace ImageSharp.Tests
foreach (TestFile file in Files) foreach (TestFile file in Files)
{ {
using (Image<Rgba32> srcImage = file.CreateImage()) using (Image<Rgba32> srcImage = Image.Load<Rgba32>(file.Bytes, out var mimeType))
{ {
using (Image<Rgba32> image = new Image<Rgba32>(srcImage)) using (Image<Rgba32> image = new Image<Rgba32>(srcImage))
{ {
using (FileStream output = File.OpenWrite($"{path}/Octree-{file.FileName}")) using (FileStream output = File.OpenWrite($"{path}/Octree-{file.FileName}"))
{ {
image.Quantize(Quantization.Octree) image.Quantize(Quantization.Octree)
.Save(output, image.CurrentImageFormat); .Save(output, mimeType);
} }
} }
@ -82,7 +79,7 @@ namespace ImageSharp.Tests
using (FileStream output = File.OpenWrite($"{path}/Wu-{file.FileName}")) using (FileStream output = File.OpenWrite($"{path}/Wu-{file.FileName}"))
{ {
image.Quantize(Quantization.Wu) image.Quantize(Quantization.Wu)
.Save(output, image.CurrentImageFormat); .Save(output, mimeType);
} }
} }
@ -91,7 +88,7 @@ namespace ImageSharp.Tests
using (FileStream output = File.OpenWrite($"{path}/Palette-{file.FileName}")) using (FileStream output = File.OpenWrite($"{path}/Palette-{file.FileName}"))
{ {
image.Quantize(Quantization.Palette) image.Quantize(Quantization.Palette)
.Save(output, image.CurrentImageFormat); .Save(output, mimeType);
} }
} }
} }
@ -138,18 +135,17 @@ namespace ImageSharp.Tests
foreach (TestFile file in Files) foreach (TestFile file in Files)
{ {
byte[] serialized; byte[] serialized;
using (Image<Rgba32> image = file.CreateImage()) using (Image<Rgba32> image = Image.Load(file.Bytes, out string mimeType))
using (MemoryStream memoryStream = new MemoryStream()) using (MemoryStream memoryStream = new MemoryStream())
{ {
image.Save(memoryStream); image.Save(memoryStream, mimeType);
memoryStream.Flush(); memoryStream.Flush();
serialized = memoryStream.ToArray(); serialized = memoryStream.ToArray();
} }
using (Image<Rgba32> image2 = Image.Load<Rgba32>(serialized)) using (Image<Rgba32> image2 = Image.Load<Rgba32>(serialized))
using (FileStream output = File.OpenWrite($"{path}/{file.FileName}"))
{ {
image2.Save(output); image2.Save($"{path}/{file.FileName}");
} }
} }
} }

6
tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs

@ -33,7 +33,7 @@ namespace ImageSharp.Tests
[Fact] [Fact]
public void Decode_IgnoreMetadataIsFalse_CommentsAreRead() public void Decode_IgnoreMetadataIsFalse_CommentsAreRead()
{ {
DecoderOptions options = new DecoderOptions() GifDecoder options = new GifDecoder()
{ {
IgnoreMetadata = false IgnoreMetadata = false
}; };
@ -51,7 +51,7 @@ namespace ImageSharp.Tests
[Fact] [Fact]
public void Decode_IgnoreMetadataIsTrue_CommentsAreIgnored() public void Decode_IgnoreMetadataIsTrue_CommentsAreIgnored()
{ {
DecoderOptions options = new DecoderOptions() GifDecoder options = new GifDecoder()
{ {
IgnoreMetadata = true IgnoreMetadata = true
}; };
@ -67,7 +67,7 @@ namespace ImageSharp.Tests
[Fact] [Fact]
public void Decode_TextEncodingSetToUnicode_TextIsReadWithCorrectEncoding() public void Decode_TextEncodingSetToUnicode_TextIsReadWithCorrectEncoding()
{ {
GifDecoderOptions options = new GifDecoderOptions() GifDecoder options = new GifDecoder()
{ {
TextEncoding = Encoding.Unicode TextEncoding = Encoding.Unicode
}; };

8
tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs

@ -29,7 +29,7 @@ namespace ImageSharp.Tests
[Fact] [Fact]
public void Encode_IgnoreMetadataIsFalse_CommentsAreWritten() public void Encode_IgnoreMetadataIsFalse_CommentsAreWritten()
{ {
EncoderOptions options = new EncoderOptions() GifEncoder options = new GifEncoder()
{ {
IgnoreMetadata = false IgnoreMetadata = false
}; };
@ -40,7 +40,7 @@ namespace ImageSharp.Tests
{ {
using (MemoryStream memStream = new MemoryStream()) using (MemoryStream memStream = new MemoryStream())
{ {
input.Save(memStream, new GifFormat(), options); input.Save(memStream, options);
memStream.Position = 0; memStream.Position = 0;
using (Image<Rgba32> output = Image.Load<Rgba32>(memStream)) using (Image<Rgba32> output = Image.Load<Rgba32>(memStream))
@ -56,7 +56,7 @@ namespace ImageSharp.Tests
[Fact] [Fact]
public void Encode_IgnoreMetadataIsTrue_CommentsAreNotWritten() public void Encode_IgnoreMetadataIsTrue_CommentsAreNotWritten()
{ {
GifEncoderOptions options = new GifEncoderOptions() GifEncoder options = new GifEncoder()
{ {
IgnoreMetadata = true IgnoreMetadata = true
}; };
@ -88,7 +88,7 @@ namespace ImageSharp.Tests
using (MemoryStream memStream = new MemoryStream()) using (MemoryStream memStream = new MemoryStream())
{ {
input.Save(memStream, new GifFormat()); input.Save(memStream, new GifEncoder());
memStream.Position = 0; memStream.Position = 0;
using (Image<Rgba32> output = Image.Load<Rgba32>(memStream)) using (Image<Rgba32> output = Image.Load<Rgba32>(memStream))

13
tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs

@ -62,13 +62,12 @@ namespace ImageSharp.Tests
byte[] data; byte[] data;
using (Image<TPixel> image = provider.GetImage()) using (Image<TPixel> image = provider.GetImage())
{ {
JpegEncoder encoder = new JpegEncoder(); JpegEncoder encoder = new JpegEncoder { Subsample = subsample, Quality = quality };
JpegEncoderOptions options = new JpegEncoderOptions { Subsample = subsample, Quality = quality };
data = new byte[65536]; data = new byte[65536];
using (MemoryStream ms = new MemoryStream(data)) using (MemoryStream ms = new MemoryStream(data))
{ {
image.Save(ms, encoder, options); image.Save(ms, encoder);
} }
} }
@ -91,7 +90,7 @@ namespace ImageSharp.Tests
image.Save(ms, new JpegEncoder()); image.Save(ms, new JpegEncoder());
ms.Seek(0, SeekOrigin.Begin); ms.Seek(0, SeekOrigin.Begin);
using (JpegDecoderCore decoder = new JpegDecoderCore(null, null)) using (JpegDecoderCore decoder = new JpegDecoderCore(null))
{ {
Image<TPixel> mirror = decoder.Decode<TPixel>(ms); Image<TPixel> mirror = decoder.Decode<TPixel>(ms);
@ -125,14 +124,14 @@ namespace ImageSharp.Tests
[Fact] [Fact]
public void Decode_IgnoreMetadataIsFalse_ExifProfileIsRead() public void Decode_IgnoreMetadataIsFalse_ExifProfileIsRead()
{ {
DecoderOptions options = new DecoderOptions() JpegDecoder decoder = new JpegDecoder()
{ {
IgnoreMetadata = false IgnoreMetadata = false
}; };
TestFile testFile = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan); TestFile testFile = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan);
using (Image<Rgba32> image = testFile.CreateImage(options)) using (Image<Rgba32> image = testFile.CreateImage(decoder))
{ {
Assert.NotNull(image.MetaData.ExifProfile); Assert.NotNull(image.MetaData.ExifProfile);
} }
@ -141,7 +140,7 @@ namespace ImageSharp.Tests
[Fact] [Fact]
public void Decode_IgnoreMetadataIsTrue_ExifProfileIgnored() public void Decode_IgnoreMetadataIsTrue_ExifProfileIgnored()
{ {
DecoderOptions options = new DecoderOptions() JpegDecoder options = new JpegDecoder()
{ {
IgnoreMetadata = true IgnoreMetadata = true
}; };

16
tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs

@ -37,14 +37,12 @@ namespace ImageSharp.Tests
{ {
using (Image<TPixel> image = provider.GetImage().Resize(new ResizeOptions { Size = new Size(150, 100), Mode = ResizeMode.Max })) using (Image<TPixel> image = provider.GetImage().Resize(new ResizeOptions { Size = new Size(150, 100), Mode = ResizeMode.Max }))
{ {
image.MetaData.Quality = quality;
image.MetaData.ExifProfile = null; // Reduce the size of the file image.MetaData.ExifProfile = null; // Reduce the size of the file
JpegEncoder encoder = new JpegEncoder(); JpegEncoder options = new JpegEncoder { Subsample = subsample, Quality = quality };
JpegEncoderOptions options = new JpegEncoderOptions { Subsample = subsample, Quality = quality };
provider.Utility.TestName += $"{subsample}_Q{quality}"; provider.Utility.TestName += $"{subsample}_Q{quality}";
provider.Utility.SaveTestOutputFile(image, "png"); provider.Utility.SaveTestOutputFile(image, "png");
provider.Utility.SaveTestOutputFile(image, "jpg", encoder, options); provider.Utility.SaveTestOutputFile(image, "jpg", options);
} }
} }
@ -61,9 +59,7 @@ namespace ImageSharp.Tests
using (FileStream outputStream = File.OpenWrite(utility.GetTestOutputFileName("jpg"))) using (FileStream outputStream = File.OpenWrite(utility.GetTestOutputFileName("jpg")))
{ {
JpegEncoder encoder = new JpegEncoder(); image.Save(outputStream, new JpegEncoder()
image.Save(outputStream, encoder, new JpegEncoderOptions()
{ {
Subsample = subSample, Subsample = subSample,
Quality = quality Quality = quality
@ -75,7 +71,7 @@ namespace ImageSharp.Tests
[Fact] [Fact]
public void Encode_IgnoreMetadataIsFalse_ExifProfileIsWritten() public void Encode_IgnoreMetadataIsFalse_ExifProfileIsWritten()
{ {
EncoderOptions options = new EncoderOptions() JpegEncoder options = new JpegEncoder()
{ {
IgnoreMetadata = false IgnoreMetadata = false
}; };
@ -86,7 +82,7 @@ namespace ImageSharp.Tests
{ {
using (MemoryStream memStream = new MemoryStream()) using (MemoryStream memStream = new MemoryStream())
{ {
input.Save(memStream, new JpegFormat(), options); input.Save(memStream, options);
memStream.Position = 0; memStream.Position = 0;
using (Image<Rgba32> output = Image.Load<Rgba32>(memStream)) using (Image<Rgba32> output = Image.Load<Rgba32>(memStream))
@ -100,7 +96,7 @@ namespace ImageSharp.Tests
[Fact] [Fact]
public void Encode_IgnoreMetadataIsTrue_ExifProfileIgnored() public void Encode_IgnoreMetadataIsTrue_ExifProfileIgnored()
{ {
JpegEncoderOptions options = new JpegEncoderOptions() JpegEncoder options = new JpegEncoder()
{ {
IgnoreMetadata = true IgnoreMetadata = true
}; };

5
tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs

@ -82,9 +82,8 @@ namespace ImageSharp.Tests
{ {
foreach (Image<Rgba32> img in testImages) foreach (Image<Rgba32> img in testImages)
{ {
JpegEncoder encoder = new JpegEncoder(); JpegEncoder options = new JpegEncoder { Quality = quality, Subsample = subsample };
JpegEncoderOptions options = new JpegEncoderOptions { Quality = quality, Subsample = subsample }; img.Save(ms, options);
img.Save(ms, encoder, options);
ms.Seek(0, SeekOrigin.Begin); ms.Seek(0, SeekOrigin.Begin);
} }
}, },

6
tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs

@ -35,7 +35,7 @@ namespace ImageSharp.Tests
[Fact] [Fact]
public void Decode_IgnoreMetadataIsFalse_TextChunckIsRead() public void Decode_IgnoreMetadataIsFalse_TextChunckIsRead()
{ {
PngDecoderOptions options = new PngDecoderOptions() PngDecoder options = new PngDecoder()
{ {
IgnoreMetadata = false IgnoreMetadata = false
}; };
@ -53,7 +53,7 @@ namespace ImageSharp.Tests
[Fact] [Fact]
public void Decode_IgnoreMetadataIsTrue_TextChunksAreIgnored() public void Decode_IgnoreMetadataIsTrue_TextChunksAreIgnored()
{ {
PngDecoderOptions options = new PngDecoderOptions() PngDecoder options = new PngDecoder()
{ {
IgnoreMetadata = true IgnoreMetadata = true
}; };
@ -69,7 +69,7 @@ namespace ImageSharp.Tests
[Fact] [Fact]
public void Decode_TextEncodingSetToUnicode_TextIsReadWithCorrectEncoding() public void Decode_TextEncodingSetToUnicode_TextIsReadWithCorrectEncoding()
{ {
PngDecoderOptions options = new PngDecoderOptions() PngDecoder options = new PngDecoder()
{ {
TextEncoding = Encoding.Unicode TextEncoding = Encoding.Unicode
}; };

4
tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs

@ -31,13 +31,13 @@ namespace ImageSharp.Tests
{ {
using (Image<TPixel> image = provider.GetImage()) using (Image<TPixel> image = provider.GetImage())
{ {
PngEncoderOptions options = new PngEncoderOptions() PngEncoder options = new PngEncoder()
{ {
PngColorType = pngColorType PngColorType = pngColorType
}; };
provider.Utility.TestName += "_" + pngColorType; provider.Utility.TestName += "_" + pngColorType;
provider.Utility.SaveTestOutputFile(image, "png", new PngEncoder(), options); provider.Utility.SaveTestOutputFile(image, "png", options);
} }
} }

3
tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs

@ -50,8 +50,7 @@ namespace ImageSharp.Tests.Formats.Png
using (MemoryStream ms = new MemoryStream()) using (MemoryStream ms = new MemoryStream())
{ {
// image.Save(provider.Utility.GetTestOutputFileName("bmp")); // image.Save(provider.Utility.GetTestOutputFileName("bmp"));
image.MetaData.Quality = 256; image.Save(ms, new PngEncoder() { Quality = 256 });
image.Save(ms, new PngEncoder());
ms.Position = 0; ms.Position = 0;
using (Image<Rgba32> img2 = Image.Load<Rgba32>(ms, new PngDecoder())) using (Image<Rgba32> img2 = Image.Load<Rgba32>(ms, new PngDecoder()))
{ {

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

@ -20,10 +20,8 @@ namespace ImageSharp.Tests
public class ImageLoadTests : IDisposable public class ImageLoadTests : IDisposable
{ {
private readonly Mock<IFileSystem> fileSystem; private readonly Mock<IFileSystem> fileSystem;
private readonly IDecoderOptions decoderOptions;
private Image<Rgba32> returnImage; private Image<Rgba32> returnImage;
private Mock<IImageDecoder> localDecoder; private Mock<IImageDecoder> localDecoder;
private Mock<IImageFormat> localFormat;
private readonly string FilePath; private readonly string FilePath;
public Configuration LocalConfiguration { get; private set; } public Configuration LocalConfiguration { get; private set; }
@ -36,18 +34,14 @@ namespace ImageSharp.Tests
this.returnImage = new Image<Rgba32>(1, 1); this.returnImage = new Image<Rgba32>(1, 1);
this.localDecoder = new Mock<IImageDecoder>(); this.localDecoder = new Mock<IImageDecoder>();
this.localFormat = new Mock<IImageFormat>(); this.localDecoder.Setup(x => x.MimeTypes).Returns(new[] { "img/test" });
this.localFormat.Setup(x => x.Decoder).Returns(this.localDecoder.Object); this.localDecoder.Setup(x => x.FileExtensions).Returns(new[] { "png", "jpg" });
this.localFormat.Setup(x => x.Encoder).Returns(new Mock<IImageEncoder>().Object); this.localDecoder.Setup(x => x.HeaderSize).Returns(1);
this.localFormat.Setup(x => x.MimeType).Returns("img/test"); this.localDecoder.Setup(x => x.IsSupportedFileFormat(It.IsAny<Span<byte>>())).Returns(true);
this.localFormat.Setup(x => x.Extension).Returns("png");
this.localFormat.Setup(x => x.HeaderSize).Returns(1); this.localDecoder.Setup(x => x.Decode<Rgba32>(It.IsAny<Configuration>(), It.IsAny<Stream>()))
this.localFormat.Setup(x => x.IsSupportedFileFormat(It.IsAny<byte[]>())).Returns(true);
this.localFormat.Setup(x => x.SupportedExtensions).Returns(new string[] { "png", "jpg" }); .Callback<Configuration, Stream>((c, s) => {
this.localDecoder.Setup(x => x.Decode<Rgba32>(It.IsAny<Configuration>(), It.IsAny<Stream>(), It.IsAny<IDecoderOptions>()))
.Callback<Configuration, Stream, IDecoderOptions>((c, s, o) => {
using (var ms = new MemoryStream()) using (var ms = new MemoryStream())
{ {
s.CopyTo(ms); s.CopyTo(ms);
@ -58,14 +52,16 @@ namespace ImageSharp.Tests
this.fileSystem = new Mock<IFileSystem>(); this.fileSystem = new Mock<IFileSystem>();
this.LocalConfiguration = new Configuration(this.localFormat.Object) this.LocalConfiguration = new Configuration()
{ {
FileSystem = this.fileSystem.Object FileSystem = this.fileSystem.Object
}; };
this.LocalConfiguration.AddImageFormat(this.localDecoder.Object);
TestFormat.RegisterGloablTestFormat(); TestFormat.RegisterGloablTestFormat();
this.Marker = Guid.NewGuid().ToByteArray(); this.Marker = Guid.NewGuid().ToByteArray();
this.DataStream = TestFormat.GlobalTestFormat.CreateStream(this.Marker); this.DataStream = TestFormat.GlobalTestFormat.CreateStream(this.Marker);
this.decoderOptions = new Mock<IDecoderOptions>().Object;
this.FilePath = Guid.NewGuid().ToString(); this.FilePath = Guid.NewGuid().ToString();
this.fileSystem.Setup(x => x.OpenRead(this.FilePath)).Returns(this.DataStream); this.fileSystem.Setup(x => x.OpenRead(this.FilePath)).Returns(this.DataStream);
@ -80,10 +76,8 @@ namespace ImageSharp.Tests
Image<Rgba32> img = Image.Load<Rgba32>(this.DataStream); Image<Rgba32> img = Image.Load<Rgba32>(this.DataStream);
Assert.NotNull(img); Assert.NotNull(img);
Assert.Equal(TestFormat.GlobalTestFormat, img.CurrentImageFormat);
TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, null, Configuration.Default); TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, Configuration.Default);
} }
@ -94,10 +88,8 @@ namespace ImageSharp.Tests
Image<Rgba32> img = Image.Load<Rgba32>(stream); Image<Rgba32> img = Image.Load<Rgba32>(stream);
Assert.NotNull(img); Assert.NotNull(img);
Assert.Equal(TestFormat.GlobalTestFormat, img.CurrentImageFormat);
TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, null, Configuration.Default); TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, Configuration.Default);
} }
@ -108,36 +100,11 @@ namespace ImageSharp.Tests
Assert.NotNull(img); Assert.NotNull(img);
Assert.Equal(TestFormat.GlobalTestFormat.Sample<Rgba32>(), img); Assert.Equal(TestFormat.GlobalTestFormat.Sample<Rgba32>(), img);
Assert.Equal(TestFormat.GlobalTestFormat, img.CurrentImageFormat);
TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, null, Configuration.Default); TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, Configuration.Default);
}
[Fact]
public void LoadFromStreamWithOptions()
{
Image<Rgba32> img = Image.Load<Rgba32>(this.DataStream, this.decoderOptions);
Assert.NotNull(img);
Assert.Equal(TestFormat.GlobalTestFormat, img.CurrentImageFormat);
TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, this.decoderOptions, Configuration.Default);
}
[Fact]
public void LoadFromStreamWithTypeAndOptions()
{
Image<Rgba32> img = Image.Load<Rgba32>(this.DataStream, this.decoderOptions);
Assert.NotNull(img);
Assert.Equal(TestFormat.GlobalTestFormat.Sample<Rgba32>(), img);
Assert.Equal(TestFormat.GlobalTestFormat, img.CurrentImageFormat);
TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, this.decoderOptions, Configuration.Default);
} }
[Fact] [Fact]
public void LoadFromStreamWithConfig() public void LoadFromStreamWithConfig()
@ -146,9 +113,8 @@ namespace ImageSharp.Tests
Image<Rgba32> img = Image.Load<Rgba32>(this.LocalConfiguration, stream); Image<Rgba32> img = Image.Load<Rgba32>(this.LocalConfiguration, stream);
Assert.NotNull(img); Assert.NotNull(img);
Assert.Equal(this.localFormat.Object, img.CurrentImageFormat);
this.localDecoder.Verify(x => x.Decode<Rgba32>(this.LocalConfiguration, stream, null)); this.localDecoder.Verify(x => x.Decode<Rgba32>(this.LocalConfiguration, stream));
} }
@ -160,40 +126,11 @@ namespace ImageSharp.Tests
Assert.NotNull(img); Assert.NotNull(img);
Assert.Equal(this.returnImage, img); Assert.Equal(this.returnImage, img);
Assert.Equal(this.localFormat.Object, img.CurrentImageFormat);
this.localDecoder.Verify(x => x.Decode<Rgba32>(this.LocalConfiguration, stream, null)); this.localDecoder.Verify(x => x.Decode<Rgba32>(this.LocalConfiguration, stream));
} }
[Fact]
public void LoadFromStreamWithConfigAndOptions()
{
Stream stream = new MemoryStream();
Image<Rgba32> img = Image.Load<Rgba32>(this.LocalConfiguration, stream, this.decoderOptions);
Assert.NotNull(img);
Assert.Equal(this.localFormat.Object, img.CurrentImageFormat);
this.localDecoder.Verify(x => x.Decode<Rgba32>(this.LocalConfiguration, stream, this.decoderOptions));
}
[Fact]
public void LoadFromStreamWithTypeAndConfigAndOptions()
{
Stream stream = new MemoryStream();
Image<Rgba32> img = Image.Load<Rgba32>(this.LocalConfiguration, stream, this.decoderOptions);
Assert.NotNull(img);
Assert.Equal(this.returnImage, img);
Assert.Equal(this.localFormat.Object, img.CurrentImageFormat);
this.localDecoder.Verify(x => x.Decode<Rgba32>(this.LocalConfiguration, stream, this.decoderOptions));
}
[Fact] [Fact]
public void LoadFromStreamWithDecoder() public void LoadFromStreamWithDecoder()
@ -202,7 +139,7 @@ namespace ImageSharp.Tests
Image<Rgba32> img = Image.Load<Rgba32>(stream, this.localDecoder.Object); Image<Rgba32> img = Image.Load<Rgba32>(stream, this.localDecoder.Object);
Assert.NotNull(img); Assert.NotNull(img);
this.localDecoder.Verify(x => x.Decode<Rgba32>(Configuration.Default, stream, null)); this.localDecoder.Verify(x => x.Decode<Rgba32>(Configuration.Default, stream));
} }
[Fact] [Fact]
@ -213,28 +150,7 @@ namespace ImageSharp.Tests
Assert.NotNull(img); Assert.NotNull(img);
Assert.Equal(this.returnImage, img); Assert.Equal(this.returnImage, img);
this.localDecoder.Verify(x => x.Decode<Rgba32>(Configuration.Default, stream, null)); this.localDecoder.Verify(x => x.Decode<Rgba32>(Configuration.Default, stream));
}
[Fact]
public void LoadFromStreamWithDecoderAndOptions()
{
Stream stream = new MemoryStream();
Image<Rgba32> img = Image.Load<Rgba32>(stream, this.localDecoder.Object, this.decoderOptions);
Assert.NotNull(img);
this.localDecoder.Verify(x => x.Decode<Rgba32>(Configuration.Default, stream, this.decoderOptions));
}
[Fact]
public void LoadFromStreamWithTypeAndDecoderAndOptions()
{
Stream stream = new MemoryStream();
Image<Rgba32> img = Image.Load<Rgba32>(stream, this.localDecoder.Object, this.decoderOptions);
Assert.NotNull(img);
Assert.Equal(this.returnImage, img);
this.localDecoder.Verify(x => x.Decode<Rgba32>(Configuration.Default, stream, this.decoderOptions));
} }
[Fact] [Fact]
@ -243,10 +159,9 @@ namespace ImageSharp.Tests
Image<Rgba32> img = Image.Load<Rgba32>(this.DataStream.ToArray()); Image<Rgba32> img = Image.Load<Rgba32>(this.DataStream.ToArray());
Assert.NotNull(img); Assert.NotNull(img);
Assert.Equal(TestFormat.GlobalTestFormat, img.CurrentImageFormat);
TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, null, Configuration.Default); TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, Configuration.Default);
} }
@ -257,34 +172,8 @@ namespace ImageSharp.Tests
Assert.NotNull(img); Assert.NotNull(img);
Assert.Equal(TestFormat.GlobalTestFormat.Sample<Rgba32>(), img); Assert.Equal(TestFormat.GlobalTestFormat.Sample<Rgba32>(), img);
Assert.Equal(TestFormat.GlobalTestFormat, img.CurrentImageFormat);
TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, null, Configuration.Default);
}
[Fact]
public void LoadFromBytesWithOptions()
{
Image<Rgba32> img = Image.Load<Rgba32>(this.DataStream.ToArray(), this.decoderOptions);
Assert.NotNull(img);
Assert.Equal(TestFormat.GlobalTestFormat, img.CurrentImageFormat);
TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, this.decoderOptions, Configuration.Default);
}
[Fact]
public void LoadFromBytesWithTypeAndOptions()
{
Image<Rgba32> img = Image.Load<Rgba32>(this.DataStream.ToArray(), this.decoderOptions);
Assert.NotNull(img);
Assert.Equal(TestFormat.GlobalTestFormat.Sample<Rgba32>(), img);
Assert.Equal(TestFormat.GlobalTestFormat, img.CurrentImageFormat);
TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, this.decoderOptions, Configuration.Default); TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, Configuration.Default);
} }
@ -294,9 +183,8 @@ namespace ImageSharp.Tests
Image<Rgba32> img = Image.Load<Rgba32>(this.LocalConfiguration, this.DataStream.ToArray()); Image<Rgba32> img = Image.Load<Rgba32>(this.LocalConfiguration, this.DataStream.ToArray());
Assert.NotNull(img); Assert.NotNull(img);
Assert.Equal(this.localFormat.Object, img.CurrentImageFormat);
this.localDecoder.Verify(x => x.Decode<Rgba32>(this.LocalConfiguration, It.IsAny<Stream>(), null)); this.localDecoder.Verify(x => x.Decode<Rgba32>(this.LocalConfiguration, It.IsAny<Stream>()));
Assert.Equal(this.DataStream.ToArray(), this.DecodedData); Assert.Equal(this.DataStream.ToArray(), this.DecodedData);
} }
@ -308,49 +196,19 @@ namespace ImageSharp.Tests
Assert.NotNull(img); Assert.NotNull(img);
Assert.Equal(this.returnImage, img); Assert.Equal(this.returnImage, img);
Assert.Equal(this.localFormat.Object, img.CurrentImageFormat);
this.localDecoder.Verify(x => x.Decode<Rgba32>(this.LocalConfiguration, It.IsAny<Stream>(), null)); this.localDecoder.Verify(x => x.Decode<Rgba32>(this.LocalConfiguration, It.IsAny<Stream>()));
Assert.Equal(this.DataStream.ToArray(), this.DecodedData); Assert.Equal(this.DataStream.ToArray(), this.DecodedData);
} }
[Fact]
public void LoadFromBytesWithConfigAndOptions()
{
Image<Rgba32> img = Image.Load<Rgba32>(this.LocalConfiguration, this.DataStream.ToArray(), this.decoderOptions);
Assert.NotNull(img);
Assert.Equal(this.localFormat.Object, img.CurrentImageFormat);
this.localDecoder.Verify(x => x.Decode<Rgba32>(this.LocalConfiguration, It.IsAny<Stream>(), this.decoderOptions));
Assert.Equal(this.DataStream.ToArray(), this.DecodedData);
}
[Fact]
public void LoadFromBytesWithTypeAndConfigAndOptions()
{
Image<Rgba32> img = Image.Load<Rgba32>(this.LocalConfiguration, this.DataStream.ToArray(), this.decoderOptions);
Assert.NotNull(img);
Assert.Equal(this.returnImage, img);
Assert.Equal(this.localFormat.Object, img.CurrentImageFormat);
this.localDecoder.Verify(x => x.Decode<Rgba32>(this.LocalConfiguration, It.IsAny<Stream>(), this.decoderOptions));
Assert.Equal(this.DataStream.ToArray(), this.DecodedData);
}
[Fact] [Fact]
public void LoadFromBytesWithDecoder() public void LoadFromBytesWithDecoder()
{ {
Image<Rgba32> img = Image.Load<Rgba32>(this.DataStream.ToArray(), this.localDecoder.Object); Image<Rgba32> img = Image.Load<Rgba32>(this.DataStream.ToArray(), this.localDecoder.Object);
Assert.NotNull(img); Assert.NotNull(img);
this.localDecoder.Verify(x => x.Decode<Rgba32>(Configuration.Default, It.IsAny<Stream>(), null)); this.localDecoder.Verify(x => x.Decode<Rgba32>(Configuration.Default, It.IsAny<Stream>()));
Assert.Equal(this.DataStream.ToArray(), this.DecodedData); Assert.Equal(this.DataStream.ToArray(), this.DecodedData);
} }
@ -361,28 +219,7 @@ namespace ImageSharp.Tests
Assert.NotNull(img); Assert.NotNull(img);
Assert.Equal(this.returnImage, img); Assert.Equal(this.returnImage, img);
this.localDecoder.Verify(x => x.Decode<Rgba32>(Configuration.Default, It.IsAny<Stream>(), null)); this.localDecoder.Verify(x => x.Decode<Rgba32>(Configuration.Default, It.IsAny<Stream>()));
Assert.Equal(this.DataStream.ToArray(), this.DecodedData);
}
[Fact]
public void LoadFromBytesWithDecoderAndOptions()
{
Image<Rgba32> img = Image.Load<Rgba32>(this.DataStream.ToArray(), this.localDecoder.Object, this.decoderOptions);
Assert.NotNull(img);
this.localDecoder.Verify(x => x.Decode<Rgba32>(Configuration.Default, It.IsAny<Stream>(), this.decoderOptions));
Assert.Equal(this.DataStream.ToArray(), this.DecodedData);
}
[Fact]
public void LoadFromBytesWithTypeAndDecoderAndOptions()
{
Image<Rgba32> img = Image.Load<Rgba32>(this.DataStream.ToArray(), this.localDecoder.Object, this.decoderOptions);
Assert.NotNull(img);
Assert.Equal(this.returnImage, img);
this.localDecoder.Verify(x => x.Decode<Rgba32>(Configuration.Default, It.IsAny<Stream>(), this.decoderOptions));
Assert.Equal(this.DataStream.ToArray(), this.DecodedData); Assert.Equal(this.DataStream.ToArray(), this.DecodedData);
} }
@ -392,10 +229,9 @@ namespace ImageSharp.Tests
Image<Rgba32> img = Image.Load<Rgba32>(this.DataStream); Image<Rgba32> img = Image.Load<Rgba32>(this.DataStream);
Assert.NotNull(img); Assert.NotNull(img);
Assert.Equal(TestFormat.GlobalTestFormat, img.CurrentImageFormat);
TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, null, Configuration.Default); TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, Configuration.Default);
} }
@ -406,35 +242,8 @@ namespace ImageSharp.Tests
Assert.NotNull(img); Assert.NotNull(img);
Assert.Equal(TestFormat.GlobalTestFormat.Sample<Rgba32>(), img); Assert.Equal(TestFormat.GlobalTestFormat.Sample<Rgba32>(), img);
Assert.Equal(TestFormat.GlobalTestFormat, img.CurrentImageFormat);
TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, null, Configuration.Default);
}
[Fact]
public void LoadFromFileWithOptions()
{
Image<Rgba32> img = Image.Load<Rgba32>(this.DataStream, this.decoderOptions);
Assert.NotNull(img);
Assert.Equal(TestFormat.GlobalTestFormat, img.CurrentImageFormat);
TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, this.decoderOptions, Configuration.Default);
}
[Fact]
public void LoadFromFileWithTypeAndOptions()
{
Image<Rgba32> img = Image.Load<Rgba32>(this.DataStream, this.decoderOptions);
Assert.NotNull(img);
Assert.Equal(TestFormat.GlobalTestFormat.Sample<Rgba32>(), img);
Assert.Equal(TestFormat.GlobalTestFormat, img.CurrentImageFormat);
TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, this.decoderOptions, Configuration.Default);
TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, Configuration.Default);
} }
[Fact] [Fact]
@ -443,9 +252,8 @@ namespace ImageSharp.Tests
Image<Rgba32> img = Image.Load<Rgba32>(this.LocalConfiguration, this.FilePath); Image<Rgba32> img = Image.Load<Rgba32>(this.LocalConfiguration, this.FilePath);
Assert.NotNull(img); Assert.NotNull(img);
Assert.Equal(this.localFormat.Object, img.CurrentImageFormat);
this.localDecoder.Verify(x => x.Decode<Rgba32>(this.LocalConfiguration, this.DataStream, null)); this.localDecoder.Verify(x => x.Decode<Rgba32>(this.LocalConfiguration, this.DataStream));
} }
@ -456,45 +264,17 @@ namespace ImageSharp.Tests
Assert.NotNull(img); Assert.NotNull(img);
Assert.Equal(this.returnImage, img); Assert.Equal(this.returnImage, img);
Assert.Equal(this.localFormat.Object, img.CurrentImageFormat);
this.localDecoder.Verify(x => x.Decode<Rgba32>(this.LocalConfiguration, this.DataStream, null));
}
[Fact]
public void LoadFromFileWithConfigAndOptions()
{
Image<Rgba32> img = Image.Load<Rgba32>(this.LocalConfiguration, this.FilePath, this.decoderOptions);
Assert.NotNull(img);
Assert.Equal(this.localFormat.Object, img.CurrentImageFormat);
this.localDecoder.Verify(x => x.Decode<Rgba32>(this.LocalConfiguration, this.DataStream, this.decoderOptions));
}
[Fact]
public void LoadFromFileWithTypeAndConfigAndOptions()
{
Image<Rgba32> img = Image.Load<Rgba32>(this.LocalConfiguration, this.FilePath, this.decoderOptions);
Assert.NotNull(img);
Assert.Equal(this.returnImage, img);
Assert.Equal(this.localFormat.Object, img.CurrentImageFormat);
this.localDecoder.Verify(x => x.Decode<Rgba32>(this.LocalConfiguration, this.DataStream, this.decoderOptions));
this.localDecoder.Verify(x => x.Decode<Rgba32>(this.LocalConfiguration, this.DataStream));
} }
[Fact] [Fact]
public void LoadFromFileWithDecoder() public void LoadFromFileWithDecoder()
{ {
Image<Rgba32> img = Image.Load<Rgba32>(this.FilePath, this.localDecoder.Object); Image<Rgba32> img = Image.Load<Rgba32>(this.FilePath, this.localDecoder.Object);
Assert.NotNull(img); Assert.NotNull(img);
this.localDecoder.Verify(x => x.Decode<Rgba32>(Configuration.Default, this.DataStream, null)); this.localDecoder.Verify(x => x.Decode<Rgba32>(Configuration.Default, this.DataStream));
} }
[Fact] [Fact]
@ -504,26 +284,7 @@ namespace ImageSharp.Tests
Assert.NotNull(img); Assert.NotNull(img);
Assert.Equal(this.returnImage, img); Assert.Equal(this.returnImage, img);
this.localDecoder.Verify(x => x.Decode<Rgba32>(Configuration.Default, this.DataStream, null)); this.localDecoder.Verify(x => x.Decode<Rgba32>(Configuration.Default, this.DataStream));
}
[Fact]
public void LoadFromFileWithDecoderAndOptions()
{
Image<Rgba32> img = Image.Load<Rgba32>(this.FilePath, this.localDecoder.Object, this.decoderOptions);
Assert.NotNull(img);
this.localDecoder.Verify(x => x.Decode<Rgba32>(Configuration.Default, this.DataStream, this.decoderOptions));
}
[Fact]
public void LoadFromFileWithTypeAndDecoderAndOptions()
{
Image<Rgba32> img = Image.Load<Rgba32>(this.FilePath, this.localDecoder.Object, this.decoderOptions);
Assert.NotNull(img);
Assert.Equal(this.returnImage, img);
this.localDecoder.Verify(x => x.Decode<Rgba32>(Configuration.Default, this.DataStream, this.decoderOptions));
} }
[Fact] [Fact]

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

@ -22,37 +22,27 @@ namespace ImageSharp.Tests
{ {
private readonly Image<Rgba32> Image; private readonly Image<Rgba32> Image;
private readonly Mock<IFileSystem> fileSystem; private readonly Mock<IFileSystem> fileSystem;
private readonly Mock<IImageFormat> format;
private readonly Mock<IImageFormat> formatNotRegistered;
private readonly Mock<IImageEncoder> encoder; private readonly Mock<IImageEncoder> encoder;
private readonly Mock<IImageEncoder> encoderNotInFormat; private readonly Mock<IImageEncoder> encoderNotInFormat;
private readonly IEncoderOptions encoderOptions;
public ImageSaveTests() public ImageSaveTests()
{ {
this.encoder = new Mock<IImageEncoder>(); this.encoder = new Mock<IImageEncoder>();
this.format = new Mock<IImageFormat>(); this.encoder.Setup(x => x.MimeTypes).Returns(new[] { "img/test" });
this.format.Setup(x => x.Encoder).Returns(this.encoder.Object); this.encoder.Setup(x => x.FileExtensions).Returns(new string[] { "png", "jpg" });
this.format.Setup(x => x.Decoder).Returns(new Mock<IImageDecoder>().Object);
this.format.Setup(x => x.MimeType).Returns("img/test");
this.format.Setup(x => x.Extension).Returns("png");
this.format.Setup(x => x.SupportedExtensions).Returns(new string[] { "png", "jpg" });
this.encoderNotInFormat = new Mock<IImageEncoder>(); this.encoderNotInFormat = new Mock<IImageEncoder>();
this.formatNotRegistered = new Mock<IImageFormat>(); this.encoderNotInFormat.Setup(x => x.MimeTypes).Returns(new[] { "img/test" });
this.formatNotRegistered.Setup(x => x.Encoder).Returns(this.encoderNotInFormat.Object); this.encoderNotInFormat.Setup(x => x.FileExtensions).Returns(new string[] { "png", "jpg" });
this.formatNotRegistered.Setup(x => x.Decoder).Returns(new Mock<IImageDecoder>().Object);
this.formatNotRegistered.Setup(x => x.MimeType).Returns("img/test");
this.formatNotRegistered.Setup(x => x.Extension).Returns("png");
this.formatNotRegistered.Setup(x => x.SupportedExtensions).Returns(new string[] { "png", "jpg" });
this.fileSystem = new Mock<IFileSystem>(); this.fileSystem = new Mock<IFileSystem>();
this.encoderOptions = new Mock<IEncoderOptions>().Object; var config = new Configuration()
this.Image = new Image<Rgba32>(new Configuration(this.format.Object)
{ {
FileSystem = this.fileSystem.Object FileSystem = this.fileSystem.Object
}, 1, 1); };
config.AddImageFormat(this.encoder.Object);
this.Image = new Image<Rgba32>(config, 1, 1);
} }
[Fact] [Fact]
@ -62,19 +52,9 @@ namespace ImageSharp.Tests
this.fileSystem.Setup(x => x.Create("path.png")).Returns(stream); this.fileSystem.Setup(x => x.Create("path.png")).Returns(stream);
this.Image.Save("path.png"); this.Image.Save("path.png");
this.encoder.Verify(x => x.Encode<Rgba32>(this.Image, stream, null)); this.encoder.Verify(x => x.Encode<Rgba32>(this.Image, stream));
} }
[Fact]
public void SavePathWithOptions()
{
Stream stream = new MemoryStream();
this.fileSystem.Setup(x => x.Create("path.jpg")).Returns(stream);
this.Image.Save("path.jpg", this.encoderOptions);
this.encoder.Verify(x => x.Encode<Rgba32>(this.Image, stream, this.encoderOptions));
}
[Fact] [Fact]
public void SavePathWithEncoder() public void SavePathWithEncoder()
@ -84,61 +64,24 @@ namespace ImageSharp.Tests
this.Image.Save("path.jpg", this.encoderNotInFormat.Object); this.Image.Save("path.jpg", this.encoderNotInFormat.Object);
this.encoderNotInFormat.Verify(x => x.Encode<Rgba32>(this.Image, stream, null)); this.encoderNotInFormat.Verify(x => x.Encode<Rgba32>(this.Image, stream));
} }
[Fact] [Fact]
public void SavePathWithEncoderAndOptions() public void ToBase64String()
{ {
Stream stream = new MemoryStream(); var str = this.Image.ToBase64String("img/test");
this.fileSystem.Setup(x => x.Create("path.jpg")).Returns(stream);
this.Image.Save("path.jpg", this.encoderNotInFormat.Object, this.encoderOptions);
this.encoderNotInFormat.Verify(x => x.Encode<Rgba32>(this.Image, stream, this.encoderOptions));
}
[Fact]
public void SavePathWithFormat()
{
Stream stream = new MemoryStream();
this.fileSystem.Setup(x => x.Create("path.jpg")).Returns(stream);
this.Image.Save("path.jpg", this.encoderNotInFormat.Object);
this.encoderNotInFormat.Verify(x => x.Encode<Rgba32>(this.Image, stream, null));
}
[Fact]
public void SavePathWithFormatAndOptions()
{
Stream stream = new MemoryStream();
this.fileSystem.Setup(x => x.Create("path.jpg")).Returns(stream);
this.Image.Save("path.jpg", this.encoderNotInFormat.Object, this.encoderOptions);
this.encoderNotInFormat.Verify(x => x.Encode<Rgba32>(this.Image, stream, this.encoderOptions)); this.encoder.Verify(x => x.Encode<Rgba32>(this.Image, It.IsAny<Stream>()));
} }
[Fact] [Fact]
public void SaveStream() public void SaveStreamWithMime()
{ {
Stream stream = new MemoryStream(); Stream stream = new MemoryStream();
this.Image.Save(stream); this.Image.Save(stream, "img/test");
this.encoder.Verify(x => x.Encode<Rgba32>(this.Image, stream, null)); this.encoder.Verify(x => x.Encode<Rgba32>(this.Image, stream));
}
[Fact]
public void SaveStreamWithOptions()
{
Stream stream = new MemoryStream();
this.Image.Save(stream, this.encoderOptions);
this.encoder.Verify(x => x.Encode<Rgba32>(this.Image, stream, this.encoderOptions));
} }
[Fact] [Fact]
@ -148,37 +91,7 @@ namespace ImageSharp.Tests
this.Image.Save(stream, this.encoderNotInFormat.Object); this.Image.Save(stream, this.encoderNotInFormat.Object);
this.encoderNotInFormat.Verify(x => x.Encode<Rgba32>(this.Image, stream, null)); this.encoderNotInFormat.Verify(x => x.Encode<Rgba32>(this.Image, stream));
}
[Fact]
public void SaveStreamWithEncoderAndOptions()
{
Stream stream = new MemoryStream();
this.Image.Save(stream, this.encoderNotInFormat.Object, this.encoderOptions);
this.encoderNotInFormat.Verify(x => x.Encode<Rgba32>(this.Image, stream, this.encoderOptions));
}
[Fact]
public void SaveStreamWithFormat()
{
Stream stream = new MemoryStream();
this.Image.Save(stream, this.formatNotRegistered.Object);
this.encoderNotInFormat.Verify(x => x.Encode<Rgba32>(this.Image, stream, null));
}
[Fact]
public void SaveStreamWithFormatAndOptions()
{
Stream stream = new MemoryStream();
this.Image.Save(stream, this.formatNotRegistered.Object, this.encoderOptions);
this.encoderNotInFormat.Verify(x => x.Encode<Rgba32>(this.Image, stream, this.encoderOptions));
} }
public void Dispose() public void Dispose()

30
tests/ImageSharp.Tests/Image/ImageTests.cs

@ -74,10 +74,9 @@ namespace ImageSharp.Tests
image.Save(file); image.Save(file);
} }
TestFile c = TestFile.Create("../../TestOutput/Save_DetecedEncoding.png"); using (Image<Rgba32> img = Image.Load(file, out var mime))
using (Image<Rgba32> img = c.CreateImage())
{ {
Assert.IsType<PngFormat>(img.CurrentImageFormat); Assert.Equal("image/png", mime);
} }
} }
@ -85,7 +84,7 @@ namespace ImageSharp.Tests
public void Save_UnknownExtensionsEncoding() public void Save_UnknownExtensionsEncoding()
{ {
string file = TestFile.GetPath("../../TestOutput/Save_DetecedEncoding.tmp"); string file = TestFile.GetPath("../../TestOutput/Save_DetecedEncoding.tmp");
InvalidOperationException ex = Assert.Throws<InvalidOperationException>( NotSupportedException ex = Assert.Throws<NotSupportedException>(
() => () =>
{ {
using (Image<Rgba32> image = new Image<Rgba32>(10, 10)) using (Image<Rgba32> image = new Image<Rgba32>(10, 10))
@ -95,23 +94,6 @@ namespace ImageSharp.Tests
}); });
} }
[Fact]
public void Save_SetFormat()
{
string file = TestFile.GetPath("../../TestOutput/Save_SetFormat.dat");
System.IO.DirectoryInfo dir = System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(file));
using (Image<Rgba32> image = new Image<Rgba32>(10, 10))
{
image.Save(file, new PngFormat());
}
TestFile c = TestFile.Create("../../TestOutput/Save_SetFormat.dat");
using (Image<Rgba32> img = c.CreateImage())
{
Assert.IsType<PngFormat>(img.CurrentImageFormat);
}
}
[Fact] [Fact]
public void Save_SetEncoding() public void Save_SetEncoding()
{ {
@ -121,11 +103,9 @@ namespace ImageSharp.Tests
{ {
image.Save(file, new PngEncoder()); image.Save(file, new PngEncoder());
} }
using (Image<Rgba32> img = Image.Load(file, out var mime))
TestFile c = TestFile.Create("../../TestOutput/Save_SetEncoding.dat");
using (Image<Rgba32> img = c.CreateImage())
{ {
Assert.IsType<PngFormat>(img.CurrentImageFormat); Assert.Equal("image/png", mime);
} }
} }
} }

2
tests/ImageSharp.Tests/MetaData/ImageMetaDataTests.cs

@ -28,7 +28,6 @@ namespace ImageSharp.Tests
metaData.HorizontalResolution = 4; metaData.HorizontalResolution = 4;
metaData.VerticalResolution = 2; metaData.VerticalResolution = 2;
metaData.Properties.Add(imageProperty); metaData.Properties.Add(imageProperty);
metaData.Quality = 24;
metaData.RepeatCount = 1; metaData.RepeatCount = 1;
metaData.DisposalMethod = DisposalMethod.RestoreToBackground; metaData.DisposalMethod = DisposalMethod.RestoreToBackground;
@ -39,7 +38,6 @@ namespace ImageSharp.Tests
Assert.Equal(4, clone.HorizontalResolution); Assert.Equal(4, clone.HorizontalResolution);
Assert.Equal(2, clone.VerticalResolution); Assert.Equal(2, clone.VerticalResolution);
Assert.Equal(imageProperty, clone.Properties[0]); Assert.Equal(imageProperty, clone.Properties[0]);
Assert.Equal(24, clone.Quality);
Assert.Equal(1, clone.RepeatCount); Assert.Equal(1, clone.RepeatCount);
Assert.Equal(DisposalMethod.RestoreToBackground, clone.DisposalMethod); Assert.Equal(DisposalMethod.RestoreToBackground, clone.DisposalMethod);
} }

7
tests/ImageSharp.Tests/TestFile.cs

@ -11,7 +11,7 @@ namespace ImageSharp.Tests
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using ImageSharp.Formats;
using ImageSharp.PixelFormats; using ImageSharp.PixelFormats;
/// <summary> /// <summary>
@ -135,13 +135,12 @@ namespace ImageSharp.Tests
/// <summary> /// <summary>
/// Creates a new image. /// Creates a new image.
/// </summary> /// </summary>
/// <param name="options">The options for the decoder.</param>
/// <returns> /// <returns>
/// The <see cref="Image"/>. /// The <see cref="Image"/>.
/// </returns> /// </returns>
public Image<Rgba32> CreateImage(IDecoderOptions options) public Image<Rgba32> CreateImage(IImageDecoder decoder)
{ {
return Image.Load<Rgba32>(this.Bytes, options); return Image.Load(this.image.Configuration, this.Bytes, decoder);
} }
/// <summary> /// <summary>

34
tests/ImageSharp.Tests/TestFormat.cs

@ -19,13 +19,14 @@ namespace ImageSharp.Tests
/// <summary> /// <summary>
/// A test image file. /// A test image file.
/// </summary> /// </summary>
public class TestFormat : ImageSharp.Formats.IImageFormat public class TestFormat
{ {
public static TestFormat GlobalTestFormat { get; } = new TestFormat(); public static TestFormat GlobalTestFormat { get; } = new TestFormat();
public static void RegisterGloablTestFormat() public static void RegisterGloablTestFormat()
{ {
Configuration.Default.AddImageFormat(GlobalTestFormat); Configuration.Default.AddImageFormat(GlobalTestFormat.Encoder);
Configuration.Default.AddImageFormat(GlobalTestFormat.Decoder);
} }
public TestFormat() public TestFormat()
@ -58,9 +59,9 @@ namespace ImageSharp.Tests
Dictionary<Type, object> _sampleImages = new Dictionary<Type, object>(); Dictionary<Type, object> _sampleImages = new Dictionary<Type, object>();
public void VerifyDecodeCall(byte[] marker, IDecoderOptions options, Configuration config) public void VerifyDecodeCall(byte[] marker, Configuration config)
{ {
DecodeOperation[] discovered = this.DecodeCalls.Where(x => x.IsMatch(marker, options, config)).ToArray(); DecodeOperation[] discovered = this.DecodeCalls.Where(x => x.IsMatch(marker, config)).ToArray();
Assert.True(discovered.Any(), "No calls to decode on this formate with the proveded options happend"); Assert.True(discovered.Any(), "No calls to decode on this formate with the proveded options happend");
@ -92,7 +93,7 @@ namespace ImageSharp.Tests
public int HeaderSize => this.header.Length; public int HeaderSize => this.header.Length;
public bool IsSupportedFileFormat(byte[] header) public bool IsSupportedFileFormat(Span<byte> header)
{ {
if (header.Length < this.header.Length) if (header.Length < this.header.Length)
{ {
@ -110,15 +111,10 @@ namespace ImageSharp.Tests
public struct DecodeOperation public struct DecodeOperation
{ {
public byte[] marker; public byte[] marker;
public IDecoderOptions options;
internal Configuration config; internal Configuration config;
public bool IsMatch(byte[] testMarker, IDecoderOptions testOptions, Configuration config) public bool IsMatch(byte[] testMarker, Configuration config)
{ {
if (this.options != testOptions)
{
return false;
}
if (this.config != config) if (this.config != config)
{ {
@ -150,8 +146,13 @@ namespace ImageSharp.Tests
this.testFormat = testFormat; this.testFormat = testFormat;
} }
public IEnumerable<string> MimeTypes => new[] { testFormat.MimeType };
public IEnumerable<string> FileExtensions => testFormat.SupportedExtensions;
public Image<TPixel> Decode<TPixel>(Configuration config, Stream stream, IDecoderOptions options) where TPixel : struct, IPixel<TPixel> public int HeaderSize => testFormat.HeaderSize;
public Image<TPixel> Decode<TPixel>(Configuration config, Stream stream) where TPixel : struct, IPixel<TPixel>
{ {
var ms = new MemoryStream(); var ms = new MemoryStream();
@ -160,13 +161,14 @@ namespace ImageSharp.Tests
this.testFormat.DecodeCalls.Add(new DecodeOperation this.testFormat.DecodeCalls.Add(new DecodeOperation
{ {
marker = marker, marker = marker,
options = options,
config = config config = config
}); });
// TODO record this happend so we an verify it. // TODO record this happend so we an verify it.
return this.testFormat.Sample<TPixel>(); return this.testFormat.Sample<TPixel>();
} }
public bool IsSupportedFileFormat(Span<byte> header) => testFormat.IsSupportedFileFormat(header);
} }
public class TestEncoder : ImageSharp.Formats.IImageEncoder public class TestEncoder : ImageSharp.Formats.IImageEncoder
@ -178,7 +180,11 @@ namespace ImageSharp.Tests
this.testFormat = testFormat; this.testFormat = testFormat;
} }
public void Encode<TPixel>(Image<TPixel> image, Stream stream, IEncoderOptions options) where TPixel : struct, IPixel<TPixel> public IEnumerable<string> MimeTypes => new[] { testFormat.MimeType };
public IEnumerable<string> FileExtensions => testFormat.SupportedExtensions;
public void Encode<TPixel>(Image<TPixel> image, Stream stream) where TPixel : struct, IPixel<TPixel>
{ {
// TODO record this happend so we an verify it. // TODO record this happend so we an verify it.
} }

12
tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs

@ -97,18 +97,16 @@ namespace ImageSharp.Tests
/// <param name="extension">The requested extension</param> /// <param name="extension">The requested extension</param>
/// <param name="encoder">Optional encoder</param> /// <param name="encoder">Optional encoder</param>
/// <param name="options">Optional encoder options</param> /// <param name="options">Optional encoder options</param>
public void SaveTestOutputFile<TPixel>(Image<TPixel> image, string extension = null, IImageEncoder encoder = null, IEncoderOptions options = null, string tag = null) public void SaveTestOutputFile<TPixel>(Image<TPixel> image, string extension = null, IImageEncoder encoder = null, string tag = null)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
string path = this.GetTestOutputFileName(extension: extension, tag:tag); string path = this.GetTestOutputFileName(extension: extension, tag:tag);
extension = Path.GetExtension(path); extension = Path.GetExtension(path);
IImageFormat format = GetImageFormatByExtension(extension); encoder = encoder ?? GetImageFormatByExtension(extension);
encoder = encoder ?? format.Encoder;
using (FileStream stream = File.OpenWrite(path)) using (FileStream stream = File.OpenWrite(path))
{ {
image.Save(stream, encoder, options); image.Save(stream, encoder);
} }
} }
@ -123,10 +121,10 @@ namespace ImageSharp.Tests
this.Init(method.DeclaringType.Name, method.Name); this.Init(method.DeclaringType.Name, method.Name);
} }
private static IImageFormat GetImageFormatByExtension(string extension) private static IImageEncoder GetImageFormatByExtension(string extension)
{ {
extension = extension?.TrimStart('.'); extension = extension?.TrimStart('.');
return Configuration.Default.ImageFormats.First(f => f.SupportedExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase)); return Configuration.Default.ImageEncoders.Last(f => f.FileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase));
} }
private string GetTestOutputDir() private string GetTestOutputDir()

Loading…
Cancel
Save