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.
pull/254/head
Scott Williams 9 years ago
parent
commit
ea44e6ce15
  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();
/// <summary>
/// The list of supported <see cref="IImageFormat"/>.
/// The list of supported <see cref="IImageEncoder"/>.
/// </summary>
private readonly List<IImageFormat> imageFormatsList = new List<IImageFormat>();
private readonly List<IImageEncoder> encoders = new List<IImageEncoder>();
/// <summary>
/// Initializes a new instance of the <see cref="Configuration" /> class.
/// The list of supported <see cref="IImageDecoder"/>.
/// </summary>
public Configuration()
{
}
private readonly List<IImageDecoder> decoders = new List<IImageDecoder>();
/// <summary>
/// Initializes a new instance of the <see cref="Configuration" /> class.
/// </summary>
/// <param name="providers">The inital set of image formats.</param>
public Configuration(params IImageFormat[] providers)
public Configuration()
{
foreach (IImageFormat p in providers)
{
this.AddImageFormat(p);
}
}
/// <summary>
@ -59,9 +52,14 @@ namespace ImageSharp
public static Configuration Default { get; } = Lazy.Value;
/// <summary>
/// Gets the collection of supported <see cref="IImageFormat"/>
/// Gets the collection of supported <see cref="IImageEncoder"/>
/// </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>
/// Gets the global parallel options for processing tasks in parallel.
@ -81,118 +79,57 @@ namespace ImageSharp
#endif
/// <summary>
/// Adds a new <see cref="IImageFormat"/> 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)
/// Adds a new <see cref="IImageEncoder"/> to the collection of supported image formats.
/// </summary>
/// <returns>The default configuration of <see cref="Configuration"/> </returns>
internal static Configuration CreateDefaultInstance()
/// <param name="decoder">The new format to add.</param>
public void AddImageFormat(IImageDecoder decoder)
{
Configuration config = new Configuration();
// lets try auto loading the known image formats
config.AddImageFormat(new Formats.PngFormat());
config.AddImageFormat(new Formats.JpegFormat());
config.AddImageFormat(new Formats.GifFormat());
config.AddImageFormat(new Formats.BmpFormat());
return config;
}
Guard.NotNull(decoder, nameof(decoder));
Guard.NotNullOrEmpty(decoder.FileExtensions, nameof(decoder.FileExtensions));
Guard.NotNullOrEmpty(decoder.MimeTypes, nameof(decoder.MimeTypes));
/// <summary>
/// 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)
lock (this.syncRoot)
{
IImageFormat format = Activator.CreateInstance(type) as IImageFormat;
if (format != null
&& format.Encoder != null
&& format.Decoder != null
&& !string.IsNullOrEmpty(format.MimeType)
&& format.SupportedExtensions?.Any() == true)
{
// we can use the locked version as we have already validated in the if.
this.AddImageFormatLocked(format);
return true;
}
}
this.decoders.Add(decoder);
return false;
this.SetMaxHeaderSize();
}
}
/// <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>
/// <param name="format">The image format.</param>
private void AddImageFormatLocked(IImageFormat format)
/// <param name="encoder">The new format to add.</param>
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)
{
if (this.GuardDuplicate(format))
{
this.imageFormatsList.Add(format);
this.SetMaxHeaderSize();
}
this.encoders.Add(encoder);
}
}
/// <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>
/// <param name="format">The image format.</param>
/// <exception cref="ArgumentException">Thrown if a duplicate is added.</exception>
/// <returns>
/// The <see cref="bool"/>.
/// </returns>
private bool GuardDuplicate(IImageFormat format)
/// <returns>The default configuration of <see cref="Configuration"/> </returns>
internal static Configuration CreateDefaultInstance()
{
if (!format.SupportedExtensions.Contains(format.Extension, StringComparer.OrdinalIgnoreCase))
{
throw new ArgumentException("The supported extensions should contain the default extension.", nameof(format));
}
// 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;
}
}
Configuration config = new Configuration();
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>
@ -200,7 +137,7 @@ namespace ImageSharp
/// </summary>
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
{
using System;
using System.Collections.Generic;
using System.IO;
using ImageSharp.PixelFormats;
@ -28,7 +29,24 @@ namespace ImageSharp.Formats
public class BmpDecoder : IImageDecoder
{
/// <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>
{

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

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

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

@ -17,11 +17,6 @@ namespace ImageSharp.Formats
/// </summary>
internal sealed class BmpEncoderCore
{
/// <summary>
/// The options for the encoder.
/// </summary>
private readonly IBmpEncoderOptions options;
/// <summary>
/// The amount to pad each row by.
/// </summary>
@ -30,12 +25,15 @@ namespace ImageSharp.Formats
/// <summary>
/// Initializes a new instance of the <see cref="BmpEncoderCore"/> class.
/// </summary>
/// <param name="options">The options for the encoder.</param>
public BmpEncoderCore(IBmpEncoderOptions options)
public BmpEncoderCore()
{
this.options = options ?? new BmpEncoderOptions();
}
/// <summary>
/// Gets or sets the BitsPerPixel
/// </summary>
public BmpBitsPerPixel BitsPerPixel { get; internal set; } = BmpBitsPerPixel.Pixel24;
/// <summary>
/// Encodes the image to the specified stream from the <see cref="ImageBase{TPixel}"/>.
/// </summary>
@ -49,9 +47,9 @@ namespace ImageSharp.Formats
Guard.NotNull(stream, nameof(stream));
// Cast to int will get the bytes per pixel
short bpp = (short)(8 * (int)this.options.BitsPerPixel);
short bpp = (short)(8 * (int)this.BitsPerPixel);
int bytesPerLine = 4 * (((image.Width * bpp) + 31) / 32);
this.padding = bytesPerLine - (image.Width * (int)this.options.BitsPerPixel);
this.padding = bytesPerLine - (image.Width * (int)this.BitsPerPixel);
// Do not use IDisposable pattern here as we want to preserve the stream.
EndianBinaryWriter writer = new EndianBinaryWriter(Endianness.LittleEndian, stream);
@ -136,7 +134,7 @@ namespace ImageSharp.Formats
{
using (PixelAccessor<TPixel> pixels = image.Lock())
{
switch (this.options.BitsPerPixel)
switch (this.BitsPerPixel)
{
case BmpBitsPerPixel.Pixel32:
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
{
using System.Collections.Generic;
using System.Text;
/// <summary>
@ -90,6 +91,16 @@ namespace ImageSharp.Formats
/// <summary>
/// Gets the default encoding to use when reading comments.
/// </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
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using ImageSharp.PixelFormats;
/// <summary>
@ -16,27 +17,43 @@ namespace ImageSharp.Formats
public class GifDecoder : IImageDecoder
{
/// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream, IDecoderOptions options)
public IEnumerable<string> MimeTypes => GifConstants.MimeTypes;
where TPixel : struct, IPixel<TPixel>
{
IGifDecoderOptions gifOptions = GifDecoderOptions.Create(options);
/// <inheritdoc/>
public IEnumerable<string> FileExtensions => GifConstants.FileExtensions;
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>
/// 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>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="configuration">The configuration.</param>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <param name="options">The options for the decoder.</param>
/// <returns>The image thats been decoded.</returns>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream, IGifDecoderOptions options)
public Encoding TextEncoding { get; set; } = GifConstants.DefaultEncoding;
/// <inheritdoc/>
public int HeaderSize => 6;
/// <inheritdoc/>
public bool IsSupportedFileFormat(Span<byte> header)
{
return header.Length >= this.HeaderSize &&
header[0] == 0x47 && // G
header[1] == 0x49 && // I
header[2] == 0x46 && // F
header[3] == 0x38 && // 8
(header[4] == 0x39 || header[4] == 0x37) && // 9 or 7
header[5] == 0x61; // a
}
/// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
where TPixel : struct, IPixel<TPixel>
{
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>
private readonly byte[] buffer = new byte[16];
/// <summary>
/// The decoder options.
/// </summary>
private readonly IGifDecoderOptions options;
/// <summary>
/// The global configuration.
/// </summary>
@ -83,14 +78,24 @@ namespace ImageSharp.Formats
/// <summary>
/// Initializes a new instance of the <see cref="GifDecoderCore{TPixel}"/> class.
/// </summary>
/// <param name="options">The decoder options.</param>
/// <param name="encoding">The decoder encoding.</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;
}
/// <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>
/// Decodes the stream to the image.
/// </summary>
@ -268,7 +273,7 @@ namespace ImageSharp.Formats
throw new ImageFormatException($"Gif comment length '{length}' exceeds max '{GifConstants.MaxCommentLength}'");
}
if (this.options.IgnoreMetadata)
if (this.IgnoreMetadata)
{
this.currentStream.Seek(length, SeekOrigin.Current);
continue;
@ -279,7 +284,7 @@ namespace ImageSharp.Formats
try
{
this.currentStream.Read(commentsBuffer, 0, length);
string comments = this.options.TextEncoding.GetString(commentsBuffer, 0, length);
string comments = this.TextEncoding.GetString(commentsBuffer, 0, length);
this.metaData.Properties.Add(new ImageProperty(GifConstants.Comments, comments));
}
finally
@ -363,8 +368,6 @@ namespace ImageSharp.Formats
if (this.previousFrame == null)
{
this.metaData.Quality = colorTableLength / 3;
// This initializes the image to become fully transparent because the alpha channel is zero.
this.image = new Image<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
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using ImageSharp.PixelFormats;
using ImageSharp.Quantizers;
/// <summary>
/// Image encoder for writing image data to a stream in gif format.
@ -16,25 +18,46 @@ namespace ImageSharp.Formats
public class GifEncoder : IImageEncoder
{
/// <inheritdoc/>
public void Encode<TPixel>(Image<TPixel> image, Stream stream, IEncoderOptions options)
where TPixel : struct, IPixel<TPixel>
{
IGifEncoderOptions gifOptions = GifEncoderOptions.Create(options);
public IEnumerable<string> MimeTypes => GifConstants.MimeTypes;
this.Encode(image, stream, gifOptions);
}
/// <inheritdoc/>
public IEnumerable<string> FileExtensions => GifConstants.FileExtensions;
/// <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>
/// <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, IGifEncoderOptions options)
public bool IgnoreMetadata { get; set; } = false;
/// <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; }
/// <inheritdoc/>
public void Encode<TPixel>(Image<TPixel> image, Stream stream)
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);
}
}

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

@ -9,7 +9,7 @@ namespace ImageSharp.Formats
using System.Buffers;
using System.IO;
using System.Linq;
using System.Text;
using ImageSharp.PixelFormats;
using IO;
@ -25,11 +25,6 @@ namespace ImageSharp.Formats
/// </summary>
private readonly byte[] buffer = new byte[16];
/// <summary>
/// The options for the encoder.
/// </summary>
private readonly IGifEncoderOptions options;
/// <summary>
/// The number of bits requires to store the image palette.
/// </summary>
@ -43,17 +38,37 @@ namespace ImageSharp.Formats
/// <summary>
/// Initializes a new instance of the <see cref="GifEncoderCore"/> class.
/// </summary>
/// <param name="options">The options for the encoder.</param>
public GifEncoderCore(IGifEncoderOptions options)
/// <param name="encoding">The encoding for the encoder.</param>
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>
/// Gets or sets the quantizer for reducing the color count.
/// </summary>
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>
/// Encodes the image to the specified stream from the <see cref="Image{TPixel}"/>.
/// </summary>
@ -66,13 +81,13 @@ namespace ImageSharp.Formats
Guard.NotNull(image, nameof(image));
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.
var writer = new EndianBinaryWriter(Endianness.LittleEndian, stream);
// Ensure that quality can be set but has a fallback.
int quality = this.options.Quality > 0 ? this.options.Quality : image.MetaData.Quality;
int quality = this.Quality;
quality = quality > 0 ? quality.Clamp(1, 256) : 256;
// Get the number of bits.
@ -240,7 +255,7 @@ namespace ImageSharp.Formats
private void WriteComments<TPixel>(Image<TPixel> image, EndianBinaryWriter writer)
where TPixel : struct, IPixel<TPixel>
{
if (this.options.IgnoreMetadata)
if (this.IgnoreMetadata)
{
return;
}
@ -251,7 +266,7 @@ namespace ImageSharp.Formats
return;
}
byte[] comments = this.options.TextEncoding.GetBytes(property.Value);
byte[] comments = this.TextEncoding.GetBytes(property.Value);
int count = Math.Min(comments.Length, 255);

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>
/// <param name="source">The image this method extends.</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>
/// <returns>
/// The <see cref="Image{TPixel}"/>.
/// </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>
{
GifEncoder encoder = new GifEncoder();
encoder.Encode(source, stream, options);
encoder = encoder ?? new GifEncoder();
encoder.Encode(source, stream);
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
{
using System;
using System.Collections.Generic;
using System.IO;
using ImageSharp.PixelFormats;
@ -15,15 +16,40 @@ namespace ImageSharp.Formats
/// </summary>
public interface IImageDecoder
{
/// <summary>
/// Gets the collection of mime types that this decoder supports decoding on.
/// </summary>
IEnumerable<string> MimeTypes { get; }
/// <summary>
/// Gets the collection of file extensionsthis decoder supports decoding.
/// </summary>
IEnumerable<string> FileExtensions { get; }
/// <summary>
/// Gets the size of the header for this image type.
/// </summary>
/// <value>The size of the header.</value>
int HeaderSize { get; }
/// <summary>
/// Returns a value indicating whether the <see cref="IImageDecoder"/> supports the specified
/// file header.
/// </summary>
/// <param name="header">The <see cref="T:byte[]"/> containing the file header.</param>
/// <returns>
/// True if the decoder supports the file header; otherwise, false.
/// </returns>
bool IsSupportedFileFormat(Span<byte> header);
/// <summary>
/// Decodes the image from the specified stream to the <see cref="ImageBase{TPixel}"/>.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="configuration">The configuration for the image.</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>
Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream, IDecoderOptions options)
Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
where TPixel : struct, IPixel<TPixel>;
}
}

14
src/ImageSharp/Formats/IImageEncoder.cs

@ -6,6 +6,7 @@
namespace ImageSharp.Formats
{
using System;
using System.Collections.Generic;
using System.IO;
using ImageSharp.PixelFormats;
@ -15,14 +16,23 @@ namespace ImageSharp.Formats
/// </summary>
public interface IImageEncoder
{
/// <summary>
/// Gets the collection of mime types that this decoder supports encoding for.
/// </summary>
IEnumerable<string> MimeTypes { get; }
/// <summary>
/// Gets the collection of file extensionsthis decoder supports encoding for.
/// </summary>
IEnumerable<string> FileExtensions { get; }
/// <summary>
/// Encodes the image to the specified stream from the <see cref="Image{TPixel}"/>.
/// </summary>
/// <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>
void Encode<TPixel>(Image<TPixel> image, Stream stream, IEncoderOptions options)
void Encode<TPixel>(Image<TPixel> image, Stream stream)
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>
/// <param name="source">The image this method extends.</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>
/// <returns>
/// The <see cref="Image{TPixel}"/>.
/// </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>
{
JpegEncoder encoder = new JpegEncoder();
encoder.Encode(source, stream, options);
encoder = encoder ?? new JpegEncoder();
encoder.Encode(source, stream);
return source;
}

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

@ -5,6 +5,8 @@
namespace ImageSharp.Formats
{
using System.Collections.Generic;
/// <summary>
/// Defines jpeg constants defined in the specification.
/// </summary>
@ -15,6 +17,16 @@ namespace ImageSharp.Formats
/// </summary>
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>
/// Represents high detail chroma horizontal subsampling.
/// </summary>

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

@ -6,6 +6,7 @@
namespace ImageSharp.Formats
{
using System;
using System.Collections.Generic;
using System.IO;
using ImageSharp.PixelFormats;
@ -15,16 +16,87 @@ namespace ImageSharp.Formats
/// </summary>
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/>
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>
{
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);
}
}
/// <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>
private static YCbCrToRgbTables yCbCrToRgbTables = YCbCrToRgbTables.Create();
/// <summary>
/// The decoder options.
/// </summary>
private readonly IDecoderOptions options;
/// <summary>
/// The global configuration
/// </summary>
@ -103,12 +98,10 @@ namespace ImageSharp.Formats
/// <summary>
/// Initializes a new instance of the <see cref="JpegDecoderCore" /> class.
/// </summary>
/// <param name="options">The decoder options.</param>
/// <param name="configuration">The configuration.</param>
public JpegDecoderCore(IDecoderOptions options, Configuration configuration)
public JpegDecoderCore(Configuration configuration)
{
this.configuration = configuration ?? Configuration.Default;
this.options = options ?? new DecoderOptions();
this.HuffmanTrees = HuffmanTree.CreateHuffmanTrees();
this.QuantizationTables = new Block8x8F[MaxTq + 1];
this.Temp = new byte[2 * Block8x8F.ScalarCount];
@ -190,6 +183,11 @@ namespace ImageSharp.Formats
/// </summary>
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>
/// Decodes the image from the specified <see cref="Stream"/> and sets
/// the data to image.
@ -938,7 +936,7 @@ namespace ImageSharp.Formats
/// <param name="metadata">The image.</param>
private void ProcessApp1Marker(int remaining, ImageMetaData metadata)
{
if (remaining < 6 || this.options.IgnoreMetadata)
if (remaining < 6 || this.IgnoreMetadata)
{
this.InputProcessor.Skip(remaining);
return;
@ -968,7 +966,7 @@ namespace ImageSharp.Formats
{
// Length is 14 though we only need to check 12.
const int Icclength = 14;
if (remaining < Icclength || this.options.IgnoreMetadata)
if (remaining < Icclength || this.IgnoreMetadata)
{
this.InputProcessor.Skip(remaining);
return;

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

@ -5,6 +5,8 @@
namespace ImageSharp.Formats
{
using System;
using System.Collections.Generic;
using System.IO;
using ImageSharp.PixelFormats;
@ -14,14 +16,29 @@ namespace ImageSharp.Formats
/// </summary>
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/>
public void Encode<TPixel>(Image<TPixel> image, Stream stream, IEncoderOptions options)
where TPixel : struct, IPixel<TPixel>
{
IJpegEncoderOptions gifOptions = JpegEncoderOptions.Create(options);
public IEnumerable<string> MimeTypes => JpegConstants.MimeTypes;
this.Encode(image, stream, gifOptions);
}
/// <inheritdoc/>
public IEnumerable<string> FileExtensions => JpegConstants.FileExtensions;
/// <summary>
/// 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>
/// <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, IJpegEncoderOptions options)
where TPixel : struct, IPixel<TPixel>
public void Encode<TPixel>(Image<TPixel> image, Stream stream)
where TPixel : struct, IPixel<TPixel>
{
JpegEncoderCore encode = new JpegEncoderCore(options);
encode.Encode(image, stream);
JpegEncoderCore encoder = new JpegEncoderCore();
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>
private readonly byte[] huffmanBuffer = new byte[179];
/// <summary>
/// The options for the encoder.
/// </summary>
private readonly IJpegEncoderOptions options;
/// <summary>
/// The accumulated bits to write to the stream.
/// </summary>
@ -154,20 +149,30 @@ namespace ImageSharp.Formats
/// </summary>
private Stream outputStream;
/// <summary>
/// The subsampling method to use.
/// </summary>
private JpegSubsample subsample;
/// <summary>
/// Initializes a new instance of the <see cref="JpegEncoderCore"/> class.
/// </summary>
/// <param name="options">The options for the encoder.</param>
public JpegEncoderCore(IJpegEncoderOptions options)
public JpegEncoderCore()
{
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>
/// Encode writes the image to the jpeg baseline format with the given options.
/// </summary>
@ -186,21 +191,13 @@ namespace ImageSharp.Formats
throw new ImageFormatException($"Image is too large to encode at {image.Width}x{image.Height}.");
}
// Ensure that quality can be set but has a fallback.
int quality = this.options.Quality > 0 ? this.options.Quality : image.MetaData.Quality;
if (quality == 0)
{
quality = 75;
}
quality = quality.Clamp(1, 100);
this.outputStream = stream;
this.subsample = this.options.Subsample ?? (quality >= 91 ? JpegSubsample.Ratio444 : JpegSubsample.Ratio420);
int quality = this.Quality.Clamp(1, 100);
// Convert from a quality rating to a scaling factor.
int scale;
if (quality < 50)
if (this.Quality < 50)
{
scale = 5000 / quality;
}
@ -788,7 +785,7 @@ namespace ImageSharp.Formats
private void WriteProfiles<TPixel>(Image<TPixel> image)
where TPixel : struct, IPixel<TPixel>
{
if (this.options.IgnoreMetadata)
if (this.IgnoreMetadata)
{
return;
}
@ -810,7 +807,7 @@ namespace ImageSharp.Formats
byte[] subsamples = { 0x22, 0x11, 0x11 };
byte[] chroma = { 0x00, 0x01, 0x01 };
switch (this.subsample)
switch (this.Subsample)
{
case JpegSubsample.Ratio444:
subsamples = new byte[] { 0x11, 0x11, 0x11 };
@ -866,7 +863,7 @@ namespace ImageSharp.Formats
// TODO: We should allow grayscale writing.
this.outputStream.Write(SosHeaderYCbCr, 0, SosHeaderYCbCr.Length);
switch (this.subsample)
switch (this.Subsample)
{
case JpegSubsample.Ratio444:
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>
/// <param name="source">The image this method extends.</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>
/// <returns>
/// The <see cref="Image{TPixel}"/>.
/// </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>
{
PngEncoder encoder = new PngEncoder();
encoder.Encode(source, stream, options);
encoder = encoder ?? new PngEncoder();
encoder.Encode(source, stream);
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
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using ImageSharp.PixelFormats;
/// <summary>
@ -32,14 +33,37 @@ namespace ImageSharp.Formats
/// </remarks>
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/>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream, IDecoderOptions options)
public IEnumerable<string> MimeTypes => PngConstants.MimeTypes;
where TPixel : struct, IPixel<TPixel>
{
IPngDecoderOptions pngOptions = PngDecoderOptions.Create(options);
/// <inheritdoc/>
public IEnumerable<string> FileExtensions => PngConstants.FileExtensions;
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>
@ -48,12 +72,13 @@ namespace ImageSharp.Formats
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="configuration">The configuration for the image.</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>
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>
{
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.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using ImageSharp.Memory;
using ImageSharp.PixelFormats;
@ -74,11 +74,6 @@ namespace ImageSharp.Formats
/// </summary>
private readonly char[] chars = new char[4];
/// <summary>
/// The decoder options.
/// </summary>
private readonly IPngDecoderOptions options;
/// <summary>
/// Reusable crc for validating chunks.
/// </summary>
@ -154,21 +149,31 @@ namespace ImageSharp.Formats
/// </summary>
private int currentRowBytesRead;
/// <summary>
/// Gets or sets the png color type
/// </summary>
private PngColorType pngColorType;
/// <summary>
/// Initializes a new instance of the <see cref="PngDecoderCore"/> class.
/// </summary>
/// <param name="options">The decoder options.</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.options = options ?? new PngDecoderOptions();
this.TextEncoding = encoding ?? PngConstants.DefaultEncoding;
}
/// <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>
public PngColorType PngColorType { get; set; }
public bool IgnoreMetadata { get; internal set; }
/// <summary>
/// Decodes the stream to the image.
@ -221,7 +226,6 @@ namespace ImageSharp.Formats
byte[] pal = new byte[currentChunk.Length];
Buffer.BlockCopy(currentChunk.Data, 0, pal, 0, currentChunk.Length);
this.palette = pal;
metadata.Quality = pal.Length / 3;
break;
case PngChunkTypes.PaletteAlpha:
byte[] alpha = new byte[currentChunk.Length];
@ -344,7 +348,7 @@ namespace ImageSharp.Formats
/// <returns>The <see cref="int"/></returns>
private int CalculateBytesPerPixel()
{
switch (this.PngColorType)
switch (this.pngColorType)
{
case PngColorType.Grayscale:
return 1;
@ -572,7 +576,7 @@ namespace ImageSharp.Formats
Span<TPixel> rowSpan = pixels.GetRowSpan(this.currentRow);
var scanlineBuffer = new Span<byte>(defilteredScanline, 1);
switch (this.PngColorType)
switch (this.pngColorType)
{
case PngColorType.Grayscale:
int factor = 255 / ((int)Math.Pow(2, this.header.BitDepth) - 1);
@ -731,7 +735,7 @@ namespace ImageSharp.Formats
{
var color = default(TPixel);
switch (this.PngColorType)
switch (this.pngColorType)
{
case PngColorType.Grayscale:
int factor = 255 / ((int)Math.Pow(2, this.header.BitDepth) - 1);
@ -896,7 +900,7 @@ namespace ImageSharp.Formats
/// <param name="length">The maximum length to read.</param>
private void ReadTextChunk(ImageMetaData metadata, byte[] data, int length)
{
if (this.options.IgnoreMetadata)
if (this.IgnoreMetadata)
{
return;
}
@ -912,8 +916,8 @@ namespace ImageSharp.Formats
}
}
string name = this.options.TextEncoding.GetString(data, 0, zeroIndex);
string value = this.options.TextEncoding.GetString(data, zeroIndex + 1, length - zeroIndex - 1);
string name = this.TextEncoding.GetString(data, 0, zeroIndex);
string value = this.TextEncoding.GetString(data, zeroIndex + 1, length - zeroIndex - 1);
metadata.Properties.Add(new ImageProperty(name, value));
}
@ -967,7 +971,7 @@ namespace ImageSharp.Formats
throw new NotSupportedException("The png specification only defines 'None' and 'Adam7' as interlaced methods.");
}
this.PngColorType = this.header.ColorType;
this.pngColorType = this.header.ColorType;
}
/// <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
{
using System.Collections.Generic;
using System.IO;
using ImageSharp.PixelFormats;
using ImageSharp.Quantizers;
/// <summary>
/// Image encoder for writing image data to a stream in png format.
@ -15,13 +17,55 @@ namespace ImageSharp.Formats
public class PngEncoder : IImageEncoder
{
/// <inheritdoc/>
public void Encode<TPixel>(Image<TPixel> image, Stream stream, IEncoderOptions options)
where TPixel : struct, IPixel<TPixel>
{
IPngEncoderOptions pngOptions = PngEncoderOptions.Create(options);
public IEnumerable<string> MimeTypes => PngConstants.MimeTypes;
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>
/// 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>
/// <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, IPngEncoderOptions options)
public void Encode<TPixel>(Image<TPixel> image, Stream stream)
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);
}
}

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

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

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>
internal interface IImage : IImageBase
{
/// <summary>
/// Gets the currently loaded image format.
/// </summary>
IImageFormat CurrentImageFormat { get; }
/// <summary>
/// Gets the meta data of the image.
/// </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="config">The configuration.</param>
/// <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!
int maxHeaderSize = config.MaxHeaderSize;
@ -32,14 +32,14 @@ namespace ImageSharp
return null;
}
IImageFormat format;
IImageDecoder format;
byte[] header = ArrayPool<byte>.Shared.Rent(maxHeaderSize);
try
{
long startPosition = stream.Position;
stream.Read(header, 0, maxHeaderSize);
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
{
@ -49,28 +49,28 @@ namespace ImageSharp
return format;
}
#pragma warning disable SA1008 // Opening parenthesis must be spaced correctly
/// <summary>
/// Decodes the image stream to the current image.
/// </summary>
/// <param name="stream">The stream.</param>
/// <param name="options">The options for the decoder.</param>
/// <param name="config">the configuration.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>
/// A new <see cref="Image{TPixel}"/>.
/// </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>
{
IImageFormat format = DiscoverFormat(stream, config);
if (format == null)
IImageDecoder decoder = DiscoverDecoder(stream, config);
if (decoder == null)
{
return null;
return (null, null);
}
Image<TPixel> img = format.Decoder.Decode<TPixel>(config, stream, options);
img.CurrentImageFormat = format;
return img;
Image<TPixel> img = decoder.Decode<TPixel>(config, stream);
return (img, decoder);
}
}
}

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

@ -20,15 +20,15 @@ namespace ImageSharp
/// </summary>
/// <param name="data">The byte array containing image data.</param>
/// <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>
/// Create a new instance of the <see cref="Image{Rgba32}"/> class from the given byte array.
/// </summary>
/// <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>
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>
/// 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="data">The byte array containing image data.</param>
/// <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>
/// 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>
/// <param name="config">The config for the decoder.</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>
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>
/// 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>
/// <param name="config">The configuration options.</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>
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>
/// 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>
/// <param name="config">The config for the decoder.</param>
/// <param name="data">The byte array containing image data.</param>
/// <param name="decoder">The decoder.</param>
/// <param name="options">The options for the decoder.</param>
/// <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>
/// 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)
where TPixel : struct, IPixel<TPixel>
{
return Load<TPixel>(null, data, null);
return Load<TPixel>(null, data);
}
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given byte array.
/// </summary>
/// <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>
/// <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>
{
return Load<TPixel>(null, data, options);
return Load<TPixel>(null, data, out mimeType);
}
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given byte array.
/// </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>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(Configuration config, byte[] data)
where TPixel : struct, IPixel<TPixel>
{
return Load<TPixel>(config, data, null);
using (MemoryStream ms = new MemoryStream(data))
{
return Load<TPixel>(config, ms);
}
}
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given byte array.
/// </summary>
/// <param name="config">The configuration options.</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>
/// <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>
{
return Load<TPixel>(data, decoder, null);
using (MemoryStream ms = new MemoryStream(data))
{
return Load<TPixel>(config, ms, out mimeType);
}
}
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given byte array.
/// </summary>
/// <param name="config">The configuration options.</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>
/// <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>
{
using (MemoryStream ms = new MemoryStream(data))
{
return Load<TPixel>(config, ms, options);
return Load<TPixel>(ms, decoder);
}
}
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given byte array.
/// </summary>
/// <param name="config">The Configuration.</param>
/// <param name="data">The byte array containing image data.</param>
/// <param name="decoder">The decoder.</param>
/// <param name="options">The options for the decoder.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <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>
{
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.
/// </summary>
/// <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">
/// Thrown if the stream is not readable nor seekable.
/// </exception>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<Rgba32> Load(string path, IDecoderOptions options) => Load<Rgba32>(path, options);
/// <returns>A new <see cref="Image{Rgba32}"/>.</returns>
public static Image<Rgba32> Load(string path, out string mimeType) => Load<Rgba32>(path, out mimeType);
/// <summary>
/// Create a new instance of the <see cref="Image{Rgba32}"/> class from the given file.
@ -51,37 +51,37 @@ namespace ImageSharp
/// <summary>
/// Create a new instance of the <see cref="Image{Rgba32}"/> class from the given file.
/// </summary>
/// <param name="config">The config for the decoder.</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">
/// Thrown if the stream is not readable nor seekable.
/// </exception>
/// <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>
/// Create a new instance of the <see cref="Image{Rgba32}"/> class from the given file.
/// </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="options">The options for the decoder.</param>
/// <param name="decoder">The decoder.</param>
/// <exception cref="NotSupportedException">
/// Thrown if the stream is not readable nor seekable.
/// </exception>
/// <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>
/// Create a new instance of the <see cref="Image{Rgba32}"/> class from the given file.
/// </summary>
/// <param name="path">The file path to the image.</param>
/// <param name="decoder">The decoder.</param>
/// <param name="options">The options for the decoder.</param>
/// <exception cref="NotSupportedException">
/// Thrown if the stream is not readable nor seekable.
/// </exception>
/// <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>
/// 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)
where TPixel : struct, IPixel<TPixel>
{
return Load<TPixel>(null, path, null);
return Load<TPixel>(null, path);
}
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given file.
/// </summary>
/// <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">
/// Thrown if the stream is not readable nor seekable.
/// </exception>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <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>
{
return Load<TPixel>(null, path, options);
return Load<TPixel>(null, path, out mimeType);
}
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given file.
/// </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>
/// <exception cref="NotSupportedException">
/// 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)
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>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given file.
/// </summary>
/// <param name="config">The configuration options.</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">
/// Thrown if the stream is not readable nor seekable.
/// </exception>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <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>
{
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>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given file.
/// </summary>
/// <param name="config">The configuration options.</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">
/// Thrown if the stream is not readable nor seekable.
/// </exception>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <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>
{
config = config ?? Configuration.Default;
using (Stream s = config.FileSystem.OpenRead(path))
{
return Load<TPixel>(config, s, options);
}
return Load<TPixel>(null, path, decoder);
}
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given file.
/// </summary>
/// <param name="config">The Configuration.</param>
/// <param name="path">The file path to the image.</param>
/// <param name="decoder">The decoder.</param>
/// <param name="options">The options for the decoder.</param>
/// <exception cref="NotSupportedException">
/// Thrown if the stream is not readable nor seekable.
/// </exception>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <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>
{
Configuration config = Configuration.Default;
config = config ?? Configuration.Default;
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.IO;
using System.Linq;
using System.Text;
using Formats;
@ -21,22 +22,22 @@ namespace ImageSharp
/// Create a new instance of the <see cref="Image{Rgba32}"/> class from the given stream.
/// </summary>
/// <param name="stream">The stream containing image information.</param>
/// <param name="mimeType">the mime type of the decoded image.</param>
/// <exception cref="NotSupportedException">
/// Thrown if the stream is not readable nor seekable.
/// </exception>
/// <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>
/// Create a new instance of the <see cref="Image{Rgba32}"/> class from the given stream.
/// </summary>
/// <param name="stream">The stream containing image information.</param>
/// <param name="options">The options for the decoder.</param>
/// <exception cref="NotSupportedException">
/// Thrown if the stream is not readable nor seekable.
/// </exception>
/// <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>
/// Create a new instance of the <see cref="Image{Rgba32}"/> class from the given stream.
@ -63,14 +64,14 @@ namespace ImageSharp
/// <summary>
/// Create a new instance of the <see cref="Image{Rgba32}"/> class from the given stream.
/// </summary>
/// <param name="config">The config for the decoder.</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>
/// <param name="mimeType">the mime type of the decoded image.</param>
/// <exception cref="NotSupportedException">
/// Thrown if the stream is not readable nor seekable.
/// </exception>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>>
public static Image<Rgba32> Load(Stream stream, IImageDecoder decoder, IDecoderOptions options) => Load<Rgba32>(stream, decoder, options);
/// <returns>A new <see cref="Image{Rgba32}"/>.</returns>>
public static Image<Rgba32> Load(Configuration config, Stream stream, out string mimeType) => Load<Rgba32>(config, stream, out mimeType);
/// <summary>
/// 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)
where TPixel : struct, IPixel<TPixel>
{
return Load<TPixel>(null, stream, null);
return Load<TPixel>(null, stream);
}
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given stream.
/// </summary>
/// <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">
/// Thrown if the stream is not readable nor seekable.
/// </exception>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <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>
{
return Load<TPixel>(null, stream, options);
return Load<TPixel>(null, stream, out mimeType);
}
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given stream.
/// </summary>
/// <param name="config">The config for the decoder.</param>
/// <param name="stream">The stream containing image information.</param>
/// <param name="decoder">The decoder.</param>
/// <exception cref="NotSupportedException">
/// Thrown if the stream is not readable nor seekable.
/// </exception>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <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>
{
return Load<TPixel>(config, stream, null);
return WithSeekableStream(stream, s => decoder.Decode<TPixel>(Configuration.Default, s));
}
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given stream.
/// </summary>
/// <param name="config">The Configuration.</param>
/// <param name="stream">The stream containing image information.</param>
/// <param name="decoder">The decoder.</param>
/// <exception cref="NotSupportedException">
@ -129,27 +131,26 @@ namespace ImageSharp
/// </exception>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <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>
{
return Load<TPixel>(stream, decoder, null);
return WithSeekableStream(stream, s => decoder.Decode<TPixel>(config, s));
}
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given stream.
/// </summary>
/// <param name="config">The configuration options.</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">
/// Thrown if the stream is not readable nor seekable.
/// </exception>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <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>
{
return WithSeekableStream(stream, s => decoder.Decode<TPixel>(Configuration.Default, s, options));
return Load<TPixel>(config, stream, out var _);
}
/// <summary>
@ -157,27 +158,30 @@ namespace ImageSharp
/// </summary>
/// <param name="config">The configuration options.</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">
/// Thrown if the stream is not readable nor seekable.
/// </exception>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>>
public static Image<TPixel> Load<TPixel>(Configuration config, Stream stream, IDecoderOptions options)
where TPixel : struct, IPixel<TPixel>
public static Image<TPixel> Load<TPixel>(Configuration config, Stream stream, out string mimeType)
where TPixel : struct, IPixel<TPixel>
{
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.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);
}

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

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

6
src/ImageSharp/MetaData/ImageMetaData.cs

@ -50,7 +50,6 @@ namespace ImageSharp
this.HorizontalResolution = other.HorizontalResolution;
this.VerticalResolution = other.VerticalResolution;
this.Quality = other.Quality;
this.FrameDelay = other.FrameDelay;
this.DisposalMethod = other.DisposalMethod;
this.RepeatCount = other.RepeatCount;
@ -127,11 +126,6 @@ namespace ImageSharp
/// <value>A list of image properties.</value>
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>
/// Gets or sets the number of times any animation is repeated.
/// <remarks>0 means to repeat indefinitely.</remarks>

4
tests/ImageSharp.Benchmarks/BenchmarkBase.cs

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

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

@ -53,9 +53,9 @@ namespace ImageSharp.Benchmarks.Image
{
using (MemoryStream memoryStream = new MemoryStream())
{
PngEncoderOptions options = new PngEncoderOptions() { Quantizer = new OctreeQuantizer<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())
{
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);
}
@ -75,7 +75,7 @@ namespace ImageSharp.Benchmarks.Image
{
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);
}
@ -86,7 +86,7 @@ namespace ImageSharp.Benchmarks.Image
{
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);
}
@ -97,7 +97,7 @@ namespace ImageSharp.Benchmarks.Image
{
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);
}

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

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

204
tests/ImageSharp.Tests/ConfigurationTests.cs

@ -36,7 +36,8 @@ namespace ImageSharp.Tests
{
var configuration = Configuration.CreateDefaultInstance();
Assert.Equal(4, configuration.ImageFormats.Count);
Assert.Equal(4, configuration.ImageDecoders.Count);
Assert.Equal(4, configuration.ImageDecoders.Count);
}
/// <summary>
@ -73,7 +74,8 @@ namespace ImageSharp.Tests
[Fact]
public void TestDefultConfigurationImageFormatsIsNotNull()
{
Assert.True(Configuration.Default.ImageFormats != null);
Assert.True(Configuration.Default.ImageDecoders != null);
Assert.True(Configuration.Default.ImageEncoders != null);
}
/// <summary>
@ -85,163 +87,25 @@ namespace ImageSharp.Tests
{
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>(() =>
{
Configuration.Default.AddImageFormat(format);
});
format = new TestFormat { MimeType = string.Empty };
Assert.Throws<ArgumentException>(() =>
{
Configuration.Default.AddImageFormat(format);
Configuration.Default.AddImageFormat((IImageDecoder)null);
});
}
/// <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>
/// Test that the default image constructors use default configuration.
/// </summary>
[Fact]
public void TestImageUsesDefaultConfiguration()
{
Configuration.Default.AddImageFormat(new PngFormat());
Configuration.Default.AddImageFormat(new PngDecoder());
var image = new Image<Rgba32>(1, 1);
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>
@ -250,60 +114,12 @@ namespace ImageSharp.Tests
[Fact]
public void TestImageCopiesConfiguration()
{
Configuration.Default.AddImageFormat(new PngFormat());
Configuration.Default.AddImageFormat(new PngDecoder());
var image = new Image<Rgba32>(1, 1);
var image2 = new Image<Rgba32>(image);
Assert.Equal(image2.Configuration.ParallelOptions, image.Configuration.ParallelOptions);
Assert.True(image2.Configuration.ImageFormats.SequenceEqual(image.Configuration.ImageFormats));
}
/// <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();
}
Assert.True(image2.Configuration.ImageDecoders.SequenceEqual(image.Configuration.ImageDecoders));
}
}
}

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

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

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

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

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

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

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

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

@ -23,18 +23,15 @@ namespace ImageSharp.Tests.Drawing
string path = this.CreateOutputDirectory("Drawing", "Lines");
using (Image<Rgba32> image = new Image<Rgba32>(500, 500))
{
using (FileStream output = File.OpenWrite($"{path}/Simple.png"))
{
image
.BackgroundColor(Rgba32.Blue)
.DrawLines(Rgba32.HotPink, 5,
new[] {
image
.BackgroundColor(Rgba32.Blue)
.DrawLines(Rgba32.HotPink, 5,
new[] {
new Vector2(10, 10),
new Vector2(200, 150),
new Vector2(50, 300)
})
.Save(output);
}
})
.Save($"{path}/Simple.png");
using (PixelAccessor<Rgba32> sourcePixels = image.Lock())
{
@ -53,19 +50,16 @@ namespace ImageSharp.Tests.Drawing
string path = this.CreateOutputDirectory("Drawing", "Lines");
using (Image<Rgba32> image = new Image<Rgba32>(500, 500))
{
using (FileStream output = File.OpenWrite($"{path}/Simple_noantialias.png"))
{
image
.BackgroundColor(Rgba32.Blue)
.DrawLines(Rgba32.HotPink, 5,
new[] {
image
.BackgroundColor(Rgba32.Blue)
.DrawLines(Rgba32.HotPink, 5,
new[] {
new Vector2(10, 10),
new Vector2(200, 150),
new Vector2(50, 300)
},
new GraphicsOptions(false))
.Save(output);
}
},
new GraphicsOptions(false))
.Save($"{path}/Simple_noantialias.png");
using (PixelAccessor<Rgba32> sourcePixels = image.Lock())
{
@ -84,18 +78,15 @@ namespace ImageSharp.Tests.Drawing
string path = this.CreateOutputDirectory("Drawing", "Lines");
using (Image<Rgba32> image = new Image<Rgba32>(500, 500))
{
using (FileStream output = File.OpenWrite($"{path}/Dashed.png"))
{
image
.BackgroundColor(Rgba32.Blue)
.DrawLines(Pens.Dash(Rgba32.HotPink, 5),
new[] {
image
.BackgroundColor(Rgba32.Blue)
.DrawLines(Pens.Dash(Rgba32.HotPink, 5),
new[] {
new Vector2(10, 10),
new Vector2(200, 150),
new Vector2(50, 300)
})
.Save(output);
}
})
.Save($"{path}/Dashed.png");
}
}
@ -105,18 +96,15 @@ namespace ImageSharp.Tests.Drawing
string path = this.CreateOutputDirectory("Drawing", "Lines");
using (Image<Rgba32> image = new Image<Rgba32>(500, 500))
{
using (FileStream output = File.OpenWrite($"{path}/Dot.png"))
{
image
.BackgroundColor(Rgba32.Blue)
.DrawLines(Pens.Dot(Rgba32.HotPink, 5),
new[] {
image
.BackgroundColor(Rgba32.Blue)
.DrawLines(Pens.Dot(Rgba32.HotPink, 5),
new[] {
new Vector2(10, 10),
new Vector2(200, 150),
new Vector2(50, 300)
})
.Save(output);
}
})
.Save($"{path}/Dot.png");
}
}
@ -126,18 +114,15 @@ namespace ImageSharp.Tests.Drawing
string path = this.CreateOutputDirectory("Drawing", "Lines");
using (Image<Rgba32> image = new Image<Rgba32>(500, 500))
{
using (FileStream output = File.OpenWrite($"{path}/DashDot.png"))
{
image
.BackgroundColor(Rgba32.Blue)
.DrawLines(Pens.DashDot(Rgba32.HotPink, 5),
new[] {
image
.BackgroundColor(Rgba32.Blue)
.DrawLines(Pens.DashDot(Rgba32.HotPink, 5),
new[] {
new Vector2(10, 10),
new Vector2(200, 150),
new Vector2(50, 300)
})
.Save(output);
}
})
.Save($"{path}/DashDot.png");
}
}
@ -147,17 +132,14 @@ namespace ImageSharp.Tests.Drawing
string path = this.CreateOutputDirectory("Drawing", "Lines");
Image<Rgba32> image = new Image<Rgba32>(500, 500);
using (FileStream output = File.OpenWrite($"{path}/DashDotDot.png"))
{
image
.BackgroundColor(Rgba32.Blue)
.DrawLines(Pens.DashDotDot(Rgba32.HotPink, 5), new[] {
image
.BackgroundColor(Rgba32.Blue)
.DrawLines(Pens.DashDotDot(Rgba32.HotPink, 5), new[] {
new Vector2(10, 10),
new Vector2(200, 150),
new Vector2(50, 300)
})
.Save(output);
}
})
.Save($"{path}/DashDotDot.png");
}
[Fact]
@ -169,21 +151,17 @@ namespace ImageSharp.Tests.Drawing
Image<Rgba32> image = new Image<Rgba32>(500, 500);
using (FileStream output = File.OpenWrite($"{path}/Opacity.png"))
{
image
.BackgroundColor(Rgba32.Blue)
.DrawLines(color, 10, new[] {
image
.BackgroundColor(Rgba32.Blue)
.DrawLines(color, 10, new[] {
new Vector2(10, 10),
new Vector2(200, 150),
new Vector2(50, 300)
})
.Save(output);
}
})
.Save($"{path}/Opacity.png");
//shift background color towards forground color by the opacity amount
Rgba32 mergedColor = new Rgba32(Vector4.Lerp(Rgba32.Blue.ToVector4(), Rgba32.HotPink.ToVector4(), 150f/255f));
Rgba32 mergedColor = new Rgba32(Vector4.Lerp(Rgba32.Blue.ToVector4(), Rgba32.HotPink.ToVector4(), 150f / 255f));
using (PixelAccessor<Rgba32> sourcePixels = image.Lock())
{
@ -202,18 +180,15 @@ namespace ImageSharp.Tests.Drawing
Image<Rgba32> image = new Image<Rgba32>(500, 500);
using (FileStream output = File.OpenWrite($"{path}/Rectangle.png"))
{
image
.BackgroundColor(Rgba32.Blue)
.DrawLines(Rgba32.HotPink, 10, new[] {
image
.BackgroundColor(Rgba32.Blue)
.DrawLines(Rgba32.HotPink, 10, new[] {
new Vector2(10, 10),
new Vector2(200, 10),
new Vector2(200, 150),
new Vector2(10, 150)
})
.Save(output);
}
})
.Save($"{path}/Rectangle.png");
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 (FileStream output = File.OpenWrite($"{path}/Simple.png"))
{
image
.BackgroundColor(Rgba32.Blue)
.DrawPolygon(Rgba32.HotPink, 5,
@ -34,8 +32,7 @@ namespace ImageSharp.Tests.Drawing
new Vector2(200, 150),
new Vector2(50, 300)
})
.Save(output);
}
.Save($"{path}/Simple.png");
using (PixelAccessor<Rgba32> sourcePixels = image.Lock())
{
@ -64,13 +61,10 @@ namespace ImageSharp.Tests.Drawing
using (Image<Rgba32> image = new Image<Rgba32>(500, 500))
{
using (FileStream output = File.OpenWrite($"{path}/Opacity.png"))
{
image
.BackgroundColor(Rgba32.Blue)
.DrawPolygon(color, 10, simplePath)
.Save(output);
}
.Save($"{path}/Opacity.png");
//shift background color towards forground color by the opacity amount
Rgba32 mergedColor = new Rgba32(Vector4.Lerp(Rgba32.Blue.ToVector4(), Rgba32.HotPink.ToVector4(), 150f / 255f));
@ -95,13 +89,10 @@ namespace ImageSharp.Tests.Drawing
using (Image<Rgba32> image = new Image<Rgba32>(500, 500))
{
using (FileStream output = File.OpenWrite($"{path}/Rectangle.png"))
{
image
.BackgroundColor(Rgba32.Blue)
.Draw(Rgba32.HotPink, 10, new Rectangle(10, 10, 190, 140))
.Save(output);
}
.Save($"{path}/Rectangle.png");
using (PixelAccessor<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 (FileStream output = File.OpenWrite($"{path}/{file.FileName}"))
{
image.Fill(brush)
.Save(output);
}
image.Fill(brush)
.Save($"{path}/{file.FileName}");
}
}
}
@ -46,12 +43,9 @@ namespace ImageSharp.Tests
{
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))
.Save(output);
}
int imageHeight = image.Height;
image.Fill(brush, new Rectangle(0, imageHeight / 2 - imageHeight / 4, image.Width, imageHeight / 2))
.Save($"{path}/Shaped_{file.FileName}");
}
}
}

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

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

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

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

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

@ -31,7 +31,7 @@ namespace ImageSharp.Tests
string filename = file.GetFileNameWithoutExtension(bitsPerPixel);
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())
{
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 (FileStream output = File.OpenWrite($"{path}/{file.FileName}"))
{
image.Save(output);
}
image.Save($"{path}/{file.FileName}");
}
}
}
@ -65,14 +62,14 @@ namespace ImageSharp.Tests
foreach (TestFile file in Files)
{
using (Image<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 (FileStream output = File.OpenWrite($"{path}/Octree-{file.FileName}"))
{
image.Quantize(Quantization.Octree)
.Save(output, image.CurrentImageFormat);
.Save(output, mimeType);
}
}
@ -82,7 +79,7 @@ namespace ImageSharp.Tests
using (FileStream output = File.OpenWrite($"{path}/Wu-{file.FileName}"))
{
image.Quantize(Quantization.Wu)
.Save(output, image.CurrentImageFormat);
.Save(output, mimeType);
}
}
@ -91,7 +88,7 @@ namespace ImageSharp.Tests
using (FileStream output = File.OpenWrite($"{path}/Palette-{file.FileName}"))
{
image.Quantize(Quantization.Palette)
.Save(output, image.CurrentImageFormat);
.Save(output, mimeType);
}
}
}
@ -138,18 +135,17 @@ namespace ImageSharp.Tests
foreach (TestFile file in Files)
{
byte[] serialized;
using (Image<Rgba32> image = file.CreateImage())
using (Image<Rgba32> image = Image.Load(file.Bytes, out string mimeType))
using (MemoryStream memoryStream = new MemoryStream())
{
image.Save(memoryStream);
image.Save(memoryStream, mimeType);
memoryStream.Flush();
serialized = memoryStream.ToArray();
}
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]
public void Decode_IgnoreMetadataIsFalse_CommentsAreRead()
{
DecoderOptions options = new DecoderOptions()
GifDecoder options = new GifDecoder()
{
IgnoreMetadata = false
};
@ -51,7 +51,7 @@ namespace ImageSharp.Tests
[Fact]
public void Decode_IgnoreMetadataIsTrue_CommentsAreIgnored()
{
DecoderOptions options = new DecoderOptions()
GifDecoder options = new GifDecoder()
{
IgnoreMetadata = true
};
@ -67,7 +67,7 @@ namespace ImageSharp.Tests
[Fact]
public void Decode_TextEncodingSetToUnicode_TextIsReadWithCorrectEncoding()
{
GifDecoderOptions options = new GifDecoderOptions()
GifDecoder options = new GifDecoder()
{
TextEncoding = Encoding.Unicode
};

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

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

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

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

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

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

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

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

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

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

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

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

@ -50,8 +50,7 @@ namespace ImageSharp.Tests.Formats.Png
using (MemoryStream ms = new MemoryStream())
{
// image.Save(provider.Utility.GetTestOutputFileName("bmp"));
image.MetaData.Quality = 256;
image.Save(ms, new PngEncoder());
image.Save(ms, new PngEncoder() { Quality = 256 });
ms.Position = 0;
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
{
private readonly Mock<IFileSystem> fileSystem;
private readonly IDecoderOptions decoderOptions;
private Image<Rgba32> returnImage;
private Mock<IImageDecoder> localDecoder;
private Mock<IImageFormat> localFormat;
private readonly string FilePath;
public Configuration LocalConfiguration { get; private set; }
@ -36,18 +34,14 @@ namespace ImageSharp.Tests
this.returnImage = new Image<Rgba32>(1, 1);
this.localDecoder = new Mock<IImageDecoder>();
this.localFormat = new Mock<IImageFormat>();
this.localFormat.Setup(x => x.Decoder).Returns(this.localDecoder.Object);
this.localFormat.Setup(x => x.Encoder).Returns(new Mock<IImageEncoder>().Object);
this.localFormat.Setup(x => x.MimeType).Returns("img/test");
this.localFormat.Setup(x => x.Extension).Returns("png");
this.localFormat.Setup(x => x.HeaderSize).Returns(1);
this.localFormat.Setup(x => x.IsSupportedFileFormat(It.IsAny<byte[]>())).Returns(true);
this.localFormat.Setup(x => x.SupportedExtensions).Returns(new string[] { "png", "jpg" });
this.localDecoder.Setup(x => x.Decode<Rgba32>(It.IsAny<Configuration>(), It.IsAny<Stream>(), It.IsAny<IDecoderOptions>()))
.Callback<Configuration, Stream, IDecoderOptions>((c, s, o) => {
this.localDecoder.Setup(x => x.MimeTypes).Returns(new[] { "img/test" });
this.localDecoder.Setup(x => x.FileExtensions).Returns(new[] { "png", "jpg" });
this.localDecoder.Setup(x => x.HeaderSize).Returns(1);
this.localDecoder.Setup(x => x.IsSupportedFileFormat(It.IsAny<Span<byte>>())).Returns(true);
this.localDecoder.Setup(x => x.Decode<Rgba32>(It.IsAny<Configuration>(), It.IsAny<Stream>()))
.Callback<Configuration, Stream>((c, s) => {
using (var ms = new MemoryStream())
{
s.CopyTo(ms);
@ -58,14 +52,16 @@ namespace ImageSharp.Tests
this.fileSystem = new Mock<IFileSystem>();
this.LocalConfiguration = new Configuration(this.localFormat.Object)
this.LocalConfiguration = new Configuration()
{
FileSystem = this.fileSystem.Object
};
this.LocalConfiguration.AddImageFormat(this.localDecoder.Object);
TestFormat.RegisterGloablTestFormat();
this.Marker = Guid.NewGuid().ToByteArray();
this.DataStream = TestFormat.GlobalTestFormat.CreateStream(this.Marker);
this.decoderOptions = new Mock<IDecoderOptions>().Object;
this.FilePath = Guid.NewGuid().ToString();
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);
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);
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.Equal(TestFormat.GlobalTestFormat.Sample<Rgba32>(), img);
Assert.Equal(TestFormat.GlobalTestFormat, img.CurrentImageFormat);
TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, null, 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);
TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, Configuration.Default);
}
[Fact]
public void LoadFromStreamWithConfig()
@ -146,9 +113,8 @@ namespace ImageSharp.Tests
Image<Rgba32> img = Image.Load<Rgba32>(this.LocalConfiguration, stream);
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.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]
public void LoadFromStreamWithDecoder()
@ -202,7 +139,7 @@ namespace ImageSharp.Tests
Image<Rgba32> img = Image.Load<Rgba32>(stream, this.localDecoder.Object);
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]
@ -213,28 +150,7 @@ namespace ImageSharp.Tests
Assert.NotNull(img);
Assert.Equal(this.returnImage, img);
this.localDecoder.Verify(x => x.Decode<Rgba32>(Configuration.Default, stream, null));
}
[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));
this.localDecoder.Verify(x => x.Decode<Rgba32>(Configuration.Default, stream));
}
[Fact]
@ -243,10 +159,9 @@ namespace ImageSharp.Tests
Image<Rgba32> img = Image.Load<Rgba32>(this.DataStream.ToArray());
Assert.NotNull(img);
Assert.Equal(TestFormat.GlobalTestFormat, img.CurrentImageFormat);
TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, null, Configuration.Default);
TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, Configuration.Default);
}
@ -257,34 +172,8 @@ namespace ImageSharp.Tests
Assert.NotNull(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());
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);
}
@ -308,49 +196,19 @@ namespace ImageSharp.Tests
Assert.NotNull(img);
Assert.Equal(this.returnImage, img);
Assert.Equal(this.localFormat.Object, img.CurrentImageFormat);
this.localDecoder.Verify(x => x.Decode<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);
}
[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]
public void LoadFromBytesWithDecoder()
{
Image<Rgba32> img = Image.Load<Rgba32>(this.DataStream.ToArray(), this.localDecoder.Object);
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);
}
@ -361,28 +219,7 @@ namespace ImageSharp.Tests
Assert.NotNull(img);
Assert.Equal(this.returnImage, img);
this.localDecoder.Verify(x => x.Decode<Rgba32>(Configuration.Default, It.IsAny<Stream>(), null));
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));
this.localDecoder.Verify(x => x.Decode<Rgba32>(Configuration.Default, It.IsAny<Stream>()));
Assert.Equal(this.DataStream.ToArray(), this.DecodedData);
}
@ -392,10 +229,9 @@ namespace ImageSharp.Tests
Image<Rgba32> img = Image.Load<Rgba32>(this.DataStream);
Assert.NotNull(img);
Assert.Equal(TestFormat.GlobalTestFormat, img.CurrentImageFormat);
TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, null, Configuration.Default);
TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, Configuration.Default);
}
@ -406,35 +242,8 @@ namespace ImageSharp.Tests
Assert.NotNull(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]
@ -443,9 +252,8 @@ namespace ImageSharp.Tests
Image<Rgba32> img = Image.Load<Rgba32>(this.LocalConfiguration, this.FilePath);
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.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]
public void LoadFromFileWithDecoder()
{
Image<Rgba32> img = Image.Load<Rgba32>(this.FilePath, this.localDecoder.Object);
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]
@ -504,26 +284,7 @@ namespace ImageSharp.Tests
Assert.NotNull(img);
Assert.Equal(this.returnImage, img);
this.localDecoder.Verify(x => x.Decode<Rgba32>(Configuration.Default, this.DataStream, null));
}
[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));
this.localDecoder.Verify(x => x.Decode<Rgba32>(Configuration.Default, this.DataStream));
}
[Fact]

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

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

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

@ -74,10 +74,9 @@ namespace ImageSharp.Tests
image.Save(file);
}
TestFile c = TestFile.Create("../../TestOutput/Save_DetecedEncoding.png");
using (Image<Rgba32> img = c.CreateImage())
using (Image<Rgba32> img = Image.Load(file, out var mime))
{
Assert.IsType<PngFormat>(img.CurrentImageFormat);
Assert.Equal("image/png", mime);
}
}
@ -85,7 +84,7 @@ namespace ImageSharp.Tests
public void Save_UnknownExtensionsEncoding()
{
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))
@ -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]
public void Save_SetEncoding()
{
@ -121,11 +103,9 @@ namespace ImageSharp.Tests
{
image.Save(file, new PngEncoder());
}
TestFile c = TestFile.Create("../../TestOutput/Save_SetEncoding.dat");
using (Image<Rgba32> img = c.CreateImage())
using (Image<Rgba32> img = Image.Load(file, out var mime))
{
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.VerticalResolution = 2;
metaData.Properties.Add(imageProperty);
metaData.Quality = 24;
metaData.RepeatCount = 1;
metaData.DisposalMethod = DisposalMethod.RestoreToBackground;
@ -39,7 +38,6 @@ namespace ImageSharp.Tests
Assert.Equal(4, clone.HorizontalResolution);
Assert.Equal(2, clone.VerticalResolution);
Assert.Equal(imageProperty, clone.Properties[0]);
Assert.Equal(24, clone.Quality);
Assert.Equal(1, clone.RepeatCount);
Assert.Equal(DisposalMethod.RestoreToBackground, clone.DisposalMethod);
}

7
tests/ImageSharp.Tests/TestFile.cs

@ -11,7 +11,7 @@ namespace ImageSharp.Tests
using System.IO;
using System.Linq;
using System.Reflection;
using ImageSharp.Formats;
using ImageSharp.PixelFormats;
/// <summary>
@ -135,13 +135,12 @@ namespace ImageSharp.Tests
/// <summary>
/// Creates a new image.
/// </summary>
/// <param name="options">The options for the decoder.</param>
/// <returns>
/// The <see cref="Image"/>.
/// </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>

34
tests/ImageSharp.Tests/TestFormat.cs

@ -19,13 +19,14 @@ namespace ImageSharp.Tests
/// <summary>
/// A test image file.
/// </summary>
public class TestFormat : ImageSharp.Formats.IImageFormat
public class TestFormat
{
public static TestFormat GlobalTestFormat { get; } = new TestFormat();
public static void RegisterGloablTestFormat()
{
Configuration.Default.AddImageFormat(GlobalTestFormat);
Configuration.Default.AddImageFormat(GlobalTestFormat.Encoder);
Configuration.Default.AddImageFormat(GlobalTestFormat.Decoder);
}
public TestFormat()
@ -58,9 +59,9 @@ namespace ImageSharp.Tests
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");
@ -92,7 +93,7 @@ namespace ImageSharp.Tests
public int HeaderSize => this.header.Length;
public bool IsSupportedFileFormat(byte[] header)
public bool IsSupportedFileFormat(Span<byte> header)
{
if (header.Length < this.header.Length)
{
@ -110,15 +111,10 @@ namespace ImageSharp.Tests
public struct DecodeOperation
{
public byte[] marker;
public IDecoderOptions options;
internal Configuration config;
public bool IsMatch(byte[] testMarker, IDecoderOptions testOptions, Configuration config)
public bool IsMatch(byte[] testMarker, Configuration config)
{
if (this.options != testOptions)
{
return false;
}
if (this.config != config)
{
@ -150,8 +146,13 @@ namespace ImageSharp.Tests
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();
@ -160,13 +161,14 @@ namespace ImageSharp.Tests
this.testFormat.DecodeCalls.Add(new DecodeOperation
{
marker = marker,
options = options,
config = config
});
// TODO record this happend so we an verify it.
return this.testFormat.Sample<TPixel>();
}
public bool IsSupportedFileFormat(Span<byte> header) => testFormat.IsSupportedFileFormat(header);
}
public class TestEncoder : ImageSharp.Formats.IImageEncoder
@ -178,7 +180,11 @@ namespace ImageSharp.Tests
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.
}

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

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

Loading…
Cancel
Save