Browse Source

Merge branch 'master' into jpeg-port

pull/298/head
James Jackson-South 9 years ago
parent
commit
1e8f899397
  1. 17
      ImageSharp.sln
  2. 12
      samples/ChangeDefaultEncoderOptions/ChangeDefaultEncoderOptions.csproj
  3. 20
      samples/ChangeDefaultEncoderOptions/Program.cs
  4. 2
      src/ImageSharp/Common/Exceptions/ImageFormatException.cs
  5. 2
      src/ImageSharp/Common/Exceptions/ImageProcessingException.cs
  6. 233
      src/ImageSharp/Configuration.cs
  7. 21
      src/ImageSharp/Formats/Bmp/BmpConfigurationModule.cs
  8. 25
      src/ImageSharp/Formats/Bmp/BmpConstants.cs
  9. 7
      src/ImageSharp/Formats/Bmp/BmpDecoder.cs
  10. 3
      src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
  11. 25
      src/ImageSharp/Formats/Bmp/BmpEncoder.cs
  12. 18
      src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
  13. 45
      src/ImageSharp/Formats/Bmp/BmpEncoderOptions.cs
  14. 2
      src/ImageSharp/Formats/Bmp/BmpFileHeader.cs
  15. 28
      src/ImageSharp/Formats/Bmp/BmpFormat.cs
  16. 37
      src/ImageSharp/Formats/Bmp/BmpImageFormatDetector.cs
  17. 2
      src/ImageSharp/Formats/Bmp/BmpInfoHeader.cs
  18. 21
      src/ImageSharp/Formats/Bmp/IBmpDecoderOptions.cs
  19. 13
      src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs
  20. 37
      src/ImageSharp/Formats/DecoderOptions.cs
  21. 37
      src/ImageSharp/Formats/EncoderOptions.cs
  22. 22
      src/ImageSharp/Formats/Gif/GifConfigurationModule.cs
  23. 13
      src/ImageSharp/Formats/Gif/GifConstants.cs
  24. 33
      src/ImageSharp/Formats/Gif/GifDecoder.cs
  25. 30
      src/ImageSharp/Formats/Gif/GifDecoderCore.cs
  26. 47
      src/ImageSharp/Formats/Gif/GifDecoderOptions.cs
  27. 44
      src/ImageSharp/Formats/Gif/GifEncoder.cs
  28. 66
      src/ImageSharp/Formats/Gif/GifEncoderCore.cs
  29. 65
      src/ImageSharp/Formats/Gif/GifEncoderOptions.cs
  30. 30
      src/ImageSharp/Formats/Gif/GifFormat.cs
  31. 41
      src/ImageSharp/Formats/Gif/GifImageFormatDetector.cs
  32. 15
      src/ImageSharp/Formats/Gif/IGifDecoderOptions.cs
  33. 23
      src/ImageSharp/Formats/Gif/IGifEncoderOptions.cs
  34. 8
      src/ImageSharp/Formats/Gif/ImageExtensions.cs
  35. 18
      src/ImageSharp/Formats/IEncoderOptions.cs
  36. 4
      src/ImageSharp/Formats/IImageDecoder.cs
  37. 4
      src/ImageSharp/Formats/IImageEncoder.cs
  38. 43
      src/ImageSharp/Formats/IImageFormat.cs
  39. 30
      src/ImageSharp/Formats/IImageFormatDetector.cs
  40. 14
      src/ImageSharp/Formats/Jpeg/IJpegDecoderOptions.cs
  41. 17
      src/ImageSharp/Formats/Jpeg/IJpegEncoderOptions.cs
  42. 8
      src/ImageSharp/Formats/Jpeg/ImageExtensions.cs
  43. 22
      src/ImageSharp/Formats/Jpeg/JpegConfigurationModule.cs
  44. 12
      src/ImageSharp/Formats/Jpeg/JpegConstants.cs
  45. 17
      src/ImageSharp/Formats/Jpeg/JpegDecoder.cs
  46. 22
      src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
  47. 34
      src/ImageSharp/Formats/Jpeg/JpegEncoder.cs
  48. 52
      src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
  49. 56
      src/ImageSharp/Formats/Jpeg/JpegEncoderOptions.cs
  50. 74
      src/ImageSharp/Formats/Jpeg/JpegFormat.cs
  51. 87
      src/ImageSharp/Formats/Jpeg/JpegImageFormatDetector.cs
  52. 20
      src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs
  53. 15
      src/ImageSharp/Formats/Png/IPngDecoderOptions.cs
  54. 26
      src/ImageSharp/Formats/Png/IPngEncoderOptions.cs
  55. 8
      src/ImageSharp/Formats/Png/ImageExtensions.cs
  56. 21
      src/ImageSharp/Formats/Png/PngConfigurationModule.cs
  57. 31
      src/ImageSharp/Formats/Png/PngConstants.cs
  58. 27
      src/ImageSharp/Formats/Png/PngDecoder.cs
  59. 51
      src/ImageSharp/Formats/Png/PngDecoderCore.cs
  60. 49
      src/ImageSharp/Formats/Png/PngDecoderOptions.cs
  61. 61
      src/ImageSharp/Formats/Png/PngEncoder.cs
  62. 80
      src/ImageSharp/Formats/Png/PngEncoderCore.cs
  63. 82
      src/ImageSharp/Formats/Png/PngEncoderOptions.cs
  64. 32
      src/ImageSharp/Formats/Png/PngFormat.cs
  65. 2
      src/ImageSharp/Formats/Png/PngHeader.cs
  66. 43
      src/ImageSharp/Formats/Png/PngImageFormatDetector.cs
  67. 2
      src/ImageSharp/Formats/Png/PngInterlaceMode.cs
  68. 2
      src/ImageSharp/Formats/Png/Zlib/IChecksum.cs
  69. 2
      src/ImageSharp/Formats/Png/Zlib/ZlibInflateStream.cs
  70. 19
      src/ImageSharp/IConfigurationModule.cs
  71. 5
      src/ImageSharp/Image/IImage.cs
  72. 49
      src/ImageSharp/Image/Image.Decode.cs
  73. 94
      src/ImageSharp/Image/Image.FromBytes.cs
  74. 93
      src/ImageSharp/Image/Image.FromFile.cs
  75. 90
      src/ImageSharp/Image/Image.FromStream.cs
  76. 158
      src/ImageSharp/Image/Image{TPixel}.cs
  77. 35
      src/ImageSharp/ImageFormats.cs
  78. 1
      src/ImageSharp/ImageSharp.csproj
  79. 10
      src/ImageSharp/Memory/Buffer.cs
  80. 6
      src/ImageSharp/MetaData/ImageMetaData.cs
  81. 4
      tests/ImageSharp.Benchmarks/BenchmarkBase.cs
  82. 12
      tests/ImageSharp.Benchmarks/Image/EncodeIndexedPng.cs
  83. 2
      tests/ImageSharp.Benchmarks/Image/EncodePng.cs
  84. 249
      tests/ImageSharp.Tests/ConfigurationTests.cs
  85. 12
      tests/ImageSharp.Tests/Drawing/BeziersTests.cs
  86. 15
      tests/ImageSharp.Tests/Drawing/DrawPathTests.cs
  87. 10
      tests/ImageSharp.Tests/Drawing/FillPatternTests.cs
  88. 18
      tests/ImageSharp.Tests/Drawing/FillSolidBrushTests.cs
  89. 25
      tests/ImageSharp.Tests/Drawing/LineComplexPolygonTests.cs
  90. 47
      tests/ImageSharp.Tests/Drawing/LineTests.cs
  91. 15
      tests/ImageSharp.Tests/Drawing/PolygonTests.cs
  92. 12
      tests/ImageSharp.Tests/Drawing/RecolorImageTest.cs
  93. 10
      tests/ImageSharp.Tests/Drawing/SolidBezierTests.cs
  94. 15
      tests/ImageSharp.Tests/Drawing/SolidComplexPolygonTests.cs
  95. 48
      tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs
  96. 2
      tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs
  97. 24
      tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs
  98. 6
      tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs
  99. 8
      tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs
  100. 13
      tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs

17
ImageSharp.sln

@ -1,7 +1,7 @@
 
Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15 # Visual Studio 15
VisualStudioVersion = 15.0.26430.6 VisualStudioVersion = 15.0.26430.14
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SolutionItems", "SolutionItems", "{C317F1B1-D75E-4C6D-83EB-80367343E0D7}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SolutionItems", "SolutionItems", "{C317F1B1-D75E-4C6D-83EB-80367343E0D7}"
ProjectSection(SolutionItems) = preProject ProjectSection(SolutionItems) = preProject
@ -49,6 +49,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{7CC6
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AvatarWithRoundedCorner", "samples\AvatarWithRoundedCorner\AvatarWithRoundedCorner.csproj", "{844FC582-4E78-4371-847D-EFD4D1103578}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AvatarWithRoundedCorner", "samples\AvatarWithRoundedCorner\AvatarWithRoundedCorner.csproj", "{844FC582-4E78-4371-847D-EFD4D1103578}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChangeDefaultEncoderOptions", "samples\ChangeDefaultEncoderOptions\ChangeDefaultEncoderOptions.csproj", "{07EE511D-4BAB-4323-BAFC-3AF2BF9366F0}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -143,6 +145,18 @@ Global
{844FC582-4E78-4371-847D-EFD4D1103578}.Release|x64.Build.0 = Release|Any CPU {844FC582-4E78-4371-847D-EFD4D1103578}.Release|x64.Build.0 = Release|Any CPU
{844FC582-4E78-4371-847D-EFD4D1103578}.Release|x86.ActiveCfg = Release|Any CPU {844FC582-4E78-4371-847D-EFD4D1103578}.Release|x86.ActiveCfg = Release|Any CPU
{844FC582-4E78-4371-847D-EFD4D1103578}.Release|x86.Build.0 = Release|Any CPU {844FC582-4E78-4371-847D-EFD4D1103578}.Release|x86.Build.0 = Release|Any CPU
{07EE511D-4BAB-4323-BAFC-3AF2BF9366F0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{07EE511D-4BAB-4323-BAFC-3AF2BF9366F0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{07EE511D-4BAB-4323-BAFC-3AF2BF9366F0}.Debug|x64.ActiveCfg = Debug|Any CPU
{07EE511D-4BAB-4323-BAFC-3AF2BF9366F0}.Debug|x64.Build.0 = Debug|Any CPU
{07EE511D-4BAB-4323-BAFC-3AF2BF9366F0}.Debug|x86.ActiveCfg = Debug|Any CPU
{07EE511D-4BAB-4323-BAFC-3AF2BF9366F0}.Debug|x86.Build.0 = Debug|Any CPU
{07EE511D-4BAB-4323-BAFC-3AF2BF9366F0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{07EE511D-4BAB-4323-BAFC-3AF2BF9366F0}.Release|Any CPU.Build.0 = Release|Any CPU
{07EE511D-4BAB-4323-BAFC-3AF2BF9366F0}.Release|x64.ActiveCfg = Release|Any CPU
{07EE511D-4BAB-4323-BAFC-3AF2BF9366F0}.Release|x64.Build.0 = Release|Any CPU
{07EE511D-4BAB-4323-BAFC-3AF2BF9366F0}.Release|x86.ActiveCfg = Release|Any CPU
{07EE511D-4BAB-4323-BAFC-3AF2BF9366F0}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@ -156,5 +170,6 @@ Global
{2BF743D8-2A06-412D-96D7-F448F00C5EA5} = {56801022-D71A-4FBE-BC5B-CBA08E2284EC} {2BF743D8-2A06-412D-96D7-F448F00C5EA5} = {56801022-D71A-4FBE-BC5B-CBA08E2284EC}
{96188137-5FA6-4924-AB6E-4EFF79C6E0BB} = {56801022-D71A-4FBE-BC5B-CBA08E2284EC} {96188137-5FA6-4924-AB6E-4EFF79C6E0BB} = {56801022-D71A-4FBE-BC5B-CBA08E2284EC}
{844FC582-4E78-4371-847D-EFD4D1103578} = {7CC6D57E-B916-43B8-B315-A0BB92F260A2} {844FC582-4E78-4371-847D-EFD4D1103578} = {7CC6D57E-B916-43B8-B315-A0BB92F260A2}
{07EE511D-4BAB-4323-BAFC-3AF2BF9366F0} = {7CC6D57E-B916-43B8-B315-A0BB92F260A2}
EndGlobalSection EndGlobalSection
EndGlobal EndGlobal

12
samples/ChangeDefaultEncoderOptions/ChangeDefaultEncoderOptions.csproj

@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp1.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\ImageSharp\ImageSharp.csproj" />
</ItemGroup>
</Project>

20
samples/ChangeDefaultEncoderOptions/Program.cs

@ -0,0 +1,20 @@
using System;
using ImageSharp;
using ImageSharp.Formats;
namespace ChangeDefaultEncoderOptions
{
class Program
{
static void Main(string[] args)
{
// lets switch out the default encoder for jpeg to one
// that saves at 90 quality and ignores the matadata
Configuration.Default.SetEncoder(ImageFormats.Jpeg, new ImageSharp.Formats.JpegEncoder()
{
Quality = 90,
IgnoreMetadata = true
});
}
}
}

2
src/ImageSharp/Common/Exceptions/ImageFormatException.cs

@ -11,7 +11,7 @@ namespace ImageSharp
/// The exception that is thrown when the library tries to load /// The exception that is thrown when the library tries to load
/// an image, which has an invalid format. /// an image, which has an invalid format.
/// </summary> /// </summary>
public class ImageFormatException : Exception public sealed class ImageFormatException : Exception
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ImageFormatException"/> class. /// Initializes a new instance of the <see cref="ImageFormatException"/> class.

2
src/ImageSharp/Common/Exceptions/ImageProcessingException.cs

@ -10,7 +10,7 @@ namespace ImageSharp
/// <summary> /// <summary>
/// The exception that is thrown when an error occurs when applying a process to an image. /// The exception that is thrown when an error occurs when applying a process to an image.
/// </summary> /// </summary>
public class ImageProcessingException : Exception public sealed class ImageProcessingException : Exception
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ImageProcessingException"/> class. /// Initializes a new instance of the <see cref="ImageProcessingException"/> class.

233
src/ImageSharp/Configuration.cs

@ -6,8 +6,8 @@
namespace ImageSharp namespace ImageSharp
{ {
using System; using System;
using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -17,22 +17,32 @@ namespace ImageSharp
/// <summary> /// <summary>
/// Provides initialization code which allows extending the library. /// Provides initialization code which allows extending the library.
/// </summary> /// </summary>
public class Configuration public sealed class Configuration
{ {
/// <summary> /// <summary>
/// A lazily initialized configuration default instance. /// A lazily initialized configuration default instance.
/// </summary> /// </summary>
private static readonly Lazy<Configuration> Lazy = new Lazy<Configuration>(() => CreateDefaultInstance()); private static readonly Lazy<Configuration> Lazy = new Lazy<Configuration>(CreateDefaultInstance);
/// <summary> /// <summary>
/// An object that can be used to synchronize access to the <see cref="Configuration"/>. /// The list of supported <see cref="IImageEncoder"/> keyed to mime types.
/// </summary> /// </summary>
private readonly object syncRoot = new object(); private readonly ConcurrentDictionary<IImageFormat, IImageEncoder> mimeTypeEncoders = new ConcurrentDictionary<IImageFormat, IImageEncoder>();
/// <summary> /// <summary>
/// The list of supported <see cref="IImageFormat"/>. /// The list of supported <see cref="IImageEncoder"/> keyed to mime types.
/// </summary> /// </summary>
private readonly List<IImageFormat> imageFormatsList = new List<IImageFormat>(); private readonly ConcurrentDictionary<IImageFormat, IImageDecoder> mimeTypeDecoders = new ConcurrentDictionary<IImageFormat, IImageDecoder>();
/// <summary>
/// The list of supported <see cref="IImageFormatDetector"/>s.
/// </summary>
private readonly List<IImageFormatDetector> imageFormatDetectors = new List<IImageFormatDetector>();
/// <summary>
/// The list of supported <see cref="IImageFormat"/>s.
/// </summary>
private readonly HashSet<IImageFormat> imageFormats = new HashSet<IImageFormat>();
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Configuration" /> class. /// Initializes a new instance of the <see cref="Configuration" /> class.
@ -44,12 +54,15 @@ namespace ImageSharp
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Configuration" /> class. /// Initializes a new instance of the <see cref="Configuration" /> class.
/// </summary> /// </summary>
/// <param name="providers">The inital set of image formats.</param> /// <param name="configurationModules">A collection of configuration modules to register</param>
public Configuration(params IImageFormat[] providers) public Configuration(params IConfigurationModule[] configurationModules)
{ {
foreach (IImageFormat p in providers) if (configurationModules != null)
{ {
this.AddImageFormat(p); foreach (IConfigurationModule p in configurationModules)
{
p.Configure(this);
}
} }
} }
@ -58,21 +71,36 @@ namespace ImageSharp
/// </summary> /// </summary>
public static Configuration Default { get; } = Lazy.Value; public static Configuration Default { get; } = Lazy.Value;
/// <summary>
/// Gets the collection of supported <see cref="IImageFormat"/>
/// </summary>
public IReadOnlyCollection<IImageFormat> ImageFormats => new ReadOnlyCollection<IImageFormat>(this.imageFormatsList);
/// <summary> /// <summary>
/// Gets the global parallel options for processing tasks in parallel. /// Gets the global parallel options for processing tasks in parallel.
/// </summary> /// </summary>
public ParallelOptions ParallelOptions { get; } = new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount }; public ParallelOptions ParallelOptions { get; } = new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount };
/// <summary> /// <summary>
/// Gets the maximum header size of all formats. /// Gets the maximum header size of all the formats.
/// </summary> /// </summary>
internal int MaxHeaderSize { get; private set; } internal int MaxHeaderSize { get; private set; }
/// <summary>
/// Gets the currently registered <see cref="IImageFormatDetector"/>s.
/// </summary>
internal IEnumerable<IImageFormatDetector> FormatDetectors => this.imageFormatDetectors;
/// <summary>
/// Gets the currently registered <see cref="IImageDecoder"/>s.
/// </summary>
internal IEnumerable<KeyValuePair<IImageFormat, IImageDecoder>> ImageDecoders => this.mimeTypeDecoders;
/// <summary>
/// Gets the currently registered <see cref="IImageEncoder"/>s.
/// </summary>
internal IEnumerable<KeyValuePair<IImageFormat, IImageEncoder>> ImageEncoders => this.mimeTypeEncoders;
/// <summary>
/// Gets the currently registered <see cref="IImageFormat"/>s.
/// </summary>
internal IEnumerable<IImageFormat> ImageFormats => this.imageFormats;
#if !NETSTANDARD1_1 #if !NETSTANDARD1_1
/// <summary> /// <summary>
/// Gets or sets the fielsystem helper for accessing the local file system. /// Gets or sets the fielsystem helper for accessing the local file system.
@ -81,126 +109,147 @@ namespace ImageSharp
#endif #endif
/// <summary> /// <summary>
/// Adds a new <see cref="IImageFormat"/> to the collection of supported image formats. /// Registers a new format provider.
/// </summary> /// </summary>
/// <param name="format">The new format to add.</param> /// <param name="configuration">The configuration provider to call configure on.</param>
public void Configure(IConfigurationModule configuration)
{
Guard.NotNull(configuration, nameof(configuration));
configuration.Configure(this);
}
/// <summary>
/// Registers a new format provider.
/// </summary>
/// <param name="format">The format to register as a well know format.</param>
public void AddImageFormat(IImageFormat format) public void AddImageFormat(IImageFormat format)
{ {
Guard.NotNull(format, nameof(format)); Guard.NotNull(format, nameof(format));
Guard.NotNull(format.Encoder, nameof(format), "The encoder should not be null."); Guard.NotNull(format.MimeTypes, nameof(format.MimeTypes));
Guard.NotNull(format.Decoder, nameof(format), "The decoder should not be null."); Guard.NotNull(format.FileExtensions, nameof(format.FileExtensions));
Guard.NotNullOrEmpty(format.MimeType, nameof(format), "The mime type should not be null or empty."); this.imageFormats.Add(format);
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> /// <summary>
/// Creates the default instance, with Png, Jpeg, Gif and Bmp preregisterd (if they have been referenced) /// For the specified file extensions type find the e <see cref="IImageFormat"/>.
/// </summary> /// </summary>
/// <returns>The default configuration of <see cref="Configuration"/> </returns> /// <param name="extension">The extension to discover</param>
internal static Configuration CreateDefaultInstance() /// <returns>The <see cref="IImageFormat"/> if found otherwise null</returns>
public IImageFormat FindFormatByFileExtensions(string extension)
{ {
Configuration config = new Configuration(); return this.imageFormats.FirstOrDefault(x => x.FileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase));
// 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;
} }
/// <summary> /// <summary>
/// Tries the add image format. /// For the specified mime type find the <see cref="IImageFormat"/>.
/// </summary> /// </summary>
/// <param name="typeName">Name of the type.</param> /// <param name="mimeType">The mime-type to discover</param>
/// <returns>True if type discoverd and is a valid <see cref="IImageFormat"/></returns> /// <returns>The <see cref="IImageFormat"/> if found otherwise null</returns>
internal bool TryAddImageFormat(string typeName) public IImageFormat FindFormatByMimeType(string mimeType)
{ {
Type type = Type.GetType(typeName, false); return this.imageFormats.FirstOrDefault(x => x.MimeTypes.Contains(mimeType, StringComparer.OrdinalIgnoreCase));
if (type != null)
{
IImageFormat format = Activator.CreateInstance(type) as IImageFormat;
if (format != null
&& format.Encoder != null
&& format.Decoder != null
&& !string.IsNullOrEmpty(format.MimeType)
&& format.SupportedExtensions?.Any() == true)
{
// we can use the locked version as we have already validated in the if.
this.AddImageFormatLocked(format);
return true;
}
} }
return false; /// <summary>
/// Sets a specific image encoder as the encoder for a specific image format.
/// </summary>
/// <param name="imageFormat">The image format to register the encoder for.</param>
/// <param name="encoder">The encoder to use,</param>
public void SetEncoder(IImageFormat imageFormat, IImageEncoder encoder)
{
Guard.NotNull(imageFormat, nameof(imageFormat));
Guard.NotNull(encoder, nameof(encoder));
this.AddImageFormat(imageFormat);
this.mimeTypeEncoders.AddOrUpdate(imageFormat, encoder, (s, e) => encoder);
} }
/// <summary> /// <summary>
/// Adds image format. The class is locked to make it thread safe. /// Sets a specific image decoder as the decoder for a specific image format.
/// </summary> /// </summary>
/// <param name="format">The image format.</param> /// <param name="imageFormat">The image format to register the encoder for.</param>
private void AddImageFormatLocked(IImageFormat format) /// <param name="decoder">The decoder to use,</param>
{ public void SetDecoder(IImageFormat imageFormat, IImageDecoder decoder)
lock (this.syncRoot)
{
if (this.GuardDuplicate(format))
{ {
this.imageFormatsList.Add(format); Guard.NotNull(imageFormat, nameof(imageFormat));
Guard.NotNull(decoder, nameof(decoder));
this.SetMaxHeaderSize(); this.AddImageFormat(imageFormat);
} this.mimeTypeDecoders.AddOrUpdate(imageFormat, decoder, (s, e) => decoder);
}
} }
/// <summary> /// <summary>
/// Checks to ensure duplicate image formats are not added. /// Removes all the registered image format detectors.
/// </summary> /// </summary>
/// <param name="format">The image format.</param> public void ClearImageFormatDetectors()
/// <exception cref="ArgumentException">Thrown if a duplicate is added.</exception>
/// <returns>
/// The <see cref="bool"/>.
/// </returns>
private bool GuardDuplicate(IImageFormat format)
{ {
if (!format.SupportedExtensions.Contains(format.Extension, StringComparer.OrdinalIgnoreCase)) this.imageFormatDetectors.Clear();
}
/// <summary>
/// Adds a new detector for detecting mime types.
/// </summary>
/// <param name="detector">The detector to add</param>
public void AddImageFormatDetector(IImageFormatDetector detector)
{ {
throw new ArgumentException("The supported extensions should contain the default extension.", nameof(format)); Guard.NotNull(detector, nameof(detector));
this.imageFormatDetectors.Add(detector);
this.SetMaxHeaderSize();
} }
// ReSharper disable once ConvertClosureToMethodGroup /// <summary>
// Prevents method group allocation /// Creates the default instance with the following <see cref="IConfigurationModule"/>s preregistered:
if (format.SupportedExtensions.Any(e => string.IsNullOrWhiteSpace(e))) /// <para><see cref="PngConfigurationModule"/></para>
/// <para><see cref="JpegConfigurationModule"/></para>
/// <para><see cref="GifConfigurationModule"/></para>
/// <para><see cref="BmpConfigurationModule"/></para>
/// </summary>
/// <returns>The default configuration of <see cref="Configuration"/></returns>
internal static Configuration CreateDefaultInstance()
{ {
throw new ArgumentException("The supported extensions should not contain empty values.", nameof(format)); return new Configuration(
new PngConfigurationModule(),
new JpegConfigurationModule(),
new GifConfigurationModule(),
new BmpConfigurationModule());
} }
// If there is already a format with the same extension or a format that supports that /// <summary>
// extension return false. /// For the specified mime type find the decoder.
foreach (IImageFormat imageFormat in this.imageFormatsList) /// </summary>
/// <param name="format">The format to discover</param>
/// <returns>The <see cref="IImageDecoder"/> if found otherwise null</returns>
internal IImageDecoder FindDecoder(IImageFormat format)
{ {
if (imageFormat.Extension.Equals(format.Extension, StringComparison.OrdinalIgnoreCase)) Guard.NotNull(format, nameof(format));
if (this.mimeTypeDecoders.TryGetValue(format, out IImageDecoder decoder))
{ {
return false; return decoder;
} }
if (imageFormat.SupportedExtensions.Intersect(format.SupportedExtensions, StringComparer.OrdinalIgnoreCase).Any()) return null;
{
return false;
} }
/// <summary>
/// For the specified mime type find the encoder.
/// </summary>
/// <param name="format">The format to discover</param>
/// <returns>The <see cref="IImageEncoder"/> if found otherwise null</returns>
internal IImageEncoder FindEncoder(IImageFormat format)
{
Guard.NotNull(format, nameof(format));
if (this.mimeTypeEncoders.TryGetValue(format, out IImageEncoder encoder))
{
return encoder;
} }
return true; return null;
} }
/// <summary> /// <summary>
/// Sets max header size. /// Sets the max header size.
/// </summary> /// </summary>
private void SetMaxHeaderSize() private void SetMaxHeaderSize()
{ {
this.MaxHeaderSize = this.imageFormatsList.Max(x => x.HeaderSize); this.MaxHeaderSize = this.imageFormatDetectors.Max(x => x.HeaderSize);
} }
} }
} }

21
src/ImageSharp/Formats/Bmp/BmpConfigurationModule.cs

@ -0,0 +1,21 @@
// <copyright file="BmpConfigurationModule.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>
/// Registers the image encoders, decoders and mime type detectors for the bmp format.
/// </summary>
public sealed class BmpConfigurationModule : IConfigurationModule
{
/// <inheritdoc/>
public void Configure(Configuration config)
{
config.SetEncoder(ImageFormats.Bitmap, new BmpEncoder());
config.SetDecoder(ImageFormats.Bitmap, new BmpDecoder());
config.AddImageFormatDetector(new BmpImageFormatDetector());
}
}
}

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 file extensions that equate to a bmp.
/// </summary>
public static readonly IEnumerable<string> FileExtensions = new[] { "bm", "bmp", "dip" };
}
}

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

@ -6,6 +6,7 @@
namespace ImageSharp.Formats namespace ImageSharp.Formats
{ {
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using ImageSharp.PixelFormats; using ImageSharp.PixelFormats;
@ -25,16 +26,16 @@ namespace ImageSharp.Formats
/// Formats will be supported in a later releases. We advise always /// Formats will be supported in a later releases. We advise always
/// to use only 24 Bit Windows bitmaps. /// to use only 24 Bit Windows bitmaps.
/// </remarks> /// </remarks>
public class BmpDecoder : IImageDecoder public sealed class BmpDecoder : IImageDecoder, IBmpDecoderOptions
{ {
/// <inheritdoc/> /// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream, IDecoderOptions options) public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
Guard.NotNull(stream, "stream"); Guard.NotNull(stream, "stream");
return new BmpDecoderCore(configuration).Decode<TPixel>(stream); return new BmpDecoderCore(configuration, this).Decode<TPixel>(stream);
} }
} }
} }

3
src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs

@ -52,7 +52,8 @@ namespace ImageSharp.Formats
/// Initializes a new instance of the <see cref="BmpDecoderCore"/> class. /// Initializes a new instance of the <see cref="BmpDecoderCore"/> class.
/// </summary> /// </summary>
/// <param name="configuration">The configuration.</param> /// <param name="configuration">The configuration.</param>
public BmpDecoderCore(Configuration configuration) /// <param name="options">The options</param>
public BmpDecoderCore(Configuration configuration, IBmpDecoderOptions options)
{ {
this.configuration = configuration; this.configuration = configuration;
} }

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

@ -6,6 +6,7 @@
namespace ImageSharp.Formats namespace ImageSharp.Formats
{ {
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using ImageSharp.PixelFormats; using ImageSharp.PixelFormats;
@ -14,28 +15,18 @@ namespace ImageSharp.Formats
/// Image encoder for writing an image to a stream as a Windows bitmap. /// Image encoder for writing an image to a stream as a Windows bitmap.
/// </summary> /// </summary>
/// <remarks>The encoder can currently only write 24-bit rgb images to streams.</remarks> /// <remarks>The encoder can currently only write 24-bit rgb images to streams.</remarks>
public class BmpEncoder : IImageEncoder public sealed class BmpEncoder : IImageEncoder, IBmpEncoderOptions
{ {
/// <inheritdoc/>
public void Encode<TPixel>(Image<TPixel> image, Stream stream, IEncoderOptions options)
where TPixel : struct, IPixel<TPixel>
{
IBmpEncoderOptions bmpOptions = BmpEncoderOptions.Create(options);
this.Encode(image, stream, bmpOptions);
}
/// <summary> /// <summary>
/// Encodes the image to the specified stream from the <see cref="Image{TPixel}"/>. /// Gets or sets the number of bits per pixel.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> public BmpBitsPerPixel BitsPerPixel { get; set; } = BmpBitsPerPixel.Pixel24;
/// <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> /// <inheritdoc/>
/// <param name="options">The options for the encoder.</param> public void Encode<TPixel>(Image<TPixel> image, Stream stream)
public void Encode<TPixel>(Image<TPixel> image, Stream stream, IBmpEncoderOptions options)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
BmpEncoderCore encoder = new BmpEncoderCore(options); var encoder = new BmpEncoderCore(this);
encoder.Encode(image, stream); encoder.Encode(image, stream);
} }
} }

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

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

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

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

2
src/ImageSharp/Formats/Bmp/BmpFileHeader.cs

@ -15,7 +15,7 @@ namespace ImageSharp.Formats
/// All of the other integer values are stored in little-endian format /// All of the other integer values are stored in little-endian format
/// (i.e. least-significant byte first). /// (i.e. least-significant byte first).
/// </remarks> /// </remarks>
internal class BmpFileHeader internal sealed class BmpFileHeader
{ {
/// <summary> /// <summary>
/// Defines of the data structure in the bitmap file. /// Defines of the data structure in the bitmap file.

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

@ -1,4 +1,4 @@
// <copyright file="BmpFormat.cs" company="James Jackson-South"> // <copyright file="GifFormat.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors. // Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
// </copyright> // </copyright>
@ -8,34 +8,20 @@ namespace ImageSharp.Formats
using System.Collections.Generic; using System.Collections.Generic;
/// <summary> /// <summary>
/// Encapsulates the means to encode and decode bitmap images. /// Registers the image encoders, decoders and mime type detectors for the jpeg format.
/// </summary> /// </summary>
public class BmpFormat : IImageFormat internal sealed class BmpFormat : IImageFormat
{ {
/// <inheritdoc/> /// <inheritdoc/>
public string MimeType => "image/bmp"; public string Name => "BMP";
/// <inheritdoc/> /// <inheritdoc/>
public string Extension => "bmp"; public string DefaultMimeType => "image/bmp";
/// <inheritdoc/> /// <inheritdoc/>
public IEnumerable<string> SupportedExtensions => new string[] { "bmp", "dip" }; public IEnumerable<string> MimeTypes => BmpConstants.MimeTypes;
/// <inheritdoc/> /// <inheritdoc/>
public IImageDecoder Decoder => new BmpDecoder(); public IEnumerable<string> FileExtensions => BmpConstants.FileExtensions;
/// <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
}
} }
} }

37
src/ImageSharp/Formats/Bmp/BmpImageFormatDetector.cs

@ -0,0 +1,37 @@
// <copyright file="BmpImageFormatDetector.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;
/// <summary>
/// Detects bmp file headers
/// </summary>
public sealed class BmpImageFormatDetector : IImageFormatDetector
{
/// <inheritdoc/>
public int HeaderSize => 2;
/// <inheritdoc/>
public IImageFormat DetectFormat(ReadOnlySpan<byte> header)
{
if (this.IsSupportedFileFormat(header))
{
return ImageFormats.Bitmap;
}
return null;
}
private bool IsSupportedFileFormat(ReadOnlySpan<byte> header)
{
// TODO: This should be in constants
return header.Length >= this.HeaderSize &&
header[0] == 0x42 && // B
header[1] == 0x4D; // M
}
}
}

2
src/ImageSharp/Formats/Bmp/BmpInfoHeader.cs

@ -10,7 +10,7 @@ namespace ImageSharp.Formats
/// the screen. /// the screen.
/// <see href="https://en.wikipedia.org/wiki/BMP_file_format"/> /// <see href="https://en.wikipedia.org/wiki/BMP_file_format"/>
/// </summary> /// </summary>
internal class BmpInfoHeader internal sealed class BmpInfoHeader
{ {
/// <summary> /// <summary>
/// Defines of the data structure in the bitmap file. /// Defines of the data structure in the bitmap file.

21
src/ImageSharp/Formats/Bmp/IBmpDecoderOptions.cs

@ -0,0 +1,21 @@
// <copyright file="BmpDecoder.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Formats
{
using System;
using System.Collections.Generic;
using System.IO;
using ImageSharp.PixelFormats;
/// <summary>
/// Image decoder options for decoding Windows bitmap streams.
/// </summary>
internal interface IBmpDecoderOptions
{
// added this for consistancy so we can add stuff as required, no options currently availible
}
}

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

@ -1,14 +1,21 @@
// <copyright file="IBmpEncoderOptions.cs" company="James Jackson-South"> // <copyright file="BmpEncoder.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors. // Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
// </copyright> // </copyright>
namespace ImageSharp.Formats namespace ImageSharp.Formats
{ {
using System;
using System.Collections.Generic;
using System.IO;
using ImageSharp.PixelFormats;
/// <summary> /// <summary>
/// Encapsulates the options for the <see cref="BmpEncoder"/>. /// Configuration options for use during bmp encoding
/// </summary> /// </summary>
public interface IBmpEncoderOptions : IEncoderOptions /// <remarks>The encoder can currently only write 24-bit rgb images to streams.</remarks>
internal interface IBmpEncoderOptions
{ {
/// <summary> /// <summary>
/// Gets the number of bits per pixel. /// Gets the number of bits per pixel.

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;
}
}

22
src/ImageSharp/Formats/Gif/GifConfigurationModule.cs

@ -0,0 +1,22 @@
// <copyright file="GifConfigurationModule.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>
/// Registers the image encoders, decoders and mime type detectors for the gif format.
/// </summary>
public sealed class GifConfigurationModule : IConfigurationModule
{
/// <inheritdoc/>
public void Configure(Configuration config)
{
config.SetEncoder(ImageFormats.Gif, new GifEncoder());
config.SetDecoder(ImageFormats.Gif, new GifDecoder());
config.AddImageFormatDetector(new GifImageFormatDetector());
}
}
}

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

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

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

@ -6,37 +6,32 @@
namespace ImageSharp.Formats namespace ImageSharp.Formats
{ {
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Text;
using ImageSharp.PixelFormats; using ImageSharp.PixelFormats;
/// <summary> /// <summary>
/// Decoder for generating an image out of a gif encoded stream. /// Decoder for generating an image out of a gif encoded stream.
/// </summary> /// </summary>
public class GifDecoder : IImageDecoder public sealed class GifDecoder : IImageDecoder, IGifDecoderOptions
{
/// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream, IDecoderOptions options)
where TPixel : struct, IPixel<TPixel>
{ {
IGifDecoderOptions gifOptions = GifDecoderOptions.Create(options); /// <summary>
/// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded.
return this.Decode<TPixel>(configuration, stream, gifOptions); /// </summary>
} public bool IgnoreMetadata { get; set; } = false;
/// <summary> /// <summary>
/// Decodes the image from the specified stream to the <see cref="ImageBase{TPixel}"/>. /// Gets or sets the encoding that should be used when reading comments.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> public Encoding TextEncoding { get; set; } = GifConstants.DefaultEncoding;
/// <param name="configuration">The configuration.</param>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param> /// <inheritdoc/>
/// <param name="options">The options for the decoder.</param> public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
/// <returns>The image thats been decoded.</returns>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream, IGifDecoderOptions options)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
return new GifDecoderCore<TPixel>(options, configuration).Decode(stream); var decoder = new GifDecoderCore<TPixel>(configuration, this);
return decoder.Decode(stream);
} }
} }
} }

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

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

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

@ -1,47 +0,0 @@
// <copyright file="GifDecoderOptions.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Formats
{
using System.Text;
/// <summary>
/// Encapsulates the options for the <see cref="GifDecoder"/>.
/// </summary>
public sealed class GifDecoderOptions : DecoderOptions, IGifDecoderOptions
{
/// <summary>
/// Initializes a new instance of the <see cref="GifDecoderOptions"/> class.
/// </summary>
public GifDecoderOptions()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="GifDecoderOptions"/> class.
/// </summary>
/// <param name="options">The options for the decoder.</param>
private GifDecoderOptions(IDecoderOptions options)
: base(options)
{
}
/// <summary>
/// Gets or sets the encoding that should be used when reading comments.
/// </summary>
public Encoding TextEncoding { get; set; } = GifConstants.DefaultEncoding;
/// <summary>
/// Converts the options to a <see cref="IGifDecoderOptions"/> instance with a cast
/// or by creating a new instance with the specfied options.
/// </summary>
/// <param name="options">The options for the decoder.</param>
/// <returns>The options for the <see cref="GifDecoder"/>.</returns>
internal static IGifDecoderOptions Create(IDecoderOptions options)
{
return options as IGifDecoderOptions ?? new GifDecoderOptions(options);
}
}
}

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

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

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

@ -9,7 +9,7 @@ namespace ImageSharp.Formats
using System.Buffers; using System.Buffers;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text;
using ImageSharp.PixelFormats; using ImageSharp.PixelFormats;
using IO; using IO;
@ -25,11 +25,6 @@ namespace ImageSharp.Formats
/// </summary> /// </summary>
private readonly byte[] buffer = new byte[16]; private readonly byte[] buffer = new byte[16];
/// <summary>
/// The options for the encoder.
/// </summary>
private readonly IGifEncoderOptions options;
/// <summary> /// <summary>
/// The number of bits requires to store the image palette. /// The number of bits requires to store the image palette.
/// </summary> /// </summary>
@ -40,19 +35,44 @@ namespace ImageSharp.Formats
/// </summary> /// </summary>
private bool hasFrames; private bool hasFrames;
/// <summary>
/// Gets the TextEncoding
/// </summary>
private Encoding textEncoding;
/// <summary>
/// Gets or sets the quantizer for reducing the color count.
/// </summary>
private IQuantizer quantizer;
/// <summary>
/// Gets or sets the threshold.
/// </summary>
private byte threshold;
/// <summary>
/// Gets or sets the size of the color palette to use.
/// </summary>
private int paletteSize;
/// <summary>
/// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded.
/// </summary>
private bool ignoreMetadata;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="GifEncoderCore"/> class. /// Initializes a new instance of the <see cref="GifEncoderCore"/> class.
/// </summary> /// </summary>
/// <param name="options">The options for the encoder.</param> /// <param name="options">The options for the encoder.</param>
public GifEncoderCore(IGifEncoderOptions options) public GifEncoderCore(IGifEncoderOptions options)
{ {
this.options = options ?? new GifEncoderOptions(); this.textEncoding = options.TextEncoding ?? GifConstants.DefaultEncoding;
}
/// <summary> this.quantizer = options.Quantizer;
/// Gets or sets the quantizer for reducing the color count. this.threshold = options.Threshold;
/// </summary> this.paletteSize = options.PaletteSize;
public IQuantizer Quantizer { get; set; } this.ignoreMetadata = options.IgnoreMetadata;
}
/// <summary> /// <summary>
/// Encodes the image to the specified stream from the <see cref="Image{TPixel}"/>. /// Encodes the image to the specified stream from the <see cref="Image{TPixel}"/>.
@ -66,26 +86,26 @@ namespace ImageSharp.Formats
Guard.NotNull(image, nameof(image)); Guard.NotNull(image, nameof(image));
Guard.NotNull(stream, nameof(stream)); Guard.NotNull(stream, nameof(stream));
this.Quantizer = this.options.Quantizer ?? new OctreeQuantizer<TPixel>(); this.quantizer = this.quantizer ?? new OctreeQuantizer<TPixel>();
// Do not use IDisposable pattern here as we want to preserve the stream. // Do not use IDisposable pattern here as we want to preserve the stream.
var writer = new EndianBinaryWriter(Endianness.LittleEndian, stream); var writer = new EndianBinaryWriter(Endianness.LittleEndian, stream);
// Ensure that quality can be set but has a fallback. // Ensure that pallete size can be set but has a fallback.
int quality = this.options.Quality > 0 ? this.options.Quality : image.MetaData.Quality; int paletteSize = this.paletteSize;
quality = quality > 0 ? quality.Clamp(1, 256) : 256; paletteSize = paletteSize > 0 ? paletteSize.Clamp(1, 256) : 256;
// Get the number of bits. // Get the number of bits.
this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(quality); this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(paletteSize);
// Quantize the image returning a palette.
this.hasFrames = image.Frames.Any(); this.hasFrames = image.Frames.Any();
// Dithering when animating gifs is a bad idea as we introduce pixel tearing across frames. // Dithering when animating gifs is a bad idea as we introduce pixel tearing across frames.
var ditheredQuantizer = (IQuantizer<TPixel>)this.Quantizer; var ditheredQuantizer = (IQuantizer<TPixel>)this.quantizer;
ditheredQuantizer.Dither = !this.hasFrames; ditheredQuantizer.Dither = !this.hasFrames;
QuantizedImage<TPixel> quantized = ditheredQuantizer.Quantize(image, quality); // Quantize the image returning a palette.
QuantizedImage<TPixel> quantized = ditheredQuantizer.Quantize(image, paletteSize);
int index = this.GetTransparentIndex(quantized); int index = this.GetTransparentIndex(quantized);
@ -111,7 +131,7 @@ namespace ImageSharp.Formats
for (int i = 0; i < image.Frames.Count; i++) for (int i = 0; i < image.Frames.Count; i++)
{ {
ImageFrame<TPixel> frame = image.Frames[i]; ImageFrame<TPixel> frame = image.Frames[i];
QuantizedImage<TPixel> quantizedFrame = ditheredQuantizer.Quantize(frame, quality); QuantizedImage<TPixel> quantizedFrame = ditheredQuantizer.Quantize(frame, paletteSize);
this.WriteGraphicalControlExtension(frame.MetaData, writer, this.GetTransparentIndex(quantizedFrame)); this.WriteGraphicalControlExtension(frame.MetaData, writer, this.GetTransparentIndex(quantizedFrame));
this.WriteImageDescriptor(frame, writer); this.WriteImageDescriptor(frame, writer);
@ -240,7 +260,7 @@ namespace ImageSharp.Formats
private void WriteComments<TPixel>(Image<TPixel> image, EndianBinaryWriter writer) private void WriteComments<TPixel>(Image<TPixel> image, EndianBinaryWriter writer)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
if (this.options.IgnoreMetadata) if (this.ignoreMetadata)
{ {
return; return;
} }
@ -251,7 +271,7 @@ namespace ImageSharp.Formats
return; return;
} }
byte[] comments = this.options.TextEncoding.GetBytes(property.Value); byte[] comments = this.textEncoding.GetBytes(property.Value);
int count = Math.Min(comments.Length, 255); int count = Math.Min(comments.Length, 255);

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

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

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

@ -8,38 +8,20 @@ namespace ImageSharp.Formats
using System.Collections.Generic; using System.Collections.Generic;
/// <summary> /// <summary>
/// Encapsulates the means to encode and decode gif images. /// Registers the image encoders, decoders and mime type detectors for the jpeg format.
/// </summary> /// </summary>
public class GifFormat : IImageFormat internal sealed class GifFormat : IImageFormat
{ {
/// <inheritdoc/> /// <inheritdoc/>
public string Extension => "gif"; public string Name => "GIF";
/// <inheritdoc/> /// <inheritdoc/>
public string MimeType => "image/gif"; public string DefaultMimeType => "image/gif";
/// <inheritdoc/> /// <inheritdoc/>
public IEnumerable<string> SupportedExtensions => new string[] { "gif" }; public IEnumerable<string> MimeTypes => GifConstants.MimeTypes;
/// <inheritdoc/> /// <inheritdoc/>
public IImageDecoder Decoder => new GifDecoder(); public IEnumerable<string> FileExtensions => GifConstants.FileExtensions;
/// <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
}
} }
} }

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

@ -0,0 +1,41 @@
// <copyright file="GifImageFormatDetector.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;
/// <summary>
/// Detects gif file headers
/// </summary>
public sealed class GifImageFormatDetector : IImageFormatDetector
{
/// <inheritdoc/>
public int HeaderSize => 6;
/// <inheritdoc/>
public IImageFormat DetectFormat(ReadOnlySpan<byte> header)
{
if (this.IsSupportedFileFormat(header))
{
return ImageFormats.Gif;
}
return null;
}
private bool IsSupportedFileFormat(ReadOnlySpan<byte> header)
{
// TODO: This should be in constants
return header.Length >= this.HeaderSize &&
header[0] == 0x47 && // G
header[1] == 0x49 && // I
header[2] == 0x46 && // F
header[3] == 0x38 && // 8
(header[4] == 0x39 || header[4] == 0x37) && // 9 or 7
header[5] == 0x61; // a
}
}
}

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

@ -1,17 +1,26 @@
// <copyright file="IGifDecoderOptions.cs" company="James Jackson-South"> // <copyright file="GifDecoder.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors. // Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
// </copyright> // </copyright>
namespace ImageSharp.Formats namespace ImageSharp.Formats
{ {
using System;
using System.Collections.Generic;
using System.IO;
using System.Text; using System.Text;
using ImageSharp.PixelFormats;
/// <summary> /// <summary>
/// Encapsulates the options for the <see cref="GifDecoder"/>. /// Decoder for generating an image out of a gif encoded stream.
/// </summary> /// </summary>
public interface IGifDecoderOptions : IDecoderOptions internal interface IGifDecoderOptions
{ {
/// <summary>
/// Gets a value indicating whether the metadata should be ignored when the image is being decoded.
/// </summary>
bool IgnoreMetadata { get; }
/// <summary> /// <summary>
/// Gets the encoding that should be used when reading comments. /// Gets the encoding that should be used when reading comments.
/// </summary> /// </summary>

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

@ -1,29 +1,36 @@
// <copyright file="IGifEncoderOptions.cs" company="James Jackson-South"> // <copyright file="GifEncoder.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors. // Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
// </copyright> // </copyright>
namespace ImageSharp.Formats namespace ImageSharp.Formats
{ {
using System;
using System.Collections.Generic;
using System.IO;
using System.Text; using System.Text;
using ImageSharp.PixelFormats;
using Quantizers; using ImageSharp.Quantizers;
/// <summary> /// <summary>
/// Encapsulates the options for the <see cref="GifEncoder"/>. /// The configuration options used for encoding gifs
/// </summary> /// </summary>
public interface IGifEncoderOptions : IEncoderOptions internal interface IGifEncoderOptions
{ {
/// <summary>
/// Gets a value indicating whether the metadata should be ignored when the image is being encoded.
/// </summary>
bool IgnoreMetadata { get; }
/// <summary> /// <summary>
/// Gets the encoding that should be used when writing comments. /// Gets the encoding that should be used when writing comments.
/// </summary> /// </summary>
Encoding TextEncoding { get; } Encoding TextEncoding { get; }
/// <summary> /// <summary>
/// Gets the quality of output for images. /// Gets the size of the color palette to use. For gifs the value ranges from 1 to 256. Leave as zero for default size.
/// </summary> /// </summary>
/// <remarks>For gifs the value ranges from 1 to 256.</remarks> int PaletteSize { get; }
int Quality { get; }
/// <summary> /// <summary>
/// Gets the transparency threshold. /// Gets the transparency threshold.

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

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

18
src/ImageSharp/Formats/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; }
}
}

4
src/ImageSharp/Formats/IImageDecoder.cs

@ -6,6 +6,7 @@
namespace ImageSharp.Formats namespace ImageSharp.Formats
{ {
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using ImageSharp.PixelFormats; using ImageSharp.PixelFormats;
@ -21,9 +22,8 @@ namespace ImageSharp.Formats
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="configuration">The configuration for the image.</param> /// <param name="configuration">The configuration for the image.</param>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param> /// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <param name="options">The options for the decoder.</param>
/// <returns>The decoded image</returns> /// <returns>The decoded image</returns>
Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream, IDecoderOptions options) Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
where TPixel : struct, IPixel<TPixel>; where TPixel : struct, IPixel<TPixel>;
} }
} }

4
src/ImageSharp/Formats/IImageEncoder.cs

@ -6,6 +6,7 @@
namespace ImageSharp.Formats namespace ImageSharp.Formats
{ {
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using ImageSharp.PixelFormats; using ImageSharp.PixelFormats;
@ -21,8 +22,7 @@ namespace ImageSharp.Formats
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The <see cref="Image{TPixel}"/> to encode from.</param> /// <param name="image">The <see cref="Image{TPixel}"/> to encode from.</param>
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param> /// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
/// <param name="options">The options for the encoder.</param> void Encode<TPixel>(Image<TPixel> image, Stream stream)
void Encode<TPixel>(Image<TPixel> image, Stream stream, IEncoderOptions options)
where TPixel : struct, IPixel<TPixel>; where TPixel : struct, IPixel<TPixel>;
} }
} }

43
src/ImageSharp/Formats/IImageFormat.cs

@ -8,53 +8,28 @@ namespace ImageSharp.Formats
using System.Collections.Generic; using System.Collections.Generic;
/// <summary> /// <summary>
/// Encapsulates a supported image format, providing means to encode and decode an image. /// Describes an image format.
/// Individual formats implements in this interface must be registered in the <see cref="Configuration"/>
/// </summary> /// </summary>
public interface IImageFormat public interface IImageFormat
{ {
/// <summary> /// <summary>
/// Gets the standard identifier used on the Internet to indicate the type of data that a file contains. /// Gets the name that describes this image format.
/// </summary> /// </summary>
string MimeType { get; } string Name { get; }
/// <summary> /// <summary>
/// Gets the default file extension for this format. /// Gets the default mimetype that the image foramt uses
/// </summary> /// </summary>
string Extension { get; } string DefaultMimeType { get; }
/// <summary> /// <summary>
/// Gets the supported file extensions for this format. /// Gets all the mimetypes that have been used by this image foramt.
/// </summary> /// </summary>
/// <returns> IEnumerable<string> MimeTypes { get; }
/// The supported file extension.
/// </returns>
IEnumerable<string> SupportedExtensions { get; }
/// <summary> /// <summary>
/// Gets the image encoder for encoding an image from a stream. /// Gets the file extensions this image format commonly uses.
/// </summary> /// </summary>
IImageEncoder Encoder { get; } IEnumerable<string> FileExtensions { 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);
} }
} }

30
src/ImageSharp/Formats/IImageFormatDetector.cs

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

14
src/ImageSharp/Formats/IDecoderOptions.cs → src/ImageSharp/Formats/Jpeg/IJpegDecoderOptions.cs

@ -1,14 +1,20 @@
// <copyright file="IDecoderOptions.cs" company="James Jackson-South"> // <copyright file="JpegDecoder.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors. // Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
// </copyright> // </copyright>
namespace ImageSharp namespace ImageSharp.Formats
{ {
using System;
using System.Collections.Generic;
using System.IO;
using ImageSharp.PixelFormats;
/// <summary> /// <summary>
/// Encapsulates the shared decoder options. /// Image decoder for generating an image out of a jpg stream.
/// </summary> /// </summary>
public interface IDecoderOptions internal interface IJpegDecoderOptions
{ {
/// <summary> /// <summary>
/// Gets a value indicating whether the metadata should be ignored when the image is being decoded. /// Gets a value indicating whether the metadata should be ignored when the image is being decoded.

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

@ -1,15 +1,26 @@
// <copyright file="IJpegEncoderOptions.cs" company="James Jackson-South"> // <copyright file="JpegEncoder.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors. // Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
// </copyright> // </copyright>
namespace ImageSharp.Formats namespace ImageSharp.Formats
{ {
using System;
using System.Collections.Generic;
using System.IO;
using ImageSharp.PixelFormats;
/// <summary> /// <summary>
/// Encapsulates the options for the <see cref="JpegEncoder"/>. /// Encoder for writing the data image to a stream in jpeg format.
/// </summary> /// </summary>
public interface IJpegEncoderOptions : IEncoderOptions internal interface IJpegEncoderOptions
{ {
/// <summary>
/// Gets a value indicating whether the metadata should be ignored when the image is being decoded.
/// </summary>
bool IgnoreMetadata { get; }
/// <summary> /// <summary>
/// Gets the quality, that will be used to encode the image. Quality /// Gets the quality, that will be used to encode the image. Quality
/// index must be between 0 and 100 (compression from max to min). /// index must be between 0 and 100 (compression from max to min).

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

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

22
src/ImageSharp/Formats/Jpeg/JpegConfigurationModule.cs

@ -0,0 +1,22 @@
// <copyright file="JpegConfigurationModule.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>
/// Registers the image encoders, decoders and mime type detectors for the jpeg format.
/// </summary>
public sealed class JpegConfigurationModule : IConfigurationModule
{
/// <inheritdoc/>
public void Configure(Configuration config)
{
config.SetEncoder(ImageFormats.Jpeg, new JpegEncoder());
config.SetDecoder(ImageFormats.Jpeg, new JpegDecoder());
config.AddImageFormatDetector(new JpegImageFormatDetector());
}
}
}

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

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

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

@ -5,7 +5,6 @@
namespace ImageSharp.Formats namespace ImageSharp.Formats
{ {
using System;
using System.IO; using System.IO;
using ImageSharp.PixelFormats; using ImageSharp.PixelFormats;
@ -13,19 +12,21 @@ namespace ImageSharp.Formats
/// <summary> /// <summary>
/// Image decoder for generating an image out of a jpg stream. /// Image decoder for generating an image out of a jpg stream.
/// </summary> /// </summary>
public class JpegDecoder : IImageDecoder public sealed class JpegDecoder : IImageDecoder, IJpegDecoderOptions
{ {
/// <summary>
/// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded.
/// </summary>
public bool IgnoreMetadata { get; set; }
/// <inheritdoc/> /// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream, IDecoderOptions options) public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
Guard.NotNull(stream, "stream"); Guard.NotNull(stream, "stream");
// using (var decoder = new JpegDecoderCore(options, configuration)) // using (var decoder = new JpegDecoderCore(configuration, this))
// { using (var decoder = new Jpeg.Port.JpegDecoderCore(configuration, this))
// return decoder.Decode<TPixel>(stream);
// }
using (var decoder = new Jpeg.Port.JpegDecoderCore(options, configuration))
{ {
return decoder.Decode<TPixel>(stream); return decoder.Decode<TPixel>(stream);
} }

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

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

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

@ -5,6 +5,8 @@
namespace ImageSharp.Formats namespace ImageSharp.Formats
{ {
using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using ImageSharp.PixelFormats; using ImageSharp.PixelFormats;
@ -12,16 +14,25 @@ namespace ImageSharp.Formats
/// <summary> /// <summary>
/// Encoder for writing the data image to a stream in jpeg format. /// Encoder for writing the data image to a stream in jpeg format.
/// </summary> /// </summary>
public class JpegEncoder : IImageEncoder public sealed class JpegEncoder : IImageEncoder, IJpegEncoderOptions
{ {
/// <inheritdoc/> /// <summary>
public void Encode<TPixel>(Image<TPixel> image, Stream stream, IEncoderOptions options) /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded.
where TPixel : struct, IPixel<TPixel> /// </summary>
{ public bool IgnoreMetadata { get; set; }
IJpegEncoderOptions gifOptions = JpegEncoderOptions.Create(options);
this.Encode(image, stream, gifOptions); /// <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; }
/// <summary> /// <summary>
/// Encodes the image to the specified stream from the <see cref="Image{TPixel}"/>. /// Encodes the image to the specified stream from the <see cref="Image{TPixel}"/>.
@ -29,12 +40,11 @@ namespace ImageSharp.Formats
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The <see cref="Image{TPixel}"/> to encode from.</param> /// <param name="image">The <see cref="Image{TPixel}"/> to encode from.</param>
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param> /// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
/// <param name="options">The options for the encoder.</param> public void Encode<TPixel>(Image<TPixel> image, Stream stream)
public void Encode<TPixel>(Image<TPixel> image, Stream stream, IJpegEncoderOptions options)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
JpegEncoderCore encode = new JpegEncoderCore(options); var encoder = new JpegEncoderCore(this);
encode.Encode(image, stream); encoder.Encode(image, stream);
} }
} }
} }

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

@ -17,7 +17,7 @@ namespace ImageSharp.Formats
/// <summary> /// <summary>
/// Image encoder for writing an image to a stream as a jpeg. /// Image encoder for writing an image to a stream as a jpeg.
/// </summary> /// </summary>
internal unsafe class JpegEncoderCore internal sealed unsafe class JpegEncoderCore
{ {
/// <summary> /// <summary>
/// The number of quantization tables. /// The number of quantization tables.
@ -124,11 +124,6 @@ namespace ImageSharp.Formats
/// </summary> /// </summary>
private readonly byte[] huffmanBuffer = new byte[179]; private readonly byte[] huffmanBuffer = new byte[179];
/// <summary>
/// The options for the encoder.
/// </summary>
private readonly IJpegEncoderOptions options;
/// <summary> /// <summary>
/// The accumulated bits to write to the stream. /// The accumulated bits to write to the stream.
/// </summary> /// </summary>
@ -155,17 +150,38 @@ namespace ImageSharp.Formats
private Stream outputStream; private Stream outputStream;
/// <summary> /// <summary>
/// The subsampling method to use. /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded.
/// </summary>
private bool ignoreMetadata = 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> /// </summary>
private JpegSubsample subsample; /// <value>The quality of the jpg image from 0 to 100.</value>
private int quality = 0;
/// <summary>
/// Gets or sets the subsampling method to use.
/// </summary>
private JpegSubsample? subsample;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="JpegEncoderCore"/> class. /// Initializes a new instance of the <see cref="JpegEncoderCore"/> class.
/// </summary> /// </summary>
/// <param name="options">The options for the encoder.</param> /// <param name="options">The options</param>
public JpegEncoderCore(IJpegEncoderOptions options) public JpegEncoderCore(IJpegEncoderOptions options)
{ {
this.options = options ?? new JpegEncoderOptions(); int quality = options.Quality;
if (quality == 0)
{
quality = 75;
}
this.quality = quality;
this.subsample = options.Subsample ?? (quality >= 91 ? JpegSubsample.Ratio444 : JpegSubsample.Ratio420);
this.ignoreMetadata = options.IgnoreMetadata;
} }
/// <summary> /// <summary>
@ -186,21 +202,13 @@ namespace ImageSharp.Formats
throw new ImageFormatException($"Image is too large to encode at {image.Width}x{image.Height}."); throw new ImageFormatException($"Image is too large to encode at {image.Width}x{image.Height}.");
} }
// Ensure that quality can be set but has a fallback.
int quality = this.options.Quality > 0 ? this.options.Quality : image.MetaData.Quality;
if (quality == 0)
{
quality = 75;
}
quality = quality.Clamp(1, 100);
this.outputStream = stream; this.outputStream = stream;
this.subsample = this.options.Subsample ?? (quality >= 91 ? JpegSubsample.Ratio444 : JpegSubsample.Ratio420);
int quality = this.quality.Clamp(1, 100);
// Convert from a quality rating to a scaling factor. // Convert from a quality rating to a scaling factor.
int scale; int scale;
if (quality < 50) if (this.quality < 50)
{ {
scale = 5000 / quality; scale = 5000 / quality;
} }
@ -788,7 +796,7 @@ namespace ImageSharp.Formats
private void WriteProfiles<TPixel>(Image<TPixel> image) private void WriteProfiles<TPixel>(Image<TPixel> image)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
if (this.options.IgnoreMetadata) if (this.ignoreMetadata)
{ {
return; return;
} }

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);
}
}
}

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

@ -8,82 +8,20 @@ namespace ImageSharp.Formats
using System.Collections.Generic; using System.Collections.Generic;
/// <summary> /// <summary>
/// Encapsulates the means to encode and decode jpeg images. /// Registers the image encoders, decoders and mime type detectors for the jpeg format.
/// </summary> /// </summary>
public class JpegFormat : IImageFormat internal sealed class JpegFormat : IImageFormat
{ {
/// <inheritdoc/> /// <inheritdoc/>
public string MimeType => "image/jpeg"; public string Name => "JPEG";
/// <inheritdoc/> /// <inheritdoc/>
public string Extension => "jpg"; public string DefaultMimeType => "image/jpeg";
/// <inheritdoc/> /// <inheritdoc/>
public IEnumerable<string> SupportedExtensions => new string[] { "jpg", "jpeg", "jfif" }; public IEnumerable<string> MimeTypes => JpegConstants.MimeTypes;
/// <inheritdoc/> /// <inheritdoc/>
public IImageDecoder Decoder => new JpegDecoder(); public IEnumerable<string> FileExtensions => JpegConstants.FileExtensions;
/// <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;
}
} }
} }

87
src/ImageSharp/Formats/Jpeg/JpegImageFormatDetector.cs

@ -0,0 +1,87 @@
// <copyright file="JpegImageFormatDetector.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;
/// <summary>
/// Detects Jpeg file headers
/// </summary>
public sealed class JpegImageFormatDetector : IImageFormatDetector
{
/// <inheritdoc/>
public int HeaderSize => 11;
/// <inheritdoc/>
public IImageFormat DetectFormat(ReadOnlySpan<byte> header)
{
if (this.IsSupportedFileFormat(header))
{
return ImageFormats.Jpeg;
}
return null;
}
private bool IsSupportedFileFormat(ReadOnlySpan<byte> header)
{
return header.Length >= this.HeaderSize &&
(this.IsJfif(header) || this.IsExif(header) || this.IsJpeg(header));
}
/// <summary>
/// Returns a value indicating whether the given bytes identify Jfif data.
/// </summary>
/// <param name="header">The bytes representing the file header.</param>
/// <returns>The <see cref="bool"/></returns>
private bool IsJfif(ReadOnlySpan<byte> header)
{
// TODO: This should be in constants
bool isJfif =
header[6] == 0x4A && // J
header[7] == 0x46 && // F
header[8] == 0x49 && // I
header[9] == 0x46 && // F
header[10] == 0x00;
return isJfif;
}
/// <summary>
/// Returns a value indicating whether the given bytes identify EXIF data.
/// </summary>
/// <param name="header">The bytes representing the file header.</param>
/// <returns>The <see cref="bool"/></returns>
private bool IsExif(ReadOnlySpan<byte> header)
{
// TODO: This should be in constants
bool isExif =
header[6] == 0x45 && // E
header[7] == 0x78 && // X
header[8] == 0x69 && // I
header[9] == 0x66 && // F
header[10] == 0x00;
return isExif;
}
/// <summary>
/// Returns a value indicating whether the given bytes identify Jpeg data.
/// This is a last chance resort for jpegs that contain ICC information.
/// </summary>
/// <param name="header">The bytes representing the file header.</param>
/// <returns>The <see cref="bool"/></returns>
private bool IsJpeg(ReadOnlySpan<byte> header)
{
// TODO: This should be in constants
bool isJpg =
header[0] == 0xFF && // 255
header[1] == 0xD8; // 216
return isJpg;
}
}
}

20
src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs

@ -22,11 +22,6 @@ namespace ImageSharp.Formats.Jpeg.Port
/// </summary> /// </summary>
internal sealed class JpegDecoderCore : IDisposable internal sealed class JpegDecoderCore : IDisposable
{ {
/// <summary>
/// The decoder options.
/// </summary>
private readonly IDecoderOptions options;
/// <summary> /// <summary>
/// The global configuration /// The global configuration
/// </summary> /// </summary>
@ -85,12 +80,12 @@ namespace ImageSharp.Formats.Jpeg.Port
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="JpegDecoderCore" /> class. /// Initializes a new instance of the <see cref="JpegDecoderCore" /> class.
/// </summary> /// </summary>
/// <param name="options">The decoder options.</param>
/// <param name="configuration">The configuration.</param> /// <param name="configuration">The configuration.</param>
public JpegDecoderCore(IDecoderOptions options, Configuration configuration) /// <param name="options">The options.</param>
public JpegDecoderCore(Configuration configuration, IJpegDecoderOptions options)
{ {
this.configuration = configuration ?? Configuration.Default; this.configuration = configuration ?? Configuration.Default;
this.options = options ?? new DecoderOptions(); this.IgnoreMetadata = options.IgnoreMetadata;
} }
/// <summary> /// <summary>
@ -98,6 +93,11 @@ namespace ImageSharp.Formats.Jpeg.Port
/// </summary> /// </summary>
public Stream InputStream { get; private set; } public Stream InputStream { get; private set; }
/// <summary>
/// Gets a value indicating whether the metadata should be ignored when the image is being decoded.
/// </summary>
public bool IgnoreMetadata { get; }
/// <summary> /// <summary>
/// Finds the next file marker within the byte stream. /// Finds the next file marker within the byte stream.
/// </summary> /// </summary>
@ -413,7 +413,7 @@ namespace ImageSharp.Formats.Jpeg.Port
/// <param name="metadata">The image.</param> /// <param name="metadata">The image.</param>
private void ProcessApp1Marker(int remaining, ImageMetaData metadata) private void ProcessApp1Marker(int remaining, ImageMetaData metadata)
{ {
if (remaining < 6 || this.options.IgnoreMetadata) if (remaining < 6 || this.IgnoreMetadata)
{ {
// Skip the application header length // Skip the application header length
this.InputStream.Skip(remaining); this.InputStream.Skip(remaining);
@ -444,7 +444,7 @@ namespace ImageSharp.Formats.Jpeg.Port
{ {
// Length is 14 though we only need to check 12. // Length is 14 though we only need to check 12.
const int Icclength = 14; const int Icclength = 14;
if (remaining < Icclength || this.options.IgnoreMetadata) if (remaining < Icclength || this.IgnoreMetadata)
{ {
this.InputStream.Skip(remaining); this.InputStream.Skip(remaining);
return; return;

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

@ -1,17 +1,26 @@
// <copyright file="IPngDecoderOptions.cs" company="James Jackson-South"> // <copyright file="PngDecoder.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors. // Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
// </copyright> // </copyright>
namespace ImageSharp.Formats namespace ImageSharp.Formats
{ {
using System;
using System.Collections.Generic;
using System.IO;
using System.Text; using System.Text;
using ImageSharp.PixelFormats;
/// <summary> /// <summary>
/// Encapsulates the options for the <see cref="PngDecoder"/>. /// The optioas for decoding png images
/// </summary> /// </summary>
public interface IPngDecoderOptions : IDecoderOptions internal interface IPngDecoderOptions
{ {
/// <summary>
/// Gets a value indicating whether the metadata should be ignored when the image is being decoded.
/// </summary>
bool IgnoreMetadata { get; }
/// <summary> /// <summary>
/// Gets the encoding that should be used when reading text chunks. /// Gets the encoding that should be used when reading text chunks.
/// </summary> /// </summary>

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

@ -1,21 +1,30 @@
// <copyright file="IPngEncoderOptions.cs" company="James Jackson-South"> // <copyright file="PngEncoder.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors. // Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
// </copyright> // </copyright>
namespace ImageSharp.Formats namespace ImageSharp.Formats
{ {
using Quantizers; using System.Collections.Generic;
using System.IO;
using ImageSharp.PixelFormats;
using ImageSharp.Quantizers;
/// <summary> /// <summary>
/// Encapsulates the options for the <see cref="PngEncoder"/>. /// The options availible for manipulating the encoder pipeline
/// </summary> /// </summary>
public interface IPngEncoderOptions : IEncoderOptions internal interface IPngEncoderOptions
{ {
/// <summary> /// <summary>
/// Gets the quality of output for images. /// Gets a value indicating whether the metadata should be ignored when the image is being encoded.
/// </summary>
bool IgnoreMetadata { get; }
/// <summary>
/// Gets the size of the color palette to use. Set to zero to leav png encoding to use pixel data.
/// </summary> /// </summary>
int Quality { get; } int PaletteSize { get; }
/// <summary> /// <summary>
/// Gets the png color type /// Gets the png color type
@ -24,13 +33,14 @@ namespace ImageSharp.Formats
/// <summary> /// <summary>
/// Gets the compression level 1-9. /// Gets the compression level 1-9.
/// <remarks>Defaults to 6.</remarks>
/// </summary> /// </summary>
int CompressionLevel { get; } int CompressionLevel { get; }
/// <summary> /// <summary>
/// Gets the gamma value, that will be written /// Gets the gamma value, that will be written
/// the the stream, when the <see cref="WriteGamma"/> property /// the the stream, when the <see cref="WriteGamma"/> property
/// is set to true. /// is set to true. The default value is 2.2F.
/// </summary> /// </summary>
/// <value>The gamma value of the image.</value> /// <value>The gamma value of the image.</value>
float Gamma { get; } float Gamma { get; }
@ -47,7 +57,7 @@ namespace ImageSharp.Formats
/// <summary> /// <summary>
/// Gets a value indicating whether this instance should write /// Gets a value indicating whether this instance should write
/// gamma information to the stream. /// gamma information to the stream. The default value is false.
/// </summary> /// </summary>
bool WriteGamma { get; } bool WriteGamma { get; }
} }

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

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

21
src/ImageSharp/Formats/Png/PngConfigurationModule.cs

@ -0,0 +1,21 @@
// <copyright file="PngConfigurationModule.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>
/// Registers the image encoders, decoders and mime type detectors for the png format.
/// </summary>
public sealed class PngConfigurationModule : IConfigurationModule
{
/// <inheritdoc/>
public void Configure(Configuration host)
{
host.SetEncoder(ImageFormats.Png, new PngEncoder());
host.SetDecoder(ImageFormats.Png, new PngDecoder());
host.AddImageFormatDetector(new PngImageFormatDetector());
}
}
}

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

@ -0,0 +1,31 @@
// <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 png.
/// </summary>
public static readonly IEnumerable<string> MimeTypes = new[] { "image/png" };
/// <summary>
/// The list of file extensions that equate to a png.
/// </summary>
public static readonly IEnumerable<string> FileExtensions = new[] { "png" };
}
}

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

@ -6,8 +6,9 @@
namespace ImageSharp.Formats namespace ImageSharp.Formats
{ {
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Text;
using ImageSharp.PixelFormats; using ImageSharp.PixelFormats;
/// <summary> /// <summary>
@ -30,17 +31,17 @@ namespace ImageSharp.Formats
/// </list> /// </list>
/// </para> /// </para>
/// </remarks> /// </remarks>
public class PngDecoder : IImageDecoder public sealed class PngDecoder : IImageDecoder, IPngDecoderOptions
{ {
/// <inheritdoc/> /// <summary>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream, IDecoderOptions options) /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded.
/// </summary>
where TPixel : struct, IPixel<TPixel> public bool IgnoreMetadata { get; set; }
{
IPngDecoderOptions pngOptions = PngDecoderOptions.Create(options);
return this.Decode<TPixel>(configuration, stream, pngOptions); /// <summary>
} /// Gets or sets the encoding that should be used when reading text chunks.
/// </summary>
public Encoding TextEncoding { get; set; } = PngConstants.DefaultEncoding;
/// <summary> /// <summary>
/// Decodes the image from the specified stream to the <see cref="ImageBase{TPixel}"/>. /// Decodes the image from the specified stream to the <see cref="ImageBase{TPixel}"/>.
@ -48,12 +49,12 @@ namespace ImageSharp.Formats
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="configuration">The configuration for the image.</param> /// <param name="configuration">The configuration for the image.</param>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param> /// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <param name="options">The options for the decoder.</param>
/// <returns>The decoded image.</returns> /// <returns>The decoded image.</returns>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream, IPngDecoderOptions options) public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
return new PngDecoderCore(options, configuration).Decode<TPixel>(stream); var decoder = new PngDecoderCore(configuration, this);
return decoder.Decode<TPixel>(stream);
} }
} }
} }

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

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

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

@ -1,49 +0,0 @@
// <copyright file="PngDecoderOptions.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Formats
{
using System.Text;
/// <summary>
/// Encapsulates the options for the <see cref="PngDecoder"/>.
/// </summary>
public sealed class PngDecoderOptions : DecoderOptions, IPngDecoderOptions
{
private static readonly Encoding DefaultEncoding = Encoding.GetEncoding("ASCII");
/// <summary>
/// Initializes a new instance of the <see cref="PngDecoderOptions"/> class.
/// </summary>
public PngDecoderOptions()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="PngDecoderOptions"/> class.
/// </summary>
/// <param name="options">The options for the decoder.</param>
private PngDecoderOptions(IDecoderOptions options)
: base(options)
{
}
/// <summary>
/// Gets or sets the encoding that should be used when reading text chunks.
/// </summary>
public Encoding TextEncoding { get; set; } = DefaultEncoding;
/// <summary>
/// Converts the options to a <see cref="IPngDecoderOptions"/> instance with a cast
/// or by creating a new instance with the specfied options.
/// </summary>
/// <param name="options">The options for the decoder.</param>
/// <returns>The options for the <see cref="PngDecoder"/>.</returns>
internal static IPngDecoderOptions Create(IDecoderOptions options)
{
return options as IPngDecoderOptions ?? new PngDecoderOptions(options);
}
}
}

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

@ -5,23 +5,61 @@
namespace ImageSharp.Formats namespace ImageSharp.Formats
{ {
using System.Collections.Generic;
using System.IO; using System.IO;
using ImageSharp.PixelFormats; using ImageSharp.PixelFormats;
using ImageSharp.Quantizers;
/// <summary> /// <summary>
/// Image encoder for writing image data to a stream in png format. /// Image encoder for writing image data to a stream in png format.
/// </summary> /// </summary>
public class PngEncoder : IImageEncoder public sealed class PngEncoder : IImageEncoder, IPngEncoderOptions
{ {
/// <inheritdoc/> /// <summary>
public void Encode<TPixel>(Image<TPixel> image, Stream stream, IEncoderOptions options) /// Gets or sets a value indicating whether the metadata should be ignored when the image is being encoded.
where TPixel : struct, IPixel<TPixel> /// </summary>
{ public bool IgnoreMetadata { get; set; }
IPngEncoderOptions pngOptions = PngEncoderOptions.Create(options);
this.Encode(image, stream, pngOptions); /// <summary>
} /// Gets or sets the size of the color palette to use. Set to zero to leav png encoding to use pixel data.
/// </summary>
public int PaletteSize { get; set; } = 0;
/// <summary>
/// Gets or sets the png color type
/// </summary>
public PngColorType PngColorType { get; set; } = PngColorType.RgbWithAlpha;
/// <summary>
/// Gets or sets the compression level 1-9.
/// <remarks>Defaults to 6.</remarks>
/// </summary>
public int CompressionLevel { get; set; } = 6;
/// <summary>
/// Gets or sets the gamma value, that will be written
/// the the stream, when the <see cref="WriteGamma"/> property
/// is set to true. The default value is 2.2F.
/// </summary>
/// <value>The gamma value of the image.</value>
public float Gamma { get; set; } = 2.2F;
/// <summary>
/// Gets or sets quantizer for reducing the color count.
/// </summary>
public IQuantizer Quantizer { get; set; }
/// <summary>
/// Gets or sets the transparency threshold.
/// </summary>
public byte Threshold { get; set; } = 255;
/// <summary>
/// Gets or sets a value indicating whether this instance should write
/// gamma information to the stream. The default value is false.
/// </summary>
public bool WriteGamma { get; set; }
/// <summary> /// <summary>
/// Encodes the image to the specified stream from the <see cref="Image{TPixel}"/>. /// Encodes the image to the specified stream from the <see cref="Image{TPixel}"/>.
@ -29,13 +67,12 @@ namespace ImageSharp.Formats
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The <see cref="Image{TPixel}"/> to encode from.</param> /// <param name="image">The <see cref="Image{TPixel}"/> to encode from.</param>
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param> /// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
/// <param name="options">The options for the encoder.</param> public void Encode<TPixel>(Image<TPixel> image, Stream stream)
public void Encode<TPixel>(Image<TPixel> image, Stream stream, IPngEncoderOptions options)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
using (var encode = new PngEncoderCore(options)) using (var encoder = new PngEncoderCore(this))
{ {
encode.Encode(image, stream); encoder.Encode(image, stream);
} }
} }
} }

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

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

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

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

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

@ -8,40 +8,20 @@ namespace ImageSharp.Formats
using System.Collections.Generic; using System.Collections.Generic;
/// <summary> /// <summary>
/// Encapsulates the means to encode and decode png images. /// Registers the image encoders, decoders and mime type detectors for the jpeg format.
/// </summary> /// </summary>
public class PngFormat : IImageFormat internal sealed class PngFormat : IImageFormat
{ {
/// <inheritdoc/> /// <inheritdoc/>
public string MimeType => "image/png"; public string Name => "PNG";
/// <inheritdoc/> /// <inheritdoc/>
public string Extension => "png"; public string DefaultMimeType => "image/png";
/// <inheritdoc/> /// <inheritdoc/>
public IEnumerable<string> SupportedExtensions => new string[] { "png" }; public IEnumerable<string> MimeTypes => PngConstants.MimeTypes;
/// <inheritdoc/> /// <inheritdoc/>
public IImageDecoder Decoder => new PngDecoder(); public IEnumerable<string> FileExtensions => PngConstants.FileExtensions;
/// <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
}
} }
} }

2
src/ImageSharp/Formats/Png/PngHeader.cs

@ -8,7 +8,7 @@ namespace ImageSharp.Formats
/// <summary> /// <summary>
/// Represents the png header chunk. /// Represents the png header chunk.
/// </summary> /// </summary>
public sealed class PngHeader internal sealed class PngHeader
{ {
/// <summary> /// <summary>
/// Gets or sets the dimension in x-direction of the image in pixels. /// Gets or sets the dimension in x-direction of the image in pixels.

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

@ -0,0 +1,43 @@
// <copyright file="PngImageFormatDetector.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;
/// <summary>
/// Detects png file headers
/// </summary>
public sealed class PngImageFormatDetector : IImageFormatDetector
{
/// <inheritdoc/>
public int HeaderSize => 8;
/// <inheritdoc/>
public IImageFormat DetectFormat(ReadOnlySpan<byte> header)
{
if (this.IsSupportedFileFormat(header))
{
return ImageFormats.Png;
}
return null;
}
private bool IsSupportedFileFormat(ReadOnlySpan<byte> header)
{
// TODO: This should be in constants
return header.Length >= this.HeaderSize &&
header[0] == 0x89 &&
header[1] == 0x50 && // P
header[2] == 0x4E && // N
header[3] == 0x47 && // G
header[4] == 0x0D && // CR
header[5] == 0x0A && // LF
header[6] == 0x1A && // EOF
header[7] == 0x0A; // LF
}
}
}

2
src/ImageSharp/Formats/Png/PngInterlaceMode.cs

@ -8,7 +8,7 @@ namespace ImageSharp.Formats
/// <summary> /// <summary>
/// Provides enumeration of available PNG interlace modes. /// Provides enumeration of available PNG interlace modes.
/// </summary> /// </summary>
public enum PngInterlaceMode : byte internal enum PngInterlaceMode : byte
{ {
/// <summary> /// <summary>
/// Non interlaced /// Non interlaced

2
src/ImageSharp/Formats/Png/Zlib/IChecksum.cs

@ -12,7 +12,7 @@ namespace ImageSharp.Formats
/// <code>Value</code>. The complete checksum object can also be reset /// <code>Value</code>. The complete checksum object can also be reset
/// so it can be used again with new data. /// so it can be used again with new data.
/// </summary> /// </summary>
public interface IChecksum internal interface IChecksum
{ {
/// <summary> /// <summary>
/// Gets the data checksum computed so far. /// Gets the data checksum computed so far.

2
src/ImageSharp/Formats/Png/Zlib/ZlibInflateStream.cs

@ -9,7 +9,7 @@
/// <summary> /// <summary>
/// Provides methods and properties for deframing streams from PNGs. /// Provides methods and properties for deframing streams from PNGs.
/// </summary> /// </summary>
internal class ZlibInflateStream : Stream internal sealed class ZlibInflateStream : Stream
{ {
/// <summary> /// <summary>
/// The inner raw memory stream /// The inner raw memory stream

19
src/ImageSharp/IConfigurationModule.cs

@ -0,0 +1,19 @@
// <copyright file="IConfigurationModule.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp
{
/// <summary>
/// Represents an interface that can register image encoders, decoders and image format detectors.
/// </summary>
public interface IConfigurationModule
{
/// <summary>
/// Called when loaded into a configuration object so the module can register items into the configuration.
/// </summary>
/// <param name="configuration">The configuration that will retain the encoders, decodes and mime type detectors.</param>
void Configure(Configuration configuration);
}
}

5
src/ImageSharp/Image/IImage.cs

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

49
src/ImageSharp/Image/Image.Decode.cs

@ -5,11 +5,10 @@
namespace ImageSharp namespace ImageSharp
{ {
using System.Buffers;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using Formats; using Formats;
using ImageSharp.Memory;
using ImageSharp.PixelFormats; using ImageSharp.PixelFormats;
/// <content> /// <content>
@ -22,8 +21,8 @@ namespace ImageSharp
/// </summary> /// </summary>
/// <param name="stream">The image stream to read the header from.</param> /// <param name="stream">The image stream to read the header from.</param>
/// <param name="config">The configuration.</param> /// <param name="config">The configuration.</param>
/// <returns>The image format or null if none found.</returns> /// <returns>The mime type or null if none found.</returns>
private static IImageFormat DiscoverFormat(Stream stream, Configuration config) private static IImageFormat InternalDetectFormat(Stream stream, Configuration config)
{ {
// This is probably a candidate for making into a public API in the future! // This is probably a candidate for making into a public API in the future!
int maxHeaderSize = config.MaxHeaderSize; int maxHeaderSize = config.MaxHeaderSize;
@ -32,45 +31,55 @@ namespace ImageSharp
return null; return null;
} }
IImageFormat format; using (var buffer = new Buffer<byte>(maxHeaderSize))
byte[] header = ArrayPool<byte>.Shared.Rent(maxHeaderSize);
try
{ {
long startPosition = stream.Position; long startPosition = stream.Position;
stream.Read(header, 0, maxHeaderSize); stream.Read(buffer.Array, 0, maxHeaderSize);
stream.Position = startPosition; stream.Position = startPosition;
format = config.ImageFormats.FirstOrDefault(x => x.IsSupportedFileFormat(header)); return config.FormatDetectors.Select(x => x.DetectFormat(buffer)).LastOrDefault(x => x != null);
} }
finally }
/// <summary>
/// By reading the header on the provided stream this calculates the images format.
/// </summary>
/// <param name="stream">The image stream to read the header from.</param>
/// <param name="config">The configuration.</param>
/// <param name="format">The IImageFormat.</param>
/// <returns>The image format or null if none found.</returns>
private static IImageDecoder DiscoverDecoder(Stream stream, Configuration config, out IImageFormat format)
{ {
ArrayPool<byte>.Shared.Return(header); format = InternalDetectFormat(stream, config);
if (format != null)
{
return config.FindDecoder(format);
} }
return format; return null;
} }
#pragma warning disable SA1008 // Opening parenthesis must be spaced correctly
/// <summary> /// <summary>
/// Decodes the image stream to the current image. /// Decodes the image stream to the current image.
/// </summary> /// </summary>
/// <param name="stream">The stream.</param> /// <param name="stream">The stream.</param>
/// <param name="options">The options for the decoder.</param>
/// <param name="config">the configuration.</param> /// <param name="config">the configuration.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns> /// <returns>
/// A new <see cref="Image{TPixel}"/>. /// A new <see cref="Image{TPixel}"/>.
/// </returns> /// </returns>
private static Image<TPixel> Decode<TPixel>(Stream stream, IDecoderOptions options, Configuration config) private static (Image<TPixel> img, IImageFormat format) Decode<TPixel>(Stream stream, Configuration config)
#pragma warning restore SA1008 // Opening parenthesis must be spaced correctly
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
IImageFormat format = DiscoverFormat(stream, config); IImageDecoder decoder = DiscoverDecoder(stream, config, out IImageFormat format);
if (format == null) if (decoder == null)
{ {
return null; return (null, null);
} }
Image<TPixel> img = format.Decoder.Decode<TPixel>(config, stream, options); Image<TPixel> img = decoder.Decode<TPixel>(config, stream);
img.CurrentImageFormat = format; return (img, format);
return img;
} }
} }
} }

94
src/ImageSharp/Image/Image.FromBytes.cs

@ -15,54 +15,78 @@ namespace ImageSharp
/// </content> /// </content>
public static partial class Image public static partial class Image
{ {
/// <summary>
/// By reading the header on the provided byte array this calculates the images format.
/// </summary>
/// <param name="data">The byte array containing image data to read the header from.</param>
/// <returns>The format or null if none found.</returns>
public static IImageFormat DetectFormat(byte[] data)
{
return DetectFormat(null, data);
}
/// <summary>
/// By reading the header on the provided byte array this calculates the images format.
/// </summary>
/// <param name="config">The configuration.</param>
/// <param name="data">The byte array containing image data to read the header from.</param>
/// <returns>The mime type or null if none found.</returns>
public static IImageFormat DetectFormat(Configuration config, byte[] data)
{
using (Stream stream = new MemoryStream(data))
{
return DetectFormat(config, stream);
}
}
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image{Rgba32}"/> class from the given byte array. /// Create a new instance of the <see cref="Image{Rgba32}"/> class from the given byte array.
/// </summary> /// </summary>
/// <param name="data">The byte array containing image data.</param> /// <param name="data">The byte array containing image data.</param>
/// <returns>A new <see cref="Image{Rgba32}"/>.</returns> /// <returns>A new <see cref="Image{Rgba32}"/>.</returns>
public static Image<Rgba32> Load(byte[] data) => Load<Rgba32>(null, data, null); public static Image<Rgba32> Load(byte[] data) => Load<Rgba32>(null, data);
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image{Rgba32}"/> class from the given byte array. /// Create a new instance of the <see cref="Image{Rgba32}"/> class from the given byte array.
/// </summary> /// </summary>
/// <param name="data">The byte array containing image data.</param> /// <param name="data">The byte array containing image data.</param>
/// <param name="options">The options for the decoder.</param> /// <param name="format">The mime type of the decoded image.</param>
/// <returns>A new <see cref="Image{Rgba32}"/>.</returns> /// <returns>A new <see cref="Image{Rgba32}"/>.</returns>
public static Image<Rgba32> Load(byte[] data, IDecoderOptions options) => Load<Rgba32>(null, data, options); public static Image<Rgba32> Load(byte[] data, out IImageFormat format) => Load<Rgba32>(null, data, out format);
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given byte array. /// Create a new instance of the <see cref="Image{Rgba32}"/> class from the given byte array.
/// </summary> /// </summary>
/// <param name="config">The config for the decoder.</param> /// <param name="config">The config for the decoder.</param>
/// <param name="data">The byte array containing image data.</param> /// <param name="data">The byte array containing image data.</param>
/// <returns>A new <see cref="Image{Rgba32}"/>.</returns> /// <returns>A new <see cref="Image{Rgba32}"/>.</returns>
public static Image<Rgba32> Load(Configuration config, byte[] data) => Load<Rgba32>(config, data, null); public static Image<Rgba32> Load(Configuration config, byte[] data) => Load<Rgba32>(config, data);
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image{Rgba32}"/> class from the given byte array. /// Create a new instance of the <see cref="Image{Rgba32}"/> class from the given byte array.
/// </summary> /// </summary>
/// <param name="config">The config for the decoder.</param>
/// <param name="data">The byte array containing image data.</param> /// <param name="data">The byte array containing image data.</param>
/// <param name="decoder">The decoder.</param> /// <param name="format">The mime type of the decoded image.</param>
/// <returns>A new <see cref="Image{Rgba32}"/>.</returns> /// <returns>A new <see cref="Image{Rgba32}"/>.</returns>
public static Image<Rgba32> Load(byte[] data, IImageDecoder decoder) => Load<Rgba32>(data, decoder, null); public static Image<Rgba32> Load(Configuration config, byte[] data, out IImageFormat format) => Load<Rgba32>(config, data, out format);
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given byte array. /// Create a new instance of the <see cref="Image{Rgba32}"/> class from the given byte array.
/// </summary> /// </summary>
/// <param name="config">The configuration options.</param>
/// <param name="data">The byte array containing image data.</param> /// <param name="data">The byte array containing image data.</param>
/// <param name="options">The options for the decoder.</param> /// <param name="decoder">The decoder.</param>
/// <returns>A new <see cref="Image{Rgba32}"/>.</returns> /// <returns>A new <see cref="Image{Rgba32}"/>.</returns>
public static Image<Rgba32> Load(Configuration config, byte[] data, IDecoderOptions options) => Load<Rgba32>(config, data, options); public static Image<Rgba32> Load(byte[] data, IImageDecoder decoder) => Load<Rgba32>(data, decoder);
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given byte array. /// Create a new instance of the <see cref="Image{Rgba32}"/> class from the given byte array.
/// </summary> /// </summary>
/// <param name="config">The config for the decoder.</param>
/// <param name="data">The byte array containing image data.</param> /// <param name="data">The byte array containing image data.</param>
/// <param name="decoder">The decoder.</param> /// <param name="decoder">The decoder.</param>
/// <param name="options">The options for the decoder.</param>
/// <returns>A new <see cref="Image{Rgba32}"/>.</returns> /// <returns>A new <see cref="Image{Rgba32}"/>.</returns>
public static Image<Rgba32> Load(byte[] data, IImageDecoder decoder, IDecoderOptions options) => Load<Rgba32>(data, decoder, options); public static Image<Rgba32> Load(Configuration config, byte[] data, IImageDecoder decoder) => Load<Rgba32>(config, data, decoder);
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given byte array. /// Create a new instance of the <see cref="Image{TPixel}"/> class from the given byte array.
@ -73,79 +97,85 @@ namespace ImageSharp
public static Image<TPixel> Load<TPixel>(byte[] data) public static Image<TPixel> Load<TPixel>(byte[] data)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
return Load<TPixel>(null, data, null); return Load<TPixel>(null, data);
} }
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given byte array. /// Create a new instance of the <see cref="Image{TPixel}"/> class from the given byte array.
/// </summary> /// </summary>
/// <param name="data">The byte array containing image data.</param> /// <param name="data">The byte array containing image data.</param>
/// <param name="options">The options for the decoder.</param> /// <param name="format">The mime type of the decoded image.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(byte[] data, IDecoderOptions options) public static Image<TPixel> Load<TPixel>(byte[] data, out IImageFormat format)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
return Load<TPixel>(null, data, options); return Load<TPixel>(null, data, out format);
} }
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given byte array. /// Create a new instance of the <see cref="Image{TPixel}"/> class from the given byte array.
/// </summary> /// </summary>
/// <param name="config">The config for the decoder.</param> /// <param name="config">The configuration options.</param>
/// <param name="data">The byte array containing image data.</param> /// <param name="data">The byte array containing image data.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(Configuration config, byte[] data) public static Image<TPixel> Load<TPixel>(Configuration config, byte[] data)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
return Load<TPixel>(config, data, null); using (var memoryStream = new MemoryStream(data))
{
return Load<TPixel>(config, memoryStream);
}
} }
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given byte array. /// Create a new instance of the <see cref="Image{TPixel}"/> class from the given byte array.
/// </summary> /// </summary>
/// <param name="config">The configuration options.</param>
/// <param name="data">The byte array containing image data.</param> /// <param name="data">The byte array containing image data.</param>
/// <param name="decoder">The decoder.</param> /// <param name="format">The mime type of the decoded image.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(byte[] data, IImageDecoder decoder) public static Image<TPixel> Load<TPixel>(Configuration config, byte[] data, out IImageFormat format)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
return Load<TPixel>(data, decoder, null); using (var memoryStream = new MemoryStream(data))
{
return Load<TPixel>(config, memoryStream, out format);
}
} }
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given byte array. /// Create a new instance of the <see cref="Image{TPixel}"/> class from the given byte array.
/// </summary> /// </summary>
/// <param name="config">The configuration options.</param>
/// <param name="data">The byte array containing image data.</param> /// <param name="data">The byte array containing image data.</param>
/// <param name="options">The options for the decoder.</param> /// <param name="decoder">The decoder.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(Configuration config, byte[] data, IDecoderOptions options) public static Image<TPixel> Load<TPixel>(byte[] data, IImageDecoder decoder)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
using (var ms = new MemoryStream(data)) using (var memoryStream = new MemoryStream(data))
{ {
return Load<TPixel>(config, ms, options); return Load<TPixel>(memoryStream, decoder);
} }
} }
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given byte array. /// Create a new instance of the <see cref="Image{TPixel}"/> class from the given byte array.
/// </summary> /// </summary>
/// <param name="config">The Configuration.</param>
/// <param name="data">The byte array containing image data.</param> /// <param name="data">The byte array containing image data.</param>
/// <param name="decoder">The decoder.</param> /// <param name="decoder">The decoder.</param>
/// <param name="options">The options for the decoder.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(byte[] data, IImageDecoder decoder, IDecoderOptions options) public static Image<TPixel> Load<TPixel>(Configuration config, byte[] data, IImageDecoder decoder)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
using (var ms = new MemoryStream(data)) using (var memoryStream = new MemoryStream(data))
{ {
return Load<TPixel>(ms, decoder, options); return Load<TPixel>(config, memoryStream, decoder);
} }
} }
} }

93
src/ImageSharp/Image/Image.FromFile.cs

@ -16,6 +16,31 @@ namespace ImageSharp
/// </content> /// </content>
public static partial class Image public static partial class Image
{ {
/// <summary>
/// By reading the header on the provided file this calculates the images mime type.
/// </summary>
/// <param name="filePath">The image file to open and to read the header from.</param>
/// <returns>The mime type or null if none found.</returns>
public static IImageFormat DetectFormat(string filePath)
{
return DetectFormat(null, filePath);
}
/// <summary>
/// By reading the header on the provided file this calculates the images mime type.
/// </summary>
/// <param name="config">The configuration.</param>
/// <param name="filePath">The image file to open and to read the header from.</param>
/// <returns>The mime type or null if none found.</returns>
public static IImageFormat DetectFormat(Configuration config, string filePath)
{
config = config ?? Configuration.Default;
using (Stream file = config.FileSystem.OpenRead(filePath))
{
return DetectFormat(config, file);
}
}
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image{Rgba32}"/> class from the given file. /// Create a new instance of the <see cref="Image{Rgba32}"/> class from the given file.
/// </summary> /// </summary>
@ -30,12 +55,12 @@ namespace ImageSharp
/// Create a new instance of the <see cref="Image{Rgba32}"/> class from the given file. /// Create a new instance of the <see cref="Image{Rgba32}"/> class from the given file.
/// </summary> /// </summary>
/// <param name="path">The file path to the image.</param> /// <param name="path">The file path to the image.</param>
/// <param name="options">The options for the decoder.</param> /// <param name="format">The mime type of the decoded image.</param>
/// <exception cref="NotSupportedException"> /// <exception cref="NotSupportedException">
/// Thrown if the stream is not readable nor seekable. /// Thrown if the stream is not readable nor seekable.
/// </exception> /// </exception>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <returns>A new <see cref="Image{Rgba32}"/>.</returns>
public static Image<Rgba32> Load(string path, IDecoderOptions options) => Load<Rgba32>(path, options); public static Image<Rgba32> Load(string path, out IImageFormat format) => Load<Rgba32>(path, out format);
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image{Rgba32}"/> class from the given file. /// Create a new instance of the <see cref="Image{Rgba32}"/> class from the given file.
@ -51,37 +76,37 @@ namespace ImageSharp
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image{Rgba32}"/> class from the given file. /// Create a new instance of the <see cref="Image{Rgba32}"/> class from the given file.
/// </summary> /// </summary>
/// <param name="config">The config for the decoder.</param>
/// <param name="path">The file path to the image.</param> /// <param name="path">The file path to the image.</param>
/// <param name="decoder">The decoder.</param> /// <param name="format">The mime type of the decoded image.</param>
/// <exception cref="NotSupportedException"> /// <exception cref="NotSupportedException">
/// Thrown if the stream is not readable nor seekable. /// Thrown if the stream is not readable nor seekable.
/// </exception> /// </exception>
/// <returns>A new <see cref="Image{Rgba32}"/>.</returns> /// <returns>A new <see cref="Image{Rgba32}"/>.</returns>
public static Image<Rgba32> Load(string path, IImageDecoder decoder) => Load<Rgba32>(path, decoder); public static Image<Rgba32> Load(Configuration config, string path, out IImageFormat format) => Load<Rgba32>(config, path, out format);
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image{Rgba32}"/> class from the given file. /// Create a new instance of the <see cref="Image{Rgba32}"/> class from the given file.
/// </summary> /// </summary>
/// <param name="config">The configuration options.</param> /// <param name="config">The Configuration.</param>
/// <param name="path">The file path to the image.</param> /// <param name="path">The file path to the image.</param>
/// <param name="options">The options for the decoder.</param> /// <param name="decoder">The decoder.</param>
/// <exception cref="NotSupportedException"> /// <exception cref="NotSupportedException">
/// Thrown if the stream is not readable nor seekable. /// Thrown if the stream is not readable nor seekable.
/// </exception> /// </exception>
/// <returns>A new <see cref="Image{Rgba32}"/>.</returns> /// <returns>A new <see cref="Image{Rgba32}"/>.</returns>
public static Image<Rgba32> Load(Configuration config, string path, IDecoderOptions options) => Load<Rgba32>(config, path, options); public static Image<Rgba32> Load(Configuration config, string path, IImageDecoder decoder) => Load<Rgba32>(config, path, decoder);
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image{Rgba32}"/> class from the given file. /// Create a new instance of the <see cref="Image{Rgba32}"/> class from the given file.
/// </summary> /// </summary>
/// <param name="path">The file path to the image.</param> /// <param name="path">The file path to the image.</param>
/// <param name="decoder">The decoder.</param> /// <param name="decoder">The decoder.</param>
/// <param name="options">The options for the decoder.</param>
/// <exception cref="NotSupportedException"> /// <exception cref="NotSupportedException">
/// Thrown if the stream is not readable nor seekable. /// Thrown if the stream is not readable nor seekable.
/// </exception> /// </exception>
/// <returns>A new <see cref="Image{Rgba32}"/>.</returns> /// <returns>A new <see cref="Image{Rgba32}"/>.</returns>
public static Image<Rgba32> Load(string path, IImageDecoder decoder, IDecoderOptions options) => Load<Rgba32>(path, decoder, options); public static Image<Rgba32> Load(string path, IImageDecoder decoder) => Load<Rgba32>(path, decoder);
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given file. /// Create a new instance of the <see cref="Image{TPixel}"/> class from the given file.
@ -95,29 +120,29 @@ namespace ImageSharp
public static Image<TPixel> Load<TPixel>(string path) public static Image<TPixel> Load<TPixel>(string path)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
return Load<TPixel>(null, path, null); return Load<TPixel>(null, path);
} }
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given file. /// Create a new instance of the <see cref="Image{TPixel}"/> class from the given file.
/// </summary> /// </summary>
/// <param name="path">The file path to the image.</param> /// <param name="path">The file path to the image.</param>
/// <param name="options">The options for the decoder.</param> /// <param name="format">The mime type of the decoded image.</param>
/// <exception cref="NotSupportedException"> /// <exception cref="NotSupportedException">
/// Thrown if the stream is not readable nor seekable. /// Thrown if the stream is not readable nor seekable.
/// </exception> /// </exception>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(string path, IDecoderOptions options) public static Image<TPixel> Load<TPixel>(string path, out IImageFormat format)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
return Load<TPixel>(null, path, options); return Load<TPixel>(null, path, out format);
} }
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given file. /// Create a new instance of the <see cref="Image{TPixel}"/> class from the given file.
/// </summary> /// </summary>
/// <param name="config">The config for the decoder.</param> /// <param name="config">The configuration options.</param>
/// <param name="path">The file path to the image.</param> /// <param name="path">The file path to the image.</param>
/// <exception cref="NotSupportedException"> /// <exception cref="NotSupportedException">
/// Thrown if the stream is not readable nor seekable. /// Thrown if the stream is not readable nor seekable.
@ -127,64 +152,68 @@ namespace ImageSharp
public static Image<TPixel> Load<TPixel>(Configuration config, string path) public static Image<TPixel> Load<TPixel>(Configuration config, string path)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
return Load<TPixel>(config, path, null); config = config ?? Configuration.Default;
using (Stream stream = config.FileSystem.OpenRead(path))
{
return Load<TPixel>(config, stream);
}
} }
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given file. /// Create a new instance of the <see cref="Image{TPixel}"/> class from the given file.
/// </summary> /// </summary>
/// <param name="config">The configuration options.</param>
/// <param name="path">The file path to the image.</param> /// <param name="path">The file path to the image.</param>
/// <param name="decoder">The decoder.</param> /// <param name="format">The mime type of the decoded image.</param>
/// <exception cref="NotSupportedException"> /// <exception cref="NotSupportedException">
/// Thrown if the stream is not readable nor seekable. /// Thrown if the stream is not readable nor seekable.
/// </exception> /// </exception>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(string path, IImageDecoder decoder) public static Image<TPixel> Load<TPixel>(Configuration config, string path, out IImageFormat format)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
return Load<TPixel>(path, decoder, null); config = config ?? Configuration.Default;
using (Stream stream = config.FileSystem.OpenRead(path))
{
return Load<TPixel>(config, stream, out format);
}
} }
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given file. /// Create a new instance of the <see cref="Image{TPixel}"/> class from the given file.
/// </summary> /// </summary>
/// <param name="config">The configuration options.</param>
/// <param name="path">The file path to the image.</param> /// <param name="path">The file path to the image.</param>
/// <param name="options">The options for the decoder.</param> /// <param name="decoder">The decoder.</param>
/// <exception cref="NotSupportedException"> /// <exception cref="NotSupportedException">
/// Thrown if the stream is not readable nor seekable. /// Thrown if the stream is not readable nor seekable.
/// </exception> /// </exception>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(Configuration config, string path, IDecoderOptions options) public static Image<TPixel> Load<TPixel>(string path, IImageDecoder decoder)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
config = config ?? Configuration.Default; return Load<TPixel>(null, path, decoder);
using (Stream s = config.FileSystem.OpenRead(path))
{
return Load<TPixel>(config, s, options);
}
} }
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given file. /// Create a new instance of the <see cref="Image{TPixel}"/> class from the given file.
/// </summary> /// </summary>
/// <param name="config">The Configuration.</param>
/// <param name="path">The file path to the image.</param> /// <param name="path">The file path to the image.</param>
/// <param name="decoder">The decoder.</param> /// <param name="decoder">The decoder.</param>
/// <param name="options">The options for the decoder.</param>
/// <exception cref="NotSupportedException"> /// <exception cref="NotSupportedException">
/// Thrown if the stream is not readable nor seekable. /// Thrown if the stream is not readable nor seekable.
/// </exception> /// </exception>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(string path, IImageDecoder decoder, IDecoderOptions options) public static Image<TPixel> Load<TPixel>(Configuration config, string path, IImageDecoder decoder)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
Configuration config = Configuration.Default; config = config ?? Configuration.Default;
using (Stream s = config.FileSystem.OpenRead(path)) using (Stream stream = config.FileSystem.OpenRead(path))
{ {
return Load<TPixel>(s, decoder, options); return Load<TPixel>(config, stream, decoder);
} }
} }
} }

90
src/ImageSharp/Image/Image.FromStream.cs

@ -6,6 +6,7 @@
namespace ImageSharp namespace ImageSharp
{ {
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Text; using System.Text;
using Formats; using Formats;
@ -17,26 +18,47 @@ namespace ImageSharp
/// </content> /// </content>
public static partial class Image public static partial class Image
{ {
/// <summary>
/// By reading the header on the provided stream this calculates the images mime type.
/// </summary>
/// <param name="stream">The image stream to read the header from.</param>
/// <returns>The mime type or null if none found.</returns>
public static IImageFormat DetectFormat(Stream stream)
{
return DetectFormat(null, stream);
}
/// <summary>
/// By reading the header on the provided stream this calculates the images mime type.
/// </summary>
/// <param name="config">The configuration.</param>
/// <param name="stream">The image stream to read the header from.</param>
/// <returns>The mime type or null if none found.</returns>
public static IImageFormat DetectFormat(Configuration config, Stream stream)
{
return WithSeekableStream(stream, s => InternalDetectFormat(s, config ?? Configuration.Default));
}
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image{Rgba32}"/> class from the given stream. /// Create a new instance of the <see cref="Image{Rgba32}"/> class from the given stream.
/// </summary> /// </summary>
/// <param name="stream">The stream containing image information.</param> /// <param name="stream">The stream containing image information.</param>
/// <param name="format">the mime type of the decoded image.</param>
/// <exception cref="NotSupportedException"> /// <exception cref="NotSupportedException">
/// Thrown if the stream is not readable nor seekable. /// Thrown if the stream is not readable nor seekable.
/// </exception> /// </exception>
/// <returns>A new <see cref="Image{Rgba32}"/>.</returns>> /// <returns>A new <see cref="Image{Rgba32}"/>.</returns>>
public static Image<Rgba32> Load(Stream stream) => Load<Rgba32>(stream); public static Image<Rgba32> Load(Stream stream, out IImageFormat format) => Load<Rgba32>(stream, out format);
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image{Rgba32}"/> class from the given stream. /// Create a new instance of the <see cref="Image{Rgba32}"/> class from the given stream.
/// </summary> /// </summary>
/// <param name="stream">The stream containing image information.</param> /// <param name="stream">The stream containing image information.</param>
/// <param name="options">The options for the decoder.</param>
/// <exception cref="NotSupportedException"> /// <exception cref="NotSupportedException">
/// Thrown if the stream is not readable nor seekable. /// Thrown if the stream is not readable nor seekable.
/// </exception> /// </exception>
/// <returns>A new <see cref="Image{Rgba32}"/>.</returns>> /// <returns>A new <see cref="Image{Rgba32}"/>.</returns>>
public static Image<Rgba32> Load(Stream stream, IDecoderOptions options) => Load<Rgba32>(stream, options); public static Image<Rgba32> Load(Stream stream) => Load<Rgba32>(stream);
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image{Rgba32}"/> class from the given stream. /// Create a new instance of the <see cref="Image{Rgba32}"/> class from the given stream.
@ -63,14 +85,14 @@ namespace ImageSharp
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image{Rgba32}"/> class from the given stream. /// Create a new instance of the <see cref="Image{Rgba32}"/> class from the given stream.
/// </summary> /// </summary>
/// <param name="config">The config for the decoder.</param>
/// <param name="stream">The stream containing image information.</param> /// <param name="stream">The stream containing image information.</param>
/// <param name="decoder">The decoder.</param> /// <param name="format">the mime type of the decoded image.</param>
/// <param name="options">The options for the decoder.</param>
/// <exception cref="NotSupportedException"> /// <exception cref="NotSupportedException">
/// Thrown if the stream is not readable nor seekable. /// Thrown if the stream is not readable nor seekable.
/// </exception> /// </exception>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>> /// <returns>A new <see cref="Image{Rgba32}"/>.</returns>>
public static Image<Rgba32> Load(Stream stream, IImageDecoder decoder, IDecoderOptions options) => Load<Rgba32>(stream, decoder, options); public static Image<Rgba32> Load(Configuration config, Stream stream, out IImageFormat format) => Load<Rgba32>(config, stream, out format);
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given stream. /// Create a new instance of the <see cref="Image{TPixel}"/> class from the given stream.
@ -84,44 +106,45 @@ namespace ImageSharp
public static Image<TPixel> Load<TPixel>(Stream stream) public static Image<TPixel> Load<TPixel>(Stream stream)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
return Load<TPixel>(null, stream, null); return Load<TPixel>(null, stream);
} }
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given stream. /// Create a new instance of the <see cref="Image{TPixel}"/> class from the given stream.
/// </summary> /// </summary>
/// <param name="stream">The stream containing image information.</param> /// <param name="stream">The stream containing image information.</param>
/// <param name="options">The options for the decoder.</param> /// <param name="format">the mime type of the decoded image.</param>
/// <exception cref="NotSupportedException"> /// <exception cref="NotSupportedException">
/// Thrown if the stream is not readable nor seekable. /// Thrown if the stream is not readable nor seekable.
/// </exception> /// </exception>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>>
public static Image<TPixel> Load<TPixel>(Stream stream, IDecoderOptions options) public static Image<TPixel> Load<TPixel>(Stream stream, out IImageFormat format)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
return Load<TPixel>(null, stream, options); return Load<TPixel>(null, stream, out format);
} }
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given stream. /// Create a new instance of the <see cref="Image{TPixel}"/> class from the given stream.
/// </summary> /// </summary>
/// <param name="config">The config for the decoder.</param>
/// <param name="stream">The stream containing image information.</param> /// <param name="stream">The stream containing image information.</param>
/// <param name="decoder">The decoder.</param>
/// <exception cref="NotSupportedException"> /// <exception cref="NotSupportedException">
/// Thrown if the stream is not readable nor seekable. /// Thrown if the stream is not readable nor seekable.
/// </exception> /// </exception>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>>
public static Image<TPixel> Load<TPixel>(Configuration config, Stream stream) public static Image<TPixel> Load<TPixel>(Stream stream, IImageDecoder decoder)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
return Load<TPixel>(config, stream, null); return WithSeekableStream(stream, s => decoder.Decode<TPixel>(Configuration.Default, s));
} }
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given stream. /// Create a new instance of the <see cref="Image{TPixel}"/> class from the given stream.
/// </summary> /// </summary>
/// <param name="config">The Configuration.</param>
/// <param name="stream">The stream containing image information.</param> /// <param name="stream">The stream containing image information.</param>
/// <param name="decoder">The decoder.</param> /// <param name="decoder">The decoder.</param>
/// <exception cref="NotSupportedException"> /// <exception cref="NotSupportedException">
@ -129,27 +152,26 @@ namespace ImageSharp
/// </exception> /// </exception>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>>
public static Image<TPixel> Load<TPixel>(Stream stream, IImageDecoder decoder) public static Image<TPixel> Load<TPixel>(Configuration config, Stream stream, IImageDecoder decoder)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
return Load<TPixel>(stream, decoder, null); return WithSeekableStream(stream, s => decoder.Decode<TPixel>(config, s));
} }
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given stream. /// Create a new instance of the <see cref="Image{TPixel}"/> class from the given stream.
/// </summary> /// </summary>
/// <param name="config">The configuration options.</param>
/// <param name="stream">The stream containing image information.</param> /// <param name="stream">The stream containing image information.</param>
/// <param name="decoder">The decoder.</param>
/// <param name="options">The options for the decoder.</param>
/// <exception cref="NotSupportedException"> /// <exception cref="NotSupportedException">
/// Thrown if the stream is not readable nor seekable. /// Thrown if the stream is not readable nor seekable.
/// </exception> /// </exception>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>>
public static Image<TPixel> Load<TPixel>(Stream stream, IImageDecoder decoder, IDecoderOptions options) public static Image<TPixel> Load<TPixel>(Configuration config, Stream stream)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
return WithSeekableStream(stream, s => decoder.Decode<TPixel>(Configuration.Default, s, options)); return Load<TPixel>(config, stream, out var _);
} }
/// <summary> /// <summary>
@ -157,29 +179,31 @@ namespace ImageSharp
/// </summary> /// </summary>
/// <param name="config">The configuration options.</param> /// <param name="config">The configuration options.</param>
/// <param name="stream">The stream containing image information.</param> /// <param name="stream">The stream containing image information.</param>
/// <param name="options">The options for the decoder.</param> /// <param name="format">the mime type of the decoded image.</param>
/// <exception cref="NotSupportedException"> /// <exception cref="NotSupportedException">
/// Thrown if the stream is not readable nor seekable. /// Thrown if the stream is not readable nor seekable.
/// </exception> /// </exception>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>>
public static Image<TPixel> Load<TPixel>(Configuration config, Stream stream, IDecoderOptions options) public static Image<TPixel> Load<TPixel>(Configuration config, Stream stream, out IImageFormat format)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
config = config ?? Configuration.Default; config = config ?? Configuration.Default;
Image<TPixel> img = WithSeekableStream(stream, s => Decode<TPixel>(s, options, config)); (Image<TPixel> img, IImageFormat format) data = WithSeekableStream(stream, s => Decode<TPixel>(s, config));
format = data.format;
if (img != null) if (data.img != null)
{ {
return img; return data.img;
} }
StringBuilder stringBuilder = new StringBuilder(); var 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 (KeyValuePair<IImageFormat, IImageDecoder> val in config.ImageDecoders)
{ {
stringBuilder.AppendLine("-" + format); stringBuilder.AppendLine($" - {val.Key.Name} : {val.Value.GetType().Name}");
} }
throw new NotSupportedException(stringBuilder.ToString()); throw new NotSupportedException(stringBuilder.ToString());
@ -198,12 +222,12 @@ namespace ImageSharp
} }
// We want to be able to load images from things like HttpContext.Request.Body // We want to be able to load images from things like HttpContext.Request.Body
using (MemoryStream ms = new MemoryStream()) using (var memoryStream = new MemoryStream())
{ {
stream.CopyTo(ms); stream.CopyTo(memoryStream);
ms.Position = 0; memoryStream.Position = 0;
return action(ms); return action(memoryStream);
} }
} }
} }

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

@ -9,8 +9,8 @@ namespace ImageSharp
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq;
using System.Numerics; using System.Numerics;
using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Formats; using Formats;
@ -95,13 +95,7 @@ namespace ImageSharp
internal Image(Configuration configuration, int width, int height, ImageMetaData metadata) internal Image(Configuration configuration, int width, int height, ImageMetaData metadata)
: base(configuration, width, height) : base(configuration, width, height)
{ {
if (!this.Configuration.ImageFormats.Any())
{
throw new InvalidOperationException("No image formats have been configured.");
}
this.MetaData = metadata ?? new ImageMetaData(); this.MetaData = metadata ?? new ImageMetaData();
this.CurrentImageFormat = this.Configuration.ImageFormats.First();
} }
/// <summary> /// <summary>
@ -139,11 +133,6 @@ namespace ImageSharp
/// <value>The list of frame images.</value> /// <value>The list of frame images.</value>
public IList<ImageFrame<TPixel>> Frames { get; } = new List<ImageFrame<TPixel>>(); public IList<ImageFrame<TPixel>> Frames { get; } = new List<ImageFrame<TPixel>>();
/// <summary>
/// Gets the currently loaded image format.
/// </summary>
public IImageFormat CurrentImageFormat { get; internal set; }
/// <summary> /// <summary>
/// Applies the processor to the image. /// Applies the processor to the image.
/// </summary> /// </summary>
@ -163,62 +152,28 @@ namespace ImageSharp
/// Saves the image to the given stream using the currently loaded image format. /// Saves the image to the given stream using the currently loaded image format.
/// </summary> /// </summary>
/// <param name="stream">The stream to save the image to.</param> /// <param name="stream">The stream to save the image to.</param>
/// <param name="format">The format to save the image to.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception> /// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <returns>The <see cref="Image{TPixel}"/></returns> /// <returns>The <see cref="Image{TPixel}"/></returns>
public Image<TPixel> Save(Stream stream) public Image<TPixel> Save(Stream stream, IImageFormat format)
{ {
return this.Save(stream, (IEncoderOptions)null); Guard.NotNull(format, nameof(format));
} IImageEncoder encoder = this.Configuration.FindEncoder(format);
/// <summary> if (encoder == null)
/// 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); var stringBuilder = new StringBuilder();
} stringBuilder.AppendLine("Can't find encoder for provided mime type. Available encoded:");
/// <summary> foreach (KeyValuePair<IImageFormat, IImageEncoder> val in this.Configuration.ImageEncoders)
/// 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); stringBuilder.AppendLine($" - {val.Key.Name} : {val.Value.GetType().Name}");
} }
/// <summary> throw new NotSupportedException(stringBuilder.ToString());
/// Saves the image to the given stream using the given image format.
/// </summary>
/// <param name="stream">The stream to save the image to.</param>
/// <param name="format">The format to save the image as.</param>
/// <param name="options">The options for the encoder.</param>
/// <returns>The <see cref="Image{TPixel}"/></returns>
public Image<TPixel> Save(Stream stream, IImageFormat format, IEncoderOptions options)
{
Guard.NotNull(format, nameof(format));
return this.Save(stream, format.Encoder, options);
} }
/// <summary> return this.Save(stream, encoder);
/// 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>
/// <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)
{
return this.Save(stream, encoder, null);
} }
/// <summary> /// <summary>
@ -226,17 +181,16 @@ namespace ImageSharp
/// </summary> /// </summary>
/// <param name="stream">The stream to save the image to.</param> /// <param name="stream">The stream to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</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> /// <exception cref="System.ArgumentNullException">Thrown if the stream or encoder is null.</exception>
/// <returns> /// <returns>
/// The <see cref="Image{TPixel}"/>. /// The <see cref="Image{TPixel}"/>.
/// </returns> /// </returns>
public Image<TPixel> Save(Stream stream, IImageEncoder encoder, IEncoderOptions options) public Image<TPixel> Save(Stream stream, IImageEncoder encoder)
{ {
Guard.NotNull(stream, nameof(stream)); Guard.NotNull(stream, nameof(stream));
Guard.NotNull(encoder, nameof(encoder)); Guard.NotNull(encoder, nameof(encoder));
encoder.Encode(this, stream, options); encoder.Encode(this, stream);
return this; return this;
} }
@ -250,64 +204,37 @@ namespace ImageSharp
/// <returns>The <see cref="Image{TPixel}"/></returns> /// <returns>The <see cref="Image{TPixel}"/></returns>
public Image<TPixel> Save(string filePath) public Image<TPixel> Save(string filePath)
{ {
return this.Save(filePath, (IEncoderOptions)null); Guard.NotNullOrEmpty(filePath, nameof(filePath));
}
/// <summary>
/// Saves the image to the given stream using the currently loaded image format.
/// </summary>
/// <param name="filePath">The file path to save the image to.</param>
/// <param name="options">The options for the encoder.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <returns>The <see cref="Image{TPixel}"/></returns>
public Image<TPixel> Save(string filePath, IEncoderOptions options)
{
string ext = Path.GetExtension(filePath).Trim('.'); string ext = Path.GetExtension(filePath).Trim('.');
IImageFormat format = this.Configuration.ImageFormats.SingleOrDefault(f => f.SupportedExtensions.Contains(ext, StringComparer.OrdinalIgnoreCase)); var format = this.Configuration.FindFormatByFileExtensions(ext);
if (format == null) if (format == null)
{ {
throw new InvalidOperationException($"No image formats have been registered for the file extension '{ext}'."); var stringBuilder = new StringBuilder();
stringBuilder.AppendLine($"Can't find a format that is associated with the file extention '{ext}'. Registered formats with there extensions include:");
foreach (IImageFormat fmt in this.Configuration.ImageFormats)
{
stringBuilder.AppendLine($" - {fmt.Name} : {string.Join(", ", fmt.FileExtensions)}");
} }
return this.Save(filePath, format, options); throw new NotSupportedException(stringBuilder.ToString());
} }
/// <summary> IImageEncoder encoder = this.Configuration.FindEncoder(format);
/// Saves the image to the given stream using the currently loaded image format.
/// </summary> if (encoder == null)
/// <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); var stringBuilder = new StringBuilder();
stringBuilder.AppendLine($"Can't find encoder for file extention '{ext}' using image format '{format.Name}'. Registered encoders include:");
foreach (KeyValuePair<IImageFormat, IImageEncoder> enc in this.Configuration.ImageEncoders)
{
stringBuilder.AppendLine($" - {enc.Key} : {enc.Value.GetType().Name}");
} }
/// <summary> throw new NotSupportedException(stringBuilder.ToString());
/// Saves the image to the given stream using the currently loaded image format.
/// </summary>
/// <param name="filePath">The file path to save the image to.</param>
/// <param name="format">The format to save the image as.</param>
/// <param name="options">The options for the encoder.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the format is null.</exception>
/// <returns>The <see cref="Image{TPixel}"/></returns>
public Image<TPixel> Save(string filePath, IImageFormat format, IEncoderOptions options)
{
Guard.NotNull(format, nameof(format));
return this.Save(filePath, format.Encoder, options);
} }
/// <summary> return this.Save(filePath, encoder);
/// Saves the image to the given stream using the currently loaded image format.
/// </summary>
/// <param name="filePath">The file path to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</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)
{
return this.Save(filePath, encoder, null);
} }
/// <summary> /// <summary>
@ -315,15 +242,14 @@ namespace ImageSharp
/// </summary> /// </summary>
/// <param name="filePath">The file path to save the image to.</param> /// <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="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> /// <exception cref="System.ArgumentNullException">Thrown if the encoder is null.</exception>
/// <returns>The <see cref="Image{TPixel}"/></returns> /// <returns>The <see cref="Image{TPixel}"/></returns>
public Image<TPixel> Save(string filePath, IImageEncoder encoder, IEncoderOptions options) public Image<TPixel> Save(string filePath, IImageEncoder encoder)
{ {
Guard.NotNull(encoder, nameof(encoder)); Guard.NotNull(encoder, nameof(encoder));
using (Stream fs = this.Configuration.FileSystem.Create(filePath)) using (Stream fs = this.Configuration.FileSystem.Create(filePath))
{ {
return this.Save(fs, encoder, options); return this.Save(fs, encoder);
} }
} }
#endif #endif
@ -331,21 +257,22 @@ namespace ImageSharp
/// <inheritdoc/> /// <inheritdoc/>
public override string ToString() public override string ToString()
{ {
return $"Image: {this.Width}x{this.Height}"; return $"Image<{typeof(TPixel).Name}>: {this.Width}x{this.Height}";
} }
/// <summary> /// <summary>
/// Returns a Base64 encoded string from the given image. /// Returns a Base64 encoded string from the given image.
/// </summary> /// </summary>
/// <example><see href="data:image/gif;base64,R0lGODlhAQABAIABAEdJRgAAACwAAAAAAQABAAACAkQBAA=="/></example> /// <example><see href="data:image/gif;base64,R0lGODlhAQABAIABAEdJRgAAACwAAAAAAQABAAACAkQBAA=="/></example>
/// <param name="format">The format.</param>
/// <returns>The <see cref="string"/></returns> /// <returns>The <see cref="string"/></returns>
public string ToBase64String() public string ToBase64String(IImageFormat format)
{ {
using (MemoryStream stream = new MemoryStream()) using (var stream = new MemoryStream())
{ {
this.Save(stream); this.Save(stream, format);
stream.Flush(); stream.Flush();
return $"data:{this.CurrentImageFormat.MimeType};base64,{Convert.ToBase64String(stream.ToArray())}"; return $"data:{format.DefaultMimeType};base64,{Convert.ToBase64String(stream.ToArray())}";
} }
} }
@ -360,7 +287,7 @@ namespace ImageSharp
{ {
scaleFunc = PackedPixelConverterHelper.ComputeScaleFunction<TPixel, TPixel2>(scaleFunc); scaleFunc = PackedPixelConverterHelper.ComputeScaleFunction<TPixel, TPixel2>(scaleFunc);
Image<TPixel2> target = new Image<TPixel2>(this.Configuration, this.Width, this.Height); var target = new Image<TPixel2>(this.Configuration, this.Width, this.Height);
target.CopyProperties(this); target.CopyProperties(this);
using (PixelAccessor<TPixel> pixels = this.Lock()) using (PixelAccessor<TPixel> pixels = this.Lock())
@ -374,7 +301,7 @@ namespace ImageSharp
{ {
for (int x = 0; x < target.Width; x++) for (int x = 0; x < target.Width; x++)
{ {
TPixel2 color = default(TPixel2); var color = default(TPixel2);
color.PackFromVector4(scaleFunc(pixels[x, y].ToVector4())); color.PackFromVector4(scaleFunc(pixels[x, y].ToVector4()));
targetPixels[x, y] = color; targetPixels[x, y] = color;
} }
@ -418,7 +345,6 @@ namespace ImageSharp
/// </param> /// </param>
private void CopyProperties(IImage other) private void CopyProperties(IImage other)
{ {
this.CurrentImageFormat = other.CurrentImageFormat;
this.MetaData = new ImageMetaData(other.MetaData); this.MetaData = new ImageMetaData(other.MetaData);
} }
} }

35
src/ImageSharp/ImageFormats.cs

@ -0,0 +1,35 @@
// <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
{
using ImageSharp.Formats;
/// <summary>
/// The static collection of all the default image formats
/// </summary>
public static class ImageFormats
{
/// <summary>
/// The format details for the jpegs.
/// </summary>
public static readonly IImageFormat Jpeg = new JpegFormat();
/// <summary>
/// The format details for the pngs.
/// </summary>
public static readonly IImageFormat Png = new PngFormat();
/// <summary>
/// The format details for the gifs.
/// </summary>
public static readonly IImageFormat Gif = new GifFormat();
/// <summary>
/// The format details for the bitmaps.
/// </summary>
public static readonly IImageFormat Bitmap = new BmpFormat();
}
}

1
src/ImageSharp/ImageSharp.csproj

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

10
src/ImageSharp/Memory/Buffer.cs

@ -115,6 +115,16 @@ namespace ImageSharp.Memory
} }
} }
/// <summary>
/// Converts <see cref="Buffer{T}"/> to an <see cref="ReadOnlySpan{T}"/>.
/// </summary>
/// <param name="buffer">The <see cref="Buffer{T}"/> to convert.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator ReadOnlySpan<T>(Buffer<T> buffer)
{
return new ReadOnlySpan<T>(buffer.Array, 0, buffer.Length);
}
/// <summary> /// <summary>
/// Converts <see cref="Buffer{T}"/> to an <see cref="Span{T}"/>. /// Converts <see cref="Buffer{T}"/> to an <see cref="Span{T}"/>.
/// </summary> /// </summary>

6
src/ImageSharp/MetaData/ImageMetaData.cs

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

4
tests/ImageSharp.Benchmarks/BenchmarkBase.cs

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

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

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

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

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

249
tests/ImageSharp.Tests/ConfigurationTests.cs

@ -13,7 +13,7 @@ namespace ImageSharp.Tests
using ImageSharp.Formats; using ImageSharp.Formats;
using ImageSharp.IO; using ImageSharp.IO;
using ImageSharp.PixelFormats; using ImageSharp.PixelFormats;
using Moq;
using Xunit; using Xunit;
/// <summary> /// <summary>
@ -21,22 +21,27 @@ namespace ImageSharp.Tests
/// </summary> /// </summary>
public class ConfigurationTests public class ConfigurationTests
{ {
public Configuration ConfigurationEmpty { get; private set; }
public Configuration DefaultConfiguration { get; private set; }
public ConfigurationTests()
{
this.DefaultConfiguration = Configuration.CreateDefaultInstance();
this.ConfigurationEmpty = new Configuration();
}
[Fact] [Fact]
public void DefaultsToLocalFileSystem() public void DefaultsToLocalFileSystem()
{ {
var configuration = Configuration.CreateDefaultInstance(); Assert.IsType<LocalFileSystem>(DefaultConfiguration.FileSystem);
Assert.IsType<LocalFileSystem>(ConfigurationEmpty.FileSystem);
ImageSharp.IO.IFileSystem fs = configuration.FileSystem;
Assert.IsType<LocalFileSystem>(fs);
} }
[Fact] [Fact]
public void IfAutoloadWellknwonFormatesIsTrueAllFormateAreLoaded() public void IfAutoloadWellknwonFormatesIsTrueAllFormateAreLoaded()
{ {
var configuration = Configuration.CreateDefaultInstance(); Assert.Equal(4, DefaultConfiguration.ImageEncoders.Count());
Assert.Equal(4, DefaultConfiguration.ImageDecoders.Count());
Assert.Equal(4, configuration.ImageFormats.Count);
} }
/// <summary> /// <summary>
@ -67,243 +72,97 @@ namespace ImageSharp.Tests
Assert.True(Configuration.Default.ParallelOptions.MaxDegreeOfParallelism == Environment.ProcessorCount); Assert.True(Configuration.Default.ParallelOptions.MaxDegreeOfParallelism == Environment.ProcessorCount);
} }
/// <summary>
/// Test that the default configuration parallel options is not null.
/// </summary>
[Fact]
public void TestDefultConfigurationImageFormatsIsNotNull()
{
Assert.True(Configuration.Default.ImageFormats != null);
}
/// <summary>
/// Tests the <see cref="M:Configuration.AddImageFormat"/> method throws an exception
/// when the format is null.
/// </summary>
[Fact] [Fact]
public void TestAddImageFormatThrowsWithNullFormat() public void AddImageFormatDetectorNullthrows()
{ {
Assert.Throws<ArgumentNullException>(() => Assert.Throws<ArgumentNullException>(() =>
{ {
Configuration.Default.AddImageFormat(null); DefaultConfiguration.AddImageFormatDetector(null);
}); });
} }
/// <summary>
/// Tests the <see cref="M:Configuration.AddImageFormat"/> method throws an exception
/// when the encoder is null.
/// </summary>
[Fact] [Fact]
public void TestAddImageFormatThrowsWithNullEncoder() public void RegisterNullMimeTypeEncoder()
{ {
var format = new TestFormat { Encoder = null };
Assert.Throws<ArgumentNullException>(() => Assert.Throws<ArgumentNullException>(() =>
{ {
Configuration.Default.AddImageFormat(format); DefaultConfiguration.SetEncoder(null, new Mock<IImageEncoder>().Object);
}); });
}
/// <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>(() => Assert.Throws<ArgumentNullException>(() =>
{ {
Configuration.Default.AddImageFormat(format); DefaultConfiguration.SetEncoder(ImageFormats.Bitmap, null);
}); });
}
/// <summary>
/// Tests the <see cref="M:Configuration.AddImageFormat"/> method throws an exception
/// when the mime type is null or an empty string.
/// </summary>
[Fact]
public void TestAddImageFormatThrowsWithNullOrEmptyMimeType()
{
var format = new TestFormat { MimeType = null };
Assert.Throws<ArgumentNullException>(() => Assert.Throws<ArgumentNullException>(() =>
{ {
Configuration.Default.AddImageFormat(format); DefaultConfiguration.SetEncoder(null, null);
});
format = new TestFormat { MimeType = string.Empty };
Assert.Throws<ArgumentException>(() =>
{
Configuration.Default.AddImageFormat(format);
}); });
} }
/// <summary>
/// Tests the <see cref="M:Configuration.AddImageFormat"/> method throws an exception
/// when the extension is null or an empty string.
/// </summary>
[Fact] [Fact]
public void TestAddImageFormatThrowsWithNullOrEmptyExtension() public void RegisterNullSetDecoder()
{ {
var format = new TestFormat { Extension = null };
Assert.Throws<ArgumentNullException>(() => Assert.Throws<ArgumentNullException>(() =>
{ {
Configuration.Default.AddImageFormat(format); DefaultConfiguration.SetDecoder(null, new Mock<IImageDecoder>().Object);
});
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>(() => Assert.Throws<ArgumentNullException>(() =>
{ {
Configuration.Default.AddImageFormat(format); DefaultConfiguration.SetDecoder(ImageFormats.Bitmap, null);
}); });
Assert.Throws<ArgumentNullException>(() =>
format = new TestFormat { SupportedExtensions = Enumerable.Empty<string>() };
Assert.Throws<ArgumentException>(() =>
{ {
Configuration.Default.AddImageFormat(format); DefaultConfiguration.SetDecoder(null, null);
}); });
} }
/// <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] [Fact]
public void TestAddImageFormatThrowsWithoutDefaultExtension() public void RegisterMimeTypeEncoderReplacesLast()
{ {
var format = new TestFormat { Extension = "test" }; var encoder1 = new Mock<IImageEncoder>().Object;
ConfigurationEmpty.SetEncoder(TestFormat.GlobalTestFormat, encoder1);
var found = ConfigurationEmpty.FindEncoder(TestFormat.GlobalTestFormat);
Assert.Equal(encoder1, found);
Assert.Throws<ArgumentException>(() => var encoder2 = new Mock<IImageEncoder>().Object;
{ ConfigurationEmpty.SetEncoder(TestFormat.GlobalTestFormat, encoder2);
Configuration.Default.AddImageFormat(format); var found2 = ConfigurationEmpty.FindEncoder(TestFormat.GlobalTestFormat);
}); Assert.Equal(encoder2, found2);
Assert.NotEqual(found, found2);
} }
/// <summary>
/// Tests the <see cref="M:Configuration.AddImageFormat"/> method throws an exception
/// when the supported extensions list contains an empty string.
/// </summary>
[Fact] [Fact]
public void TestAddImageFormatThrowsWithEmptySupportedExtension() public void RegisterMimeTypeDecoderReplacesLast()
{
var format = new TestFormat
{ {
Extension = "test", var decoder1 = new Mock<IImageDecoder>().Object;
SupportedExtensions = new[] { "test", string.Empty } ConfigurationEmpty.SetDecoder(TestFormat.GlobalTestFormat, decoder1);
}; var found = ConfigurationEmpty.FindDecoder(TestFormat.GlobalTestFormat);
Assert.Equal(decoder1, found);
Assert.Throws<ArgumentException>(() => var decoder2 = new Mock<IImageDecoder>().Object;
{ ConfigurationEmpty.SetDecoder(TestFormat.GlobalTestFormat, decoder2);
Configuration.Default.AddImageFormat(format); var found2 = ConfigurationEmpty.FindDecoder(TestFormat.GlobalTestFormat);
}); Assert.Equal(decoder2, found2);
Assert.NotEqual(found, found2);
} }
/// <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] [Fact]
public void TestImageUsesDefaultConfiguration() public void ConstructorCallConfigureOnFormatProvider()
{ {
Configuration.Default.AddImageFormat(new PngFormat()); var provider = new Mock<IConfigurationModule>();
var config = new Configuration(provider.Object);
var image = new Image<Rgba32>(1, 1); provider.Verify(x => x.Configure(config));
Assert.Equal(image.Configuration.ParallelOptions, Configuration.Default.ParallelOptions);
Assert.Equal(image.Configuration.ImageFormats, Configuration.Default.ImageFormats);
} }
/// <summary>
/// Test that the default image constructor copies the configuration.
/// </summary>
[Fact] [Fact]
public void TestImageCopiesConfiguration() public void AddFormatCallsConfig()
{
Configuration.Default.AddImageFormat(new PngFormat());
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(); var provider = new Mock<IConfigurationModule>();
} var config = new Configuration();
} config.Configure(provider.Object);
/// <inheritdoc /> provider.Verify(x => x.Configure(config));
public bool IsSupportedFileFormat(byte[] header)
{
throw new NotImplementedException();
}
} }
} }
} }

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

@ -23,8 +23,6 @@ namespace ImageSharp.Tests.Drawing
{ {
string path = this.CreateOutputDirectory("Drawing", "BezierLine"); string path = this.CreateOutputDirectory("Drawing", "BezierLine");
using (Image<Rgba32> image = new Image<Rgba32>(500, 500)) using (Image<Rgba32> image = new Image<Rgba32>(500, 500))
{
using (FileStream output = File.OpenWrite($"{path}/Simple.png"))
{ {
image.BackgroundColor(Rgba32.Blue) image.BackgroundColor(Rgba32.Blue)
.DrawBeziers(Rgba32.HotPink, 5, .DrawBeziers(Rgba32.HotPink, 5,
@ -34,8 +32,7 @@ namespace ImageSharp.Tests.Drawing
new Vector2(240, 30), new Vector2(240, 30),
new Vector2(300, 400) new Vector2(300, 400)
}) })
.Save(output); .Save($"{path}/Simple.png");
}
using (PixelAccessor<Rgba32> sourcePixels = image.Lock()) using (PixelAccessor<Rgba32> sourcePixels = image.Lock())
{ {
@ -65,20 +62,17 @@ namespace ImageSharp.Tests.Drawing
Rgba32 color = new Rgba32(Rgba32.HotPink.R, Rgba32.HotPink.G, Rgba32.HotPink.B, 150); Rgba32 color = new Rgba32(Rgba32.HotPink.R, Rgba32.HotPink.G, Rgba32.HotPink.B, 150);
using (Image<Rgba32> image = new Image<Rgba32>(500, 500)) using (Image<Rgba32> image = new Image<Rgba32>(500, 500))
{
using (FileStream output = File.OpenWrite($"{path}/Opacity.png"))
{ {
image.BackgroundColor(Rgba32.Blue) image.BackgroundColor(Rgba32.Blue)
.DrawBeziers(color, .DrawBeziers(color,
10, 10,
new SixLabors.Primitives.PointF[] { new SixLabors.Primitives.PointF[]{
new Vector2(10, 400), new Vector2(10, 400),
new Vector2(30, 10), new Vector2(30, 10),
new Vector2(240, 30), new Vector2(240, 30),
new Vector2(300, 400) new Vector2(300, 400)
}) })
.Save(output); .Save($"{path}/Opacity.png");
}
//shift background color towards forground color by the opacity amount //shift background color towards forground color by the opacity amount
Rgba32 mergedColor = new Rgba32(Vector4.Lerp(Rgba32.Blue.ToVector4(), Rgba32.HotPink.ToVector4(), 150f / 255f)); Rgba32 mergedColor = new Rgba32(Vector4.Lerp(Rgba32.Blue.ToVector4(), Rgba32.HotPink.ToVector4(), 150f / 255f));

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

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

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

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

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

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

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

@ -33,14 +33,11 @@ namespace ImageSharp.Tests.Drawing
new Vector2(65, 137))); new Vector2(65, 137)));
using (Image<Rgba32> image = new Image<Rgba32>(500, 500)) using (Image<Rgba32> image = new Image<Rgba32>(500, 500))
{
using (FileStream output = File.OpenWrite($"{path}/Simple.png"))
{ {
image image
.BackgroundColor(Rgba32.Blue) .BackgroundColor(Rgba32.Blue)
.Draw(Rgba32.HotPink, 5, simplePath.Clip(hole1)) .Draw(Rgba32.HotPink, 5, simplePath.Clip(hole1))
.Save(output); .Save($"{path}/Simple.png");
}
using (PixelAccessor<Rgba32> sourcePixels = image.Lock()) using (PixelAccessor<Rgba32> sourcePixels = image.Lock())
{ {
@ -83,14 +80,11 @@ namespace ImageSharp.Tests.Drawing
new Vector2(235, 57))); new Vector2(235, 57)));
using (Image<Rgba32> image = new Image<Rgba32>(500, 500)) using (Image<Rgba32> image = new Image<Rgba32>(500, 500))
{
using (FileStream output = File.OpenWrite($"{path}/SimpleVanishHole.png"))
{ {
image image
.BackgroundColor(Rgba32.Blue) .BackgroundColor(Rgba32.Blue)
.Draw(Rgba32.HotPink, 5, simplePath.Clip(hole1)) .Draw(Rgba32.HotPink, 5, simplePath.Clip(hole1))
.Save(output); .Save($"{path}/SimpleVanishHole.png");
}
using (PixelAccessor<Rgba32> sourcePixels = image.Lock()) using (PixelAccessor<Rgba32> sourcePixels = image.Lock())
{ {
@ -134,14 +128,11 @@ namespace ImageSharp.Tests.Drawing
new Vector2(65, 137))); new Vector2(65, 137)));
using (Image<Rgba32> image = new Image<Rgba32>(500, 500)) using (Image<Rgba32> image = new Image<Rgba32>(500, 500))
{
using (FileStream output = File.OpenWrite($"{path}/SimpleOverlapping.png"))
{ {
image image
.BackgroundColor(Rgba32.Blue) .BackgroundColor(Rgba32.Blue)
.Draw(Rgba32.HotPink, 5, simplePath.Clip(hole1)) .Draw(Rgba32.HotPink, 5, simplePath.Clip(hole1))
.Save(output); .Save($"{path}/SimpleOverlapping.png");
}
using (PixelAccessor<Rgba32> sourcePixels = image.Lock()) using (PixelAccessor<Rgba32> sourcePixels = image.Lock())
{ {
@ -180,14 +171,11 @@ namespace ImageSharp.Tests.Drawing
new Vector2(65, 137))); new Vector2(65, 137)));
using (Image<Rgba32> image = new Image<Rgba32>(500, 500)) using (Image<Rgba32> image = new Image<Rgba32>(500, 500))
{
using (FileStream output = File.OpenWrite($"{path}/Dashed.png"))
{ {
image image
.BackgroundColor(Rgba32.Blue) .BackgroundColor(Rgba32.Blue)
.Draw(Pens.Dash(Rgba32.HotPink, 5), simplePath.Clip(hole1)) .Draw(Pens.Dash(Rgba32.HotPink, 5), simplePath.Clip(hole1))
.Save(output); .Save($"{path}/Dashed.png");
}
} }
} }
@ -208,14 +196,11 @@ namespace ImageSharp.Tests.Drawing
Rgba32 color = new Rgba32(Rgba32.HotPink.R, Rgba32.HotPink.G, Rgba32.HotPink.B, 150); Rgba32 color = new Rgba32(Rgba32.HotPink.R, Rgba32.HotPink.G, Rgba32.HotPink.B, 150);
using (Image<Rgba32> image = new Image<Rgba32>(500, 500)) using (Image<Rgba32> image = new Image<Rgba32>(500, 500))
{
using (FileStream output = File.OpenWrite($"{path}/Opacity.png"))
{ {
image image
.BackgroundColor(Rgba32.Blue) .BackgroundColor(Rgba32.Blue)
.Draw(color, 5, simplePath.Clip(hole1)) .Draw(color, 5, simplePath.Clip(hole1))
.Save(output); .Save($"{path}/Opacity.png");
}
//shift background color towards forground color by the opacity amount //shift background color towards forground color by the opacity amount
Rgba32 mergedColor = new Rgba32(Vector4.Lerp(Rgba32.Blue.ToVector4(), Rgba32.HotPink.ToVector4(), 150f / 255f)); Rgba32 mergedColor = new Rgba32(Vector4.Lerp(Rgba32.Blue.ToVector4(), Rgba32.HotPink.ToVector4(), 150f / 255f));

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

@ -22,19 +22,16 @@ namespace ImageSharp.Tests.Drawing
{ {
string path = this.CreateOutputDirectory("Drawing", "Lines"); string path = this.CreateOutputDirectory("Drawing", "Lines");
using (Image<Rgba32> image = new Image<Rgba32>(500, 500)) using (Image<Rgba32> image = new Image<Rgba32>(500, 500))
{
using (FileStream output = File.OpenWrite($"{path}/Simple.png"))
{ {
image image
.BackgroundColor(Rgba32.Blue) .BackgroundColor(Rgba32.Blue)
.DrawLines(Rgba32.HotPink, 5, .DrawLines(Rgba32.HotPink, 5,
new SixLabors.Primitives.PointF[] { new SixLabors.Primitives.PointF[]{
new Vector2(10, 10), new Vector2(10, 10),
new Vector2(200, 150), new Vector2(200, 150),
new Vector2(50, 300) new Vector2(50, 300)
}) })
.Save(output); .Save($"{path}/Simple.png");
}
using (PixelAccessor<Rgba32> sourcePixels = image.Lock()) using (PixelAccessor<Rgba32> sourcePixels = image.Lock())
{ {
@ -52,8 +49,6 @@ namespace ImageSharp.Tests.Drawing
{ {
string path = this.CreateOutputDirectory("Drawing", "Lines"); string path = this.CreateOutputDirectory("Drawing", "Lines");
using (Image<Rgba32> image = new Image<Rgba32>(500, 500)) using (Image<Rgba32> image = new Image<Rgba32>(500, 500))
{
using (FileStream output = File.OpenWrite($"{path}/Simple_noantialias.png"))
{ {
image image
.BackgroundColor(Rgba32.Blue) .BackgroundColor(Rgba32.Blue)
@ -64,8 +59,7 @@ namespace ImageSharp.Tests.Drawing
new Vector2(50, 300) new Vector2(50, 300)
}, },
new GraphicsOptions(false)) new GraphicsOptions(false))
.Save(output); .Save($"{path}/Simple_noantialias.png");
}
using (PixelAccessor<Rgba32> sourcePixels = image.Lock()) using (PixelAccessor<Rgba32> sourcePixels = image.Lock())
{ {
@ -83,8 +77,6 @@ namespace ImageSharp.Tests.Drawing
{ {
string path = this.CreateOutputDirectory("Drawing", "Lines"); string path = this.CreateOutputDirectory("Drawing", "Lines");
using (Image<Rgba32> image = new Image<Rgba32>(500, 500)) using (Image<Rgba32> image = new Image<Rgba32>(500, 500))
{
using (FileStream output = File.OpenWrite($"{path}/Dashed.png"))
{ {
image image
.BackgroundColor(Rgba32.Blue) .BackgroundColor(Rgba32.Blue)
@ -94,8 +86,7 @@ namespace ImageSharp.Tests.Drawing
new Vector2(200, 150), new Vector2(200, 150),
new Vector2(50, 300) new Vector2(50, 300)
}) })
.Save(output); .Save($"{path}/Dashed.png");
}
} }
} }
@ -104,8 +95,6 @@ namespace ImageSharp.Tests.Drawing
{ {
string path = this.CreateOutputDirectory("Drawing", "Lines"); string path = this.CreateOutputDirectory("Drawing", "Lines");
using (Image<Rgba32> image = new Image<Rgba32>(500, 500)) using (Image<Rgba32> image = new Image<Rgba32>(500, 500))
{
using (FileStream output = File.OpenWrite($"{path}/Dot.png"))
{ {
image image
.BackgroundColor(Rgba32.Blue) .BackgroundColor(Rgba32.Blue)
@ -115,8 +104,7 @@ namespace ImageSharp.Tests.Drawing
new Vector2(200, 150), new Vector2(200, 150),
new Vector2(50, 300) new Vector2(50, 300)
}) })
.Save(output); .Save($"{path}/Dot.png");
}
} }
} }
@ -125,8 +113,6 @@ namespace ImageSharp.Tests.Drawing
{ {
string path = this.CreateOutputDirectory("Drawing", "Lines"); string path = this.CreateOutputDirectory("Drawing", "Lines");
using (Image<Rgba32> image = new Image<Rgba32>(500, 500)) using (Image<Rgba32> image = new Image<Rgba32>(500, 500))
{
using (FileStream output = File.OpenWrite($"{path}/DashDot.png"))
{ {
image image
.BackgroundColor(Rgba32.Blue) .BackgroundColor(Rgba32.Blue)
@ -136,8 +122,7 @@ namespace ImageSharp.Tests.Drawing
new Vector2(200, 150), new Vector2(200, 150),
new Vector2(50, 300) new Vector2(50, 300)
}) })
.Save(output); .Save($"{path}/DashDot.png");
}
} }
} }
@ -147,8 +132,6 @@ namespace ImageSharp.Tests.Drawing
string path = this.CreateOutputDirectory("Drawing", "Lines"); string path = this.CreateOutputDirectory("Drawing", "Lines");
Image<Rgba32> image = new Image<Rgba32>(500, 500); Image<Rgba32> image = new Image<Rgba32>(500, 500);
using (FileStream output = File.OpenWrite($"{path}/DashDotDot.png"))
{
image image
.BackgroundColor(Rgba32.Blue) .BackgroundColor(Rgba32.Blue)
.DrawLines(Pens.DashDotDot(Rgba32.HotPink, 5), new SixLabors.Primitives.PointF[] { .DrawLines(Pens.DashDotDot(Rgba32.HotPink, 5), new SixLabors.Primitives.PointF[] {
@ -156,8 +139,7 @@ namespace ImageSharp.Tests.Drawing
new Vector2(200, 150), new Vector2(200, 150),
new Vector2(50, 300) new Vector2(50, 300)
}) })
.Save(output); .Save($"{path}/DashDotDot.png");
}
} }
[Fact] [Fact]
@ -169,9 +151,6 @@ namespace ImageSharp.Tests.Drawing
Image<Rgba32> image = new Image<Rgba32>(500, 500); Image<Rgba32> image = new Image<Rgba32>(500, 500);
using (FileStream output = File.OpenWrite($"{path}/Opacity.png"))
{
image image
.BackgroundColor(Rgba32.Blue) .BackgroundColor(Rgba32.Blue)
.DrawLines(color, 10, new SixLabors.Primitives.PointF[] { .DrawLines(color, 10, new SixLabors.Primitives.PointF[] {
@ -179,11 +158,10 @@ namespace ImageSharp.Tests.Drawing
new Vector2(200, 150), new Vector2(200, 150),
new Vector2(50, 300) new Vector2(50, 300)
}) })
.Save(output); .Save($"{path}/Opacity.png");
}
//shift background color towards forground color by the opacity amount //shift background color towards forground color by the opacity amount
Rgba32 mergedColor = new Rgba32(Vector4.Lerp(Rgba32.Blue.ToVector4(), Rgba32.HotPink.ToVector4(), 150f/255f)); Rgba32 mergedColor = new Rgba32(Vector4.Lerp(Rgba32.Blue.ToVector4(), Rgba32.HotPink.ToVector4(), 150f / 255f));
using (PixelAccessor<Rgba32> sourcePixels = image.Lock()) using (PixelAccessor<Rgba32> sourcePixels = image.Lock())
{ {
@ -202,8 +180,6 @@ namespace ImageSharp.Tests.Drawing
Image<Rgba32> image = new Image<Rgba32>(500, 500); Image<Rgba32> image = new Image<Rgba32>(500, 500);
using (FileStream output = File.OpenWrite($"{path}/Rectangle.png"))
{
image image
.BackgroundColor(Rgba32.Blue) .BackgroundColor(Rgba32.Blue)
.DrawLines(Rgba32.HotPink, 10, new SixLabors.Primitives.PointF[] { .DrawLines(Rgba32.HotPink, 10, new SixLabors.Primitives.PointF[] {
@ -212,8 +188,7 @@ namespace ImageSharp.Tests.Drawing
new Vector2(200, 150), new Vector2(200, 150),
new Vector2(10, 150) new Vector2(10, 150)
}) })
.Save(output); .Save($"{path}/Rectangle.png");
}
using (PixelAccessor<Rgba32> sourcePixels = image.Lock()) using (PixelAccessor<Rgba32> sourcePixels = image.Lock())
{ {
@ -228,4 +203,4 @@ namespace ImageSharp.Tests.Drawing
} }
} }
} }

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

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

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

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

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

@ -27,14 +27,11 @@ namespace ImageSharp.Tests.Drawing
new Vector2(300, 400) new Vector2(300, 400)
}; };
using (Image<Rgba32> image = new Image<Rgba32>(500, 500)) using (Image<Rgba32> image = new Image<Rgba32>(500, 500))
{
using (FileStream output = File.OpenWrite($"{path}/Simple.png"))
{ {
image image
.BackgroundColor(Rgba32.Blue) .BackgroundColor(Rgba32.Blue)
.Fill(Rgba32.HotPink, new Polygon(new CubicBezierLineSegment(simplePath))) .Fill(Rgba32.HotPink, new Polygon(new CubicBezierLineSegment(simplePath)))
.Save(output); .Save($"{path}/Simple.png");
}
using (PixelAccessor<Rgba32> sourcePixels = image.Lock()) using (PixelAccessor<Rgba32> sourcePixels = image.Lock())
{ {
@ -62,14 +59,11 @@ namespace ImageSharp.Tests.Drawing
Rgba32 color = new Rgba32(Rgba32.HotPink.R, Rgba32.HotPink.G, Rgba32.HotPink.B, 150); Rgba32 color = new Rgba32(Rgba32.HotPink.R, Rgba32.HotPink.G, Rgba32.HotPink.B, 150);
using (Image<Rgba32> image = new Image<Rgba32>(500, 500)) using (Image<Rgba32> image = new Image<Rgba32>(500, 500))
{
using (FileStream output = File.OpenWrite($"{path}/Opacity.png"))
{ {
image image
.BackgroundColor(Rgba32.Blue) .BackgroundColor(Rgba32.Blue)
.Fill(color, new Polygon(new CubicBezierLineSegment(simplePath))) .Fill(color, new Polygon(new CubicBezierLineSegment(simplePath)))
.Save(output); .Save($"{path}/Opacity.png");
}
//shift background color towards forground color by the opacity amount //shift background color towards forground color by the opacity amount
Rgba32 mergedColor = new Rgba32(Vector4.Lerp(Rgba32.Blue.ToVector4(), Rgba32.HotPink.ToVector4(), 150f / 255f)); Rgba32 mergedColor = new Rgba32(Vector4.Lerp(Rgba32.Blue.ToVector4(), Rgba32.HotPink.ToVector4(), 150f / 255f));

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

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

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

@ -30,13 +30,10 @@ namespace ImageSharp.Tests.Drawing
}; };
using (Image<Rgba32> image = new Image<Rgba32>(500, 500)) using (Image<Rgba32> image = new Image<Rgba32>(500, 500))
{
using (FileStream output = File.OpenWrite($"{path}/Simple.png"))
{ {
image image
.FillPolygon(Rgba32.HotPink, simplePath, new GraphicsOptions(true)) .FillPolygon(Rgba32.HotPink, simplePath, new GraphicsOptions(true))
.Save(output); .Save($"{path}/Simple.png");
}
using (PixelAccessor<Rgba32> sourcePixels = image.Lock()) using (PixelAccessor<Rgba32> sourcePixels = image.Lock())
{ {
@ -56,13 +53,10 @@ namespace ImageSharp.Tests.Drawing
}; };
using (Image<Rgba32> image = new Image<Rgba32>(500, 500)) using (Image<Rgba32> image = new Image<Rgba32>(500, 500))
{
using (FileStream output = File.OpenWrite($"{path}/Pattern.png"))
{ {
image image
.FillPolygon(Brushes.Horizontal(Rgba32.HotPink), simplePath, new GraphicsOptions(true)) .FillPolygon(Brushes.Horizontal(Rgba32.HotPink), simplePath, new GraphicsOptions(true))
.Save(output); .Save($"{path}/Pattern.png");
}
using (PixelAccessor<Rgba32> sourcePixels = image.Lock()) using (PixelAccessor<Rgba32> sourcePixels = image.Lock())
{ {
@ -82,12 +76,11 @@ namespace ImageSharp.Tests.Drawing
}; };
using (Image<Rgba32> image = new Image<Rgba32>(500, 500)) using (Image<Rgba32> image = new Image<Rgba32>(500, 500))
using (FileStream output = File.OpenWrite($"{path}/Simple_NoAntialias.png"))
{ {
image image
.BackgroundColor(Rgba32.Blue) .BackgroundColor(Rgba32.Blue)
.FillPolygon(Rgba32.HotPink, simplePath, new GraphicsOptions(false)) .FillPolygon(Rgba32.HotPink, simplePath, new GraphicsOptions(false))
.Save(output); .Save($"{path}/Simple_NoAntialias.png");
using (PixelAccessor<Rgba32> sourcePixels = image.Lock()) using (PixelAccessor<Rgba32> sourcePixels = image.Lock())
{ {
@ -114,14 +107,13 @@ namespace ImageSharp.Tests.Drawing
using (Image<Rgba32> brushImage = TestFile.Create(TestImages.Bmp.Car).CreateImage()) using (Image<Rgba32> brushImage = TestFile.Create(TestImages.Bmp.Car).CreateImage())
using (Image<Rgba32> image = new Image<Rgba32>(500, 500)) using (Image<Rgba32> image = new Image<Rgba32>(500, 500))
using (FileStream output = File.OpenWrite($"{path}/Image.png"))
{ {
ImageBrush<Rgba32> brush = new ImageBrush<Rgba32>(brushImage); ImageBrush<Rgba32> brush = new ImageBrush<Rgba32>(brushImage);
image image
.BackgroundColor(Rgba32.Blue) .BackgroundColor(Rgba32.Blue)
.FillPolygon(brush, simplePath) .FillPolygon(brush, simplePath)
.Save(output); .Save($"{path}/Image.png");
} }
} }
@ -137,14 +129,11 @@ namespace ImageSharp.Tests.Drawing
Rgba32 color = new Rgba32(Rgba32.HotPink.R, Rgba32.HotPink.G, Rgba32.HotPink.B, 150); Rgba32 color = new Rgba32(Rgba32.HotPink.R, Rgba32.HotPink.G, Rgba32.HotPink.B, 150);
using (Image<Rgba32> image = new Image<Rgba32>(500, 500)) using (Image<Rgba32> image = new Image<Rgba32>(500, 500))
{
using (FileStream output = File.OpenWrite($"{path}/Opacity.png"))
{ {
image image
.BackgroundColor(Rgba32.Blue) .BackgroundColor(Rgba32.Blue)
.FillPolygon(color, simplePath) .FillPolygon(color, simplePath)
.Save(output); .Save($"{path}/Opacity.png");
}
//shift background color towards forground color by the opacity amount //shift background color towards forground color by the opacity amount
Rgba32 mergedColor = new Rgba32(Vector4.Lerp(Rgba32.Blue.ToVector4(), Rgba32.HotPink.ToVector4(), 150f / 255f)); Rgba32 mergedColor = new Rgba32(Vector4.Lerp(Rgba32.Blue.ToVector4(), Rgba32.HotPink.ToVector4(), 150f / 255f));
@ -162,14 +151,11 @@ namespace ImageSharp.Tests.Drawing
string path = this.CreateOutputDirectory("Drawing", "FilledPolygons"); string path = this.CreateOutputDirectory("Drawing", "FilledPolygons");
using (Image<Rgba32> image = new Image<Rgba32>(500, 500)) using (Image<Rgba32> image = new Image<Rgba32>(500, 500))
{
using (FileStream output = File.OpenWrite($"{path}/Rectangle.png"))
{ {
image image
.BackgroundColor(Rgba32.Blue) .BackgroundColor(Rgba32.Blue)
.Fill(Rgba32.HotPink, new SixLabors.Shapes.RectangularePolygon(10, 10, 190, 140)) .Fill(Rgba32.HotPink, new SixLabors.Shapes.RectangularePolygon(10, 10, 190, 140))
.Save(output); .Save($"{path}/Rectangle.png");
}
using (PixelAccessor<Rgba32> sourcePixels = image.Lock()) using (PixelAccessor<Rgba32> sourcePixels = image.Lock())
{ {
@ -192,14 +178,11 @@ namespace ImageSharp.Tests.Drawing
string path = this.CreateOutputDirectory("Drawing", "FilledPolygons"); string path = this.CreateOutputDirectory("Drawing", "FilledPolygons");
using (Image<Rgba32> image = new Image<Rgba32>(100, 100)) using (Image<Rgba32> image = new Image<Rgba32>(100, 100))
{
using (FileStream output = File.OpenWrite($"{path}/Triangle.png"))
{ {
image image
.BackgroundColor(Rgba32.Blue) .BackgroundColor(Rgba32.Blue)
.Fill(Rgba32.HotPink, new RegularPolygon(50, 50, 3, 30)) .Fill(Rgba32.HotPink, new RegularPolygon(50, 50, 3, 30))
.Save(output); .Save($"{path}/Triangle.png");
}
using (PixelAccessor<Rgba32> sourcePixels = image.Lock()) using (PixelAccessor<Rgba32> sourcePixels = image.Lock())
{ {
@ -218,14 +201,11 @@ namespace ImageSharp.Tests.Drawing
Configuration config = Configuration.CreateDefaultInstance(); Configuration config = Configuration.CreateDefaultInstance();
config.ParallelOptions.MaxDegreeOfParallelism = 1; config.ParallelOptions.MaxDegreeOfParallelism = 1;
using (Image<Rgba32> image = new Image<Rgba32>(config, 100, 100)) using (Image<Rgba32> image = new Image<Rgba32>(config, 100, 100))
{
using (FileStream output = File.OpenWrite($"{path}/Septagon.png"))
{ {
image image
.BackgroundColor(Rgba32.Blue) .BackgroundColor(Rgba32.Blue)
.Fill(Rgba32.HotPink, new RegularPolygon(50, 50, 7, 30, -(float)Math.PI)) .Fill(Rgba32.HotPink, new RegularPolygon(50, 50, 7, 30, -(float)Math.PI))
.Save(output); .Save($"{path}/Septagon.png");
}
} }
} }
@ -237,15 +217,12 @@ namespace ImageSharp.Tests.Drawing
Configuration config = Configuration.CreateDefaultInstance(); Configuration config = Configuration.CreateDefaultInstance();
config.ParallelOptions.MaxDegreeOfParallelism = 1; config.ParallelOptions.MaxDegreeOfParallelism = 1;
using (Image<Rgba32> image = new Image<Rgba32>(config, 100, 100)) using (Image<Rgba32> image = new Image<Rgba32>(config, 100, 100))
{
using (FileStream output = File.OpenWrite($"{path}/ellipse.png"))
{ {
image image
.BackgroundColor(Rgba32.Blue) .BackgroundColor(Rgba32.Blue)
.Fill(Rgba32.HotPink, new EllipsePolygon(50, 50, 30, 50) .Fill(Rgba32.HotPink, new EllipsePolygon(50, 50, 30, 50)
.Rotate((float)(Math.PI / 3))) .Rotate((float)(Math.PI / 3)))
.Save(output); .Save($"{path}/ellipse.png");
}
} }
} }
@ -257,8 +234,6 @@ namespace ImageSharp.Tests.Drawing
Configuration config = Configuration.CreateDefaultInstance(); Configuration config = Configuration.CreateDefaultInstance();
config.ParallelOptions.MaxDegreeOfParallelism = 1; config.ParallelOptions.MaxDegreeOfParallelism = 1;
using (Image<Rgba32> image = new Image<Rgba32>(config, 200, 200)) using (Image<Rgba32> image = new Image<Rgba32>(config, 200, 200))
{
using (FileStream output = File.OpenWrite($"{path}/clipped-corner.png"))
{ {
image image
.Fill(Rgba32.Blue) .Fill(Rgba32.Blue)
@ -270,9 +245,8 @@ namespace ImageSharp.Tests.Drawing
new Vector2( 120, 64 ), new Vector2( 120, 64 ),
new Vector2( 120, 120 ), new Vector2( 120, 120 ),
new Vector2( 8, 120 ) new Vector2( 8, 120 )
} ) })
.Save(output); .Save($"{path}/clipped-corner.png");
}
} }
} }
} }

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

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

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

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

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

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

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

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

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

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

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save