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. 243
      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. 30
      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. 15
      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. 32
      src/ImageSharp/Formats/Gif/GifFormat.cs
  31. 41
      src/ImageSharp/Formats/Gif/GifImageFormatDetector.cs
  32. 15
      src/ImageSharp/Formats/Gif/IGifDecoderOptions.cs
  33. 25
      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. 45
      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. 19
      src/ImageSharp/Formats/Jpeg/JpegDecoder.cs
  46. 22
      src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
  47. 36
      src/ImageSharp/Formats/Jpeg/JpegEncoder.cs
  48. 52
      src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
  49. 56
      src/ImageSharp/Formats/Jpeg/JpegEncoderOptions.cs
  50. 76
      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. 28
      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. 34
      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. 92
      src/ImageSharp/Image/Image.FromStream.cs
  76. 172
      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. 28
      tests/ImageSharp.Tests/Drawing/BeziersTests.cs
  86. 29
      tests/ImageSharp.Tests/Drawing/DrawPathTests.cs
  87. 10
      tests/ImageSharp.Tests/Drawing/FillPatternTests.cs
  88. 34
      tests/ImageSharp.Tests/Drawing/FillSolidBrushTests.cs
  89. 55
      tests/ImageSharp.Tests/Drawing/LineComplexPolygonTests.cs
  90. 149
      tests/ImageSharp.Tests/Drawing/LineTests.cs
  91. 15
      tests/ImageSharp.Tests/Drawing/PolygonTests.cs
  92. 16
      tests/ImageSharp.Tests/Drawing/RecolorImageTest.cs
  93. 10
      tests/ImageSharp.Tests/Drawing/SolidBezierTests.cs
  94. 35
      tests/ImageSharp.Tests/Drawing/SolidComplexPolygonTests.cs
  95. 96
      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
# Visual Studio 15
VisualStudioVersion = 15.0.26430.6
VisualStudioVersion = 15.0.26430.14
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SolutionItems", "SolutionItems", "{C317F1B1-D75E-4C6D-83EB-80367343E0D7}"
ProjectSection(SolutionItems) = preProject
@ -49,6 +49,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{7CC6
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AvatarWithRoundedCorner", "samples\AvatarWithRoundedCorner\AvatarWithRoundedCorner.csproj", "{844FC582-4E78-4371-847D-EFD4D1103578}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChangeDefaultEncoderOptions", "samples\ChangeDefaultEncoderOptions\ChangeDefaultEncoderOptions.csproj", "{07EE511D-4BAB-4323-BAFC-3AF2BF9366F0}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
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|x86.ActiveCfg = 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
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -156,5 +170,6 @@ Global
{2BF743D8-2A06-412D-96D7-F448F00C5EA5} = {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}
{07EE511D-4BAB-4323-BAFC-3AF2BF9366F0} = {7CC6D57E-B916-43B8-B315-A0BB92F260A2}
EndGlobalSection
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
/// an image, which has an invalid format.
/// </summary>
public class ImageFormatException : Exception
public sealed class ImageFormatException : Exception
{
/// <summary>
/// Initializes a new instance of the <see cref="ImageFormatException"/> class.

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

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

243
src/ImageSharp/Configuration.cs

@ -6,8 +6,8 @@
namespace ImageSharp
{
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
@ -17,22 +17,32 @@ namespace ImageSharp
/// <summary>
/// Provides initialization code which allows extending the library.
/// </summary>
public class Configuration
public sealed class Configuration
{
/// <summary>
/// A lazily initialized configuration default instance.
/// </summary>
private static readonly Lazy<Configuration> Lazy = new Lazy<Configuration>(() => CreateDefaultInstance());
private static readonly Lazy<Configuration> Lazy = new Lazy<Configuration>(CreateDefaultInstance);
/// <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>
private readonly object syncRoot = new object();
private readonly ConcurrentDictionary<IImageFormat, IImageEncoder> mimeTypeEncoders = new ConcurrentDictionary<IImageFormat, IImageEncoder>();
/// <summary>
/// The list of supported <see cref="IImageFormat"/>.
/// The list of supported <see cref="IImageEncoder"/> keyed to mime types.
/// </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>
/// Initializes a new instance of the <see cref="Configuration" /> class.
@ -44,12 +54,15 @@ namespace ImageSharp
/// <summary>
/// Initializes a new instance of the <see cref="Configuration" /> class.
/// </summary>
/// <param name="providers">The inital set of image formats.</param>
public Configuration(params IImageFormat[] providers)
/// <param name="configurationModules">A collection of configuration modules to register</param>
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>
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>
/// Gets the global parallel options for processing tasks in parallel.
/// </summary>
public ParallelOptions ParallelOptions { get; } = new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount };
/// <summary>
/// Gets the maximum header size of all formats.
/// Gets the maximum header size of all the formats.
/// </summary>
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
/// <summary>
/// Gets or sets the fielsystem helper for accessing the local file system.
@ -81,126 +109,147 @@ namespace ImageSharp
#endif
/// <summary>
/// Adds a new <see cref="IImageFormat"/> to the collection of supported image formats.
/// Registers a new format provider.
/// </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)
{
Guard.NotNull(format, nameof(format));
Guard.NotNull(format.Encoder, nameof(format), "The encoder should not be null.");
Guard.NotNull(format.Decoder, nameof(format), "The decoder should not be null.");
Guard.NotNullOrEmpty(format.MimeType, nameof(format), "The mime type should not be null or empty.");
Guard.NotNullOrEmpty(format.Extension, nameof(format), "The extension should not be null or empty.");
Guard.NotNullOrEmpty(format.SupportedExtensions, nameof(format), "The supported extensions not be null or empty.");
Guard.NotNull(format.MimeTypes, nameof(format.MimeTypes));
Guard.NotNull(format.FileExtensions, nameof(format.FileExtensions));
this.imageFormats.Add(format);
}
this.AddImageFormatLocked(format);
/// <summary>
/// For the specified file extensions type find the e <see cref="IImageFormat"/>.
/// </summary>
/// <param name="extension">The extension to discover</param>
/// <returns>The <see cref="IImageFormat"/> if found otherwise null</returns>
public IImageFormat FindFormatByFileExtensions(string extension)
{
return this.imageFormats.FirstOrDefault(x => x.FileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase));
}
/// <summary>
/// Creates the default instance, with Png, Jpeg, Gif and Bmp preregisterd (if they have been referenced)
/// For the specified mime type find the <see cref="IImageFormat"/>.
/// </summary>
/// <returns>The default configuration of <see cref="Configuration"/> </returns>
internal static Configuration CreateDefaultInstance()
/// <param name="mimeType">The mime-type to discover</param>
/// <returns>The <see cref="IImageFormat"/> if found otherwise null</returns>
public IImageFormat FindFormatByMimeType(string mimeType)
{
Configuration config = new Configuration();
// lets try auto loading the known image formats
config.AddImageFormat(new Formats.PngFormat());
config.AddImageFormat(new Formats.JpegFormat());
config.AddImageFormat(new Formats.GifFormat());
config.AddImageFormat(new Formats.BmpFormat());
return config;
return this.imageFormats.FirstOrDefault(x => x.MimeTypes.Contains(mimeType, StringComparer.OrdinalIgnoreCase));
}
/// <summary>
/// Tries the add image format.
/// Sets a specific image encoder as the encoder for a specific image format.
/// </summary>
/// <param name="typeName">Name of the type.</param>
/// <returns>True if type discoverd and is a valid <see cref="IImageFormat"/></returns>
internal bool TryAddImageFormat(string typeName)
/// <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)
{
Type type = Type.GetType(typeName, false);
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;
}
}
Guard.NotNull(imageFormat, nameof(imageFormat));
Guard.NotNull(encoder, nameof(encoder));
this.AddImageFormat(imageFormat);
this.mimeTypeEncoders.AddOrUpdate(imageFormat, encoder, (s, e) => encoder);
}
return false;
/// <summary>
/// Sets a specific image decoder as the decoder for a specific image format.
/// </summary>
/// <param name="imageFormat">The image format to register the encoder for.</param>
/// <param name="decoder">The decoder to use,</param>
public void SetDecoder(IImageFormat imageFormat, IImageDecoder decoder)
{
Guard.NotNull(imageFormat, nameof(imageFormat));
Guard.NotNull(decoder, nameof(decoder));
this.AddImageFormat(imageFormat);
this.mimeTypeDecoders.AddOrUpdate(imageFormat, decoder, (s, e) => decoder);
}
/// <summary>
/// Adds image format. The class is locked to make it thread safe.
/// Removes all the registered image format detectors.
/// </summary>
/// <param name="format">The image format.</param>
private void AddImageFormatLocked(IImageFormat format)
public void ClearImageFormatDetectors()
{
lock (this.syncRoot)
{
if (this.GuardDuplicate(format))
{
this.imageFormatsList.Add(format);
this.imageFormatDetectors.Clear();
}
this.SetMaxHeaderSize();
}
}
/// <summary>
/// Adds a new detector for detecting mime types.
/// </summary>
/// <param name="detector">The detector to add</param>
public void AddImageFormatDetector(IImageFormatDetector detector)
{
Guard.NotNull(detector, nameof(detector));
this.imageFormatDetectors.Add(detector);
this.SetMaxHeaderSize();
}
/// <summary>
/// Checks to ensure duplicate image formats are not added.
/// Creates the default instance with the following <see cref="IConfigurationModule"/>s preregistered:
/// <para><see cref="PngConfigurationModule"/></para>
/// <para><see cref="JpegConfigurationModule"/></para>
/// <para><see cref="GifConfigurationModule"/></para>
/// <para><see cref="BmpConfigurationModule"/></para>
/// </summary>
/// <param name="format">The image format.</param>
/// <exception cref="ArgumentException">Thrown if a duplicate is added.</exception>
/// <returns>
/// The <see cref="bool"/>.
/// </returns>
private bool GuardDuplicate(IImageFormat format)
/// <returns>The default configuration of <see cref="Configuration"/></returns>
internal static Configuration CreateDefaultInstance()
{
if (!format.SupportedExtensions.Contains(format.Extension, StringComparer.OrdinalIgnoreCase))
{
throw new ArgumentException("The supported extensions should contain the default extension.", nameof(format));
}
return new Configuration(
new PngConfigurationModule(),
new JpegConfigurationModule(),
new GifConfigurationModule(),
new BmpConfigurationModule());
}
// ReSharper disable once ConvertClosureToMethodGroup
// Prevents method group allocation
if (format.SupportedExtensions.Any(e => string.IsNullOrWhiteSpace(e)))
/// <summary>
/// For the specified mime type find the decoder.
/// </summary>
/// <param name="format">The format to discover</param>
/// <returns>The <see cref="IImageDecoder"/> if found otherwise null</returns>
internal IImageDecoder FindDecoder(IImageFormat format)
{
Guard.NotNull(format, nameof(format));
if (this.mimeTypeDecoders.TryGetValue(format, out IImageDecoder decoder))
{
throw new ArgumentException("The supported extensions should not contain empty values.", nameof(format));
return decoder;
}
// If there is already a format with the same extension or a format that supports that
// extension return false.
foreach (IImageFormat imageFormat in this.imageFormatsList)
{
if (imageFormat.Extension.Equals(format.Extension, StringComparison.OrdinalIgnoreCase))
{
return false;
}
return null;
}
if (imageFormat.SupportedExtensions.Intersect(format.SupportedExtensions, StringComparer.OrdinalIgnoreCase).Any())
{
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>
/// Sets max header size.
/// Sets the max header size.
/// </summary>
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
{
using System;
using System.Collections.Generic;
using System.IO;
using ImageSharp.PixelFormats;
@ -25,16 +26,16 @@ namespace ImageSharp.Formats
/// Formats will be supported in a later releases. We advise always
/// to use only 24 Bit Windows bitmaps.
/// </remarks>
public class BmpDecoder : IImageDecoder
public sealed class BmpDecoder : IImageDecoder, IBmpDecoderOptions
{
/// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream, IDecoderOptions options)
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
where TPixel : struct, IPixel<TPixel>
{
Guard.NotNull(stream, "stream");
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.
/// </summary>
/// <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;
}

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

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

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

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

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

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

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
/// (i.e. least-significant byte first).
/// </remarks>
internal class BmpFileHeader
internal sealed class BmpFileHeader
{
/// <summary>
/// Defines of the data structure in the bitmap file.

30
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.
// Licensed under the Apache License, Version 2.0.
// </copyright>
@ -8,34 +8,20 @@ namespace ImageSharp.Formats
using System.Collections.Generic;
/// <summary>
/// Encapsulates the means to encode and decode bitmap images.
/// Registers the image encoders, decoders and mime type detectors for the jpeg format.
/// </summary>
public class BmpFormat : IImageFormat
internal sealed class BmpFormat : IImageFormat
{
/// <inheritdoc/>
public string MimeType => "image/bmp";
public string Name => "BMP";
/// <inheritdoc/>
public string Extension => "bmp";
public string DefaultMimeType => "image/bmp";
/// <inheritdoc/>
public IEnumerable<string> SupportedExtensions => new string[] { "bmp", "dip" };
public IEnumerable<string> MimeTypes => BmpConstants.MimeTypes;
/// <inheritdoc/>
public IImageDecoder Decoder => new BmpDecoder();
/// <inheritdoc/>
public IImageEncoder Encoder => new BmpEncoder();
/// <inheritdoc/>
public int HeaderSize => 2;
/// <inheritdoc/>
public bool IsSupportedFileFormat(byte[] header)
{
return header.Length >= this.HeaderSize &&
header[0] == 0x42 && // B
header[1] == 0x4D; // M
}
public IEnumerable<string> FileExtensions => BmpConstants.FileExtensions;
}
}
}

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.
/// <see href="https://en.wikipedia.org/wiki/BMP_file_format"/>
/// </summary>
internal class BmpInfoHeader
internal sealed class BmpInfoHeader
{
/// <summary>
/// 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.
// 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>
/// Encapsulates the options for the <see cref="BmpEncoder"/>.
/// Configuration options for use during bmp encoding
/// </summary>
public interface IBmpEncoderOptions : IEncoderOptions
/// <remarks>The encoder can currently only write 24-bit rgb images to streams.</remarks>
internal interface IBmpEncoderOptions
{
/// <summary>
/// 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());
}
}
}

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

@ -5,6 +5,7 @@
namespace ImageSharp.Formats
{
using System.Collections.Generic;
using System.Text;
/// <summary>
@ -90,6 +91,16 @@ namespace ImageSharp.Formats
/// <summary>
/// Gets the default encoding to use when reading comments.
/// </summary>
public static Encoding DefaultEncoding { get; } = Encoding.GetEncoding("ASCII");
public static readonly Encoding DefaultEncoding = Encoding.GetEncoding("ASCII");
/// <summary>
/// The list of mimetypes that equate to a 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
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using ImageSharp.PixelFormats;
/// <summary>
/// Decoder for generating an image out of a gif encoded stream.
/// </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);
return this.Decode<TPixel>(configuration, stream, gifOptions);
}
/// <summary>
/// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded.
/// </summary>
public bool IgnoreMetadata { get; set; } = false;
/// <summary>
/// Decodes the image from the specified stream to the <see cref="ImageBase{TPixel}"/>.
/// Gets or sets the encoding that should be used when reading comments.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="configuration">The configuration.</param>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <param name="options">The options for the decoder.</param>
/// <returns>The image thats been decoded.</returns>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream, IGifDecoderOptions options)
public Encoding TextEncoding { get; set; } = GifConstants.DefaultEncoding;
/// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
where TPixel : struct, IPixel<TPixel>
{
return new GifDecoderCore<TPixel>(options, configuration).Decode(stream);
var decoder = new GifDecoderCore<TPixel>(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.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
internal class GifDecoderCore<TPixel>
internal sealed class GifDecoderCore<TPixel>
where TPixel : struct, IPixel<TPixel>
{
/// <summary>
@ -26,11 +26,6 @@ namespace ImageSharp.Formats
/// </summary>
private readonly byte[] buffer = new byte[16];
/// <summary>
/// The decoder options.
/// </summary>
private readonly IGifDecoderOptions options;
/// <summary>
/// The global configuration.
/// </summary>
@ -84,14 +79,25 @@ namespace ImageSharp.Formats
/// <summary>
/// Initializes a new instance of the <see cref="GifDecoderCore{TPixel}"/> class.
/// </summary>
/// <param name="options">The decoder options.</param>
/// <param name="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;
}
/// <summary>
/// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded.
/// </summary>
public bool IgnoreMetadata { get; internal set; }
/// <summary>
/// Gets the text encoding
/// </summary>
public Encoding TextEncoding { get; private set; }
/// <summary>
/// Decodes the stream to the image.
/// </summary>
@ -269,7 +275,7 @@ namespace ImageSharp.Formats
throw new ImageFormatException($"Gif comment length '{length}' exceeds max '{GifConstants.MaxCommentLength}'");
}
if (this.options.IgnoreMetadata)
if (this.IgnoreMetadata)
{
this.currentStream.Seek(length, SeekOrigin.Current);
continue;
@ -280,7 +286,7 @@ namespace ImageSharp.Formats
try
{
this.currentStream.Read(commentsBuffer, 0, length);
string comments = this.options.TextEncoding.GetString(commentsBuffer, 0, length);
string comments = this.TextEncoding.GetString(commentsBuffer, 0, length);
this.metaData.Properties.Add(new ImageProperty(GifConstants.Comments, comments));
}
finally
@ -364,8 +370,6 @@ namespace ImageSharp.Formats
if (this.previousFrame == null)
{
this.metaData.Quality = colorTableLength / 3;
// This initializes the image to become fully transparent because the alpha channel is zero.
this.image = new Image<TPixel>(this.configuration, imageWidth, imageHeight, this.metaData);

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

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

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

@ -6,35 +6,47 @@
namespace ImageSharp.Formats
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using ImageSharp.PixelFormats;
using ImageSharp.Quantizers;
/// <summary>
/// Image encoder for writing image data to a stream in gif format.
/// </summary>
public class GifEncoder : IImageEncoder
public sealed class GifEncoder : IImageEncoder, IGifEncoderOptions
{
/// <inheritdoc/>
public void Encode<TPixel>(Image<TPixel> image, Stream stream, IEncoderOptions options)
where TPixel : struct, IPixel<TPixel>
{
IGifEncoderOptions gifOptions = GifEncoderOptions.Create(options);
/// <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;
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>
/// 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>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The <see cref="Image{TPixel}"/> to encode from.</param>
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
/// <param name="options">The options for the encoder.</param>
public void Encode<TPixel>(Image<TPixel> image, Stream stream, IGifEncoderOptions options)
public int PaletteSize { get; set; } = 0;
/// <summary>
/// Gets or sets the transparency threshold.
/// </summary>
public byte Threshold { get; set; } = 128;
/// <summary>
/// Gets or sets the quantizer for reducing the color count.
/// </summary>
public IQuantizer Quantizer { get; set; }
/// <inheritdoc/>
public void Encode<TPixel>(Image<TPixel> image, Stream stream)
where TPixel : struct, IPixel<TPixel>
{
GifEncoderCore encoder = new GifEncoderCore(options);
GifEncoderCore encoder = new GifEncoderCore(this);
encoder.Encode(image, stream);
}
}

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

@ -9,7 +9,7 @@ namespace ImageSharp.Formats
using System.Buffers;
using System.IO;
using System.Linq;
using System.Text;
using ImageSharp.PixelFormats;
using IO;
@ -25,11 +25,6 @@ namespace ImageSharp.Formats
/// </summary>
private readonly byte[] buffer = new byte[16];
/// <summary>
/// The options for the encoder.
/// </summary>
private readonly IGifEncoderOptions options;
/// <summary>
/// The number of bits requires to store the image palette.
/// </summary>
@ -40,19 +35,44 @@ namespace ImageSharp.Formats
/// </summary>
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>
/// Initializes a new instance of the <see cref="GifEncoderCore"/> class.
/// </summary>
/// <param name="options">The options for the encoder.</param>
public GifEncoderCore(IGifEncoderOptions options)
{
this.options = options ?? new GifEncoderOptions();
}
this.textEncoding = options.TextEncoding ?? GifConstants.DefaultEncoding;
/// <summary>
/// Gets or sets the quantizer for reducing the color count.
/// </summary>
public IQuantizer Quantizer { get; set; }
this.quantizer = options.Quantizer;
this.threshold = options.Threshold;
this.paletteSize = options.PaletteSize;
this.ignoreMetadata = options.IgnoreMetadata;
}
/// <summary>
/// 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(stream, nameof(stream));
this.Quantizer = this.options.Quantizer ?? new OctreeQuantizer<TPixel>();
this.quantizer = this.quantizer ?? new OctreeQuantizer<TPixel>();
// Do not use IDisposable pattern here as we want to preserve the stream.
var writer = new EndianBinaryWriter(Endianness.LittleEndian, stream);
// Ensure that quality can be set but has a fallback.
int quality = this.options.Quality > 0 ? this.options.Quality : image.MetaData.Quality;
quality = quality > 0 ? quality.Clamp(1, 256) : 256;
// Ensure that pallete size can be set but has a fallback.
int paletteSize = this.paletteSize;
paletteSize = paletteSize > 0 ? paletteSize.Clamp(1, 256) : 256;
// 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();
// 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;
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);
@ -111,7 +131,7 @@ namespace ImageSharp.Formats
for (int i = 0; i < image.Frames.Count; 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.WriteImageDescriptor(frame, writer);
@ -240,7 +260,7 @@ namespace ImageSharp.Formats
private void WriteComments<TPixel>(Image<TPixel> image, EndianBinaryWriter writer)
where TPixel : struct, IPixel<TPixel>
{
if (this.options.IgnoreMetadata)
if (this.ignoreMetadata)
{
return;
}
@ -251,7 +271,7 @@ namespace ImageSharp.Formats
return;
}
byte[] comments = this.options.TextEncoding.GetBytes(property.Value);
byte[] comments = this.textEncoding.GetBytes(property.Value);
int count = Math.Min(comments.Length, 255);

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

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

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

@ -8,38 +8,20 @@ namespace ImageSharp.Formats
using System.Collections.Generic;
/// <summary>
/// Encapsulates the means to encode and decode gif images.
/// Registers the image encoders, decoders and mime type detectors for the jpeg format.
/// </summary>
public class GifFormat : IImageFormat
internal sealed class GifFormat : IImageFormat
{
/// <inheritdoc/>
public string Extension => "gif";
public string Name => "GIF";
/// <inheritdoc/>
public string MimeType => "image/gif";
public string DefaultMimeType => "image/gif";
/// <inheritdoc/>
public IEnumerable<string> SupportedExtensions => new string[] { "gif" };
public IEnumerable<string> MimeTypes => GifConstants.MimeTypes;
/// <inheritdoc/>
public IImageDecoder Decoder => new GifDecoder();
/// <inheritdoc/>
public IImageEncoder Encoder => new GifEncoder();
/// <inheritdoc/>
public int HeaderSize => 6;
/// <inheritdoc/>
public bool IsSupportedFileFormat(byte[] header)
{
return header.Length >= this.HeaderSize &&
header[0] == 0x47 && // G
header[1] == 0x49 && // I
header[2] == 0x46 && // F
header[3] == 0x38 && // 8
(header[4] == 0x39 || header[4] == 0x37) && // 9 or 7
header[5] == 0x61; // a
}
public IEnumerable<string> FileExtensions => GifConstants.FileExtensions;
}
}
}

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.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Formats
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using ImageSharp.PixelFormats;
/// <summary>
/// Encapsulates the options for the <see cref="GifDecoder"/>.
/// Decoder for generating an image out of a gif encoded stream.
/// </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>
/// Gets the encoding that should be used when reading comments.
/// </summary>

25
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.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Formats
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using Quantizers;
using ImageSharp.PixelFormats;
using ImageSharp.Quantizers;
/// <summary>
/// Encapsulates the options for the <see cref="GifEncoder"/>.
/// The configuration options used for encoding gifs
/// </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>
/// Gets the encoding that should be used when writing comments.
/// </summary>
Encoding TextEncoding { get; }
/// <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>
/// <remarks>For gifs the value ranges from 1 to 256.</remarks>
int Quality { get; }
int PaletteSize { get; }
/// <summary>
/// Gets the transparency threshold.
@ -33,6 +40,6 @@ namespace ImageSharp.Formats
/// <summary>
/// Gets the quantizer for reducing the color count.
/// </summary>
IQuantizer Quantizer { get; }
IQuantizer Quantizer { get; }
}
}

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

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

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

4
src/ImageSharp/Formats/IImageEncoder.cs

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

45
src/ImageSharp/Formats/IImageFormat.cs

@ -8,53 +8,28 @@ namespace ImageSharp.Formats
using System.Collections.Generic;
/// <summary>
/// Encapsulates a supported image format, providing means to encode and decode an image.
/// Individual formats implements in this interface must be registered in the <see cref="Configuration"/>
/// Describes an image format.
/// </summary>
public interface IImageFormat
{
/// <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>
string MimeType { get; }
string Name { get; }
/// <summary>
/// Gets the default file extension for this format.
/// Gets the default mimetype that the image foramt uses
/// </summary>
string Extension { get; }
string DefaultMimeType { get; }
/// <summary>
/// Gets the supported file extensions for this format.
/// Gets all the mimetypes that have been used by this image foramt.
/// </summary>
/// <returns>
/// The supported file extension.
/// </returns>
IEnumerable<string> SupportedExtensions { get; }
IEnumerable<string> MimeTypes { get; }
/// <summary>
/// Gets the image encoder for encoding an image from a stream.
/// Gets the file extensions this image format commonly uses.
/// </summary>
IImageEncoder Encoder { get; }
/// <summary>
/// Gets the image decoder for decoding an image from a stream.
/// </summary>
IImageDecoder Decoder { get; }
/// <summary>
/// Gets the size of the header for this image type.
/// </summary>
/// <value>The size of the header.</value>
int HeaderSize { get; }
/// <summary>
/// Returns a value indicating whether the <see cref="IImageDecoder"/> supports the specified
/// file header.
/// </summary>
/// <param name="header">The <see cref="T:byte[]"/> containing the file header.</param>
/// <returns>
/// True if the decoder supports the file header; otherwise, false.
/// </returns>
bool IsSupportedFileFormat(byte[] header);
IEnumerable<string> FileExtensions { get; }
}
}
}

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.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp
namespace ImageSharp.Formats
{
using System;
using System.Collections.Generic;
using System.IO;
using ImageSharp.PixelFormats;
/// <summary>
/// Encapsulates the shared decoder options.
/// Image decoder for generating an image out of a jpg stream.
/// </summary>
public interface IDecoderOptions
internal interface IJpegDecoderOptions
{
/// <summary>
/// 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.
// 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>
/// Encapsulates the options for the <see cref="JpegEncoder"/>.
/// Encoder for writing the data image to a stream in jpeg format.
/// </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>
/// Gets the quality, that will be used to encode the image. Quality
/// 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>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
/// <param name="options">The options for the encoder.</param>
/// <param name="encoder">The options for the encoder.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <returns>
/// The <see cref="Image{TPixel}"/>.
/// </returns>
public static Image<TPixel> SaveAsJpeg<TPixel>(this Image<TPixel> source, Stream stream, IJpegEncoderOptions options)
public static Image<TPixel> SaveAsJpeg<TPixel>(this Image<TPixel> source, Stream stream, JpegEncoder encoder)
where TPixel : struct, IPixel<TPixel>
{
JpegEncoder encoder = new JpegEncoder();
encoder.Encode(source, stream, options);
encoder = encoder ?? new JpegEncoder();
encoder.Encode(source, stream);
return source;
}

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

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

@ -5,7 +5,6 @@
namespace ImageSharp.Formats
{
using System;
using System.IO;
using ImageSharp.PixelFormats;
@ -13,22 +12,24 @@ namespace ImageSharp.Formats
/// <summary>
/// Image decoder for generating an image out of a jpg stream.
/// </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/>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream, IDecoderOptions options)
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
where TPixel : struct, IPixel<TPixel>
{
Guard.NotNull(stream, "stream");
// using (var decoder = new JpegDecoderCore(options, configuration))
// {
// return decoder.Decode<TPixel>(stream);
// }
using (var decoder = new Jpeg.Port.JpegDecoderCore(options, configuration))
// using (var decoder = new JpegDecoderCore(configuration, this))
using (var decoder = new Jpeg.Port.JpegDecoderCore(configuration, this))
{
return decoder.Decode<TPixel>(stream);
}
}
}
}
}

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

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

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

@ -5,6 +5,8 @@
namespace ImageSharp.Formats
{
using System;
using System.Collections.Generic;
using System.IO;
using ImageSharp.PixelFormats;
@ -12,16 +14,25 @@ namespace ImageSharp.Formats
/// <summary>
/// Encoder for writing the data image to a stream in jpeg format.
/// </summary>
public class JpegEncoder : IImageEncoder
public sealed class JpegEncoder : IImageEncoder, IJpegEncoderOptions
{
/// <inheritdoc/>
public void Encode<TPixel>(Image<TPixel> image, Stream stream, IEncoderOptions options)
where TPixel : struct, IPixel<TPixel>
{
IJpegEncoderOptions gifOptions = JpegEncoderOptions.Create(options);
/// <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; }
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>
/// 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>
/// <param name="image">The <see cref="Image{TPixel}"/> to encode from.</param>
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
/// <param name="options">The options for the encoder.</param>
public void Encode<TPixel>(Image<TPixel> image, Stream stream, IJpegEncoderOptions options)
where TPixel : struct, IPixel<TPixel>
public void Encode<TPixel>(Image<TPixel> image, Stream stream)
where TPixel : struct, IPixel<TPixel>
{
JpegEncoderCore encode = new JpegEncoderCore(options);
encode.Encode(image, stream);
var encoder = new JpegEncoderCore(this);
encoder.Encode(image, stream);
}
}
}

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

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

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

@ -8,82 +8,20 @@ namespace ImageSharp.Formats
using System.Collections.Generic;
/// <summary>
/// Encapsulates the means to encode and decode jpeg images.
/// Registers the image encoders, decoders and mime type detectors for the jpeg format.
/// </summary>
public class JpegFormat : IImageFormat
internal sealed class JpegFormat : IImageFormat
{
/// <inheritdoc/>
public string MimeType => "image/jpeg";
public string Name => "JPEG";
/// <inheritdoc/>
public string Extension => "jpg";
public string DefaultMimeType => "image/jpeg";
/// <inheritdoc/>
public IEnumerable<string> SupportedExtensions => new string[] { "jpg", "jpeg", "jfif" };
public IEnumerable<string> MimeTypes => JpegConstants.MimeTypes;
/// <inheritdoc/>
public IImageDecoder Decoder => new JpegDecoder();
/// <inheritdoc/>
public IImageEncoder Encoder => new JpegEncoder();
/// <inheritdoc/>
public int HeaderSize => 11;
/// <inheritdoc/>
public bool IsSupportedFileFormat(byte[] header)
{
return header.Length >= this.HeaderSize &&
(IsJfif(header) || IsExif(header) || IsJpeg(header));
}
/// <summary>
/// Returns a value indicating whether the given bytes identify Jfif data.
/// </summary>
/// <param name="header">The bytes representing the file header.</param>
/// <returns>The <see cref="bool"/></returns>
private static bool IsJfif(byte[] header)
{
bool isJfif =
header[6] == 0x4A && // J
header[7] == 0x46 && // F
header[8] == 0x49 && // I
header[9] == 0x46 && // F
header[10] == 0x00;
return isJfif;
}
/// <summary>
/// Returns a value indicating whether the given bytes identify EXIF data.
/// </summary>
/// <param name="header">The bytes representing the file header.</param>
/// <returns>The <see cref="bool"/></returns>
private static bool IsExif(byte[] header)
{
bool isExif =
header[6] == 0x45 && // E
header[7] == 0x78 && // X
header[8] == 0x69 && // I
header[9] == 0x66 && // F
header[10] == 0x00;
return isExif;
}
/// <summary>
/// Returns a value indicating whether the given bytes identify Jpeg data.
/// This is a last chance resort for jpegs that contain ICC information.
/// </summary>
/// <param name="header">The bytes representing the file header.</param>
/// <returns>The <see cref="bool"/></returns>
private static bool IsJpeg(byte[] header)
{
bool isJpg =
header[0] == 0xFF && // 255
header[1] == 0xD8; // 216
return isJpg;
}
public IEnumerable<string> FileExtensions => JpegConstants.FileExtensions;
}
}
}

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>
internal sealed class JpegDecoderCore : IDisposable
{
/// <summary>
/// The decoder options.
/// </summary>
private readonly IDecoderOptions options;
/// <summary>
/// The global configuration
/// </summary>
@ -85,12 +80,12 @@ namespace ImageSharp.Formats.Jpeg.Port
/// <summary>
/// Initializes a new instance of the <see cref="JpegDecoderCore" /> class.
/// </summary>
/// <param name="options">The decoder options.</param>
/// <param name="configuration">The configuration.</param>
public JpegDecoderCore(IDecoderOptions options, Configuration configuration)
/// <param name="options">The options.</param>
public JpegDecoderCore(Configuration configuration, IJpegDecoderOptions options)
{
this.configuration = configuration ?? Configuration.Default;
this.options = options ?? new DecoderOptions();
this.IgnoreMetadata = options.IgnoreMetadata;
}
/// <summary>
@ -98,6 +93,11 @@ namespace ImageSharp.Formats.Jpeg.Port
/// </summary>
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>
/// Finds the next file marker within the byte stream.
/// </summary>
@ -413,7 +413,7 @@ namespace ImageSharp.Formats.Jpeg.Port
/// <param name="metadata">The image.</param>
private void ProcessApp1Marker(int remaining, ImageMetaData metadata)
{
if (remaining < 6 || this.options.IgnoreMetadata)
if (remaining < 6 || this.IgnoreMetadata)
{
// Skip the application header length
this.InputStream.Skip(remaining);
@ -444,7 +444,7 @@ namespace ImageSharp.Formats.Jpeg.Port
{
// Length is 14 though we only need to check 12.
const int Icclength = 14;
if (remaining < Icclength || this.options.IgnoreMetadata)
if (remaining < Icclength || this.IgnoreMetadata)
{
this.InputStream.Skip(remaining);
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.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Formats
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using ImageSharp.PixelFormats;
/// <summary>
/// Encapsulates the options for the <see cref="PngDecoder"/>.
/// The optioas for decoding png images
/// </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>
/// Gets the encoding that should be used when reading text chunks.
/// </summary>

28
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.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Formats
{
using Quantizers;
using System.Collections.Generic;
using System.IO;
using ImageSharp.PixelFormats;
using ImageSharp.Quantizers;
/// <summary>
/// Encapsulates the options for the <see cref="PngEncoder"/>.
/// The options availible for manipulating the encoder pipeline
/// </summary>
public interface IPngEncoderOptions : IEncoderOptions
internal interface IPngEncoderOptions
{
/// <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>
int Quality { get; }
int PaletteSize { get; }
/// <summary>
/// Gets the png color type
@ -24,19 +33,20 @@ namespace ImageSharp.Formats
/// <summary>
/// Gets the compression level 1-9.
/// <remarks>Defaults to 6.</remarks>
/// </summary>
int CompressionLevel { get; }
/// <summary>
/// Gets the gamma value, that will be written
/// the the stream, when the <see cref="WriteGamma"/> property
/// is set to true.
/// is set to true. The default value is 2.2F.
/// </summary>
/// <value>The gamma value of the image.</value>
float Gamma { get; }
/// <summary>
/// Gets quantizer for reducing the color count.
/// Gets quantizer for reducing the color count.
/// </summary>
IQuantizer Quantizer { get; }
@ -47,7 +57,7 @@ namespace ImageSharp.Formats
/// <summary>
/// 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>
bool WriteGamma { get; }
}

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

@ -38,16 +38,16 @@ namespace ImageSharp
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
/// <param name="options">The options for the encoder.</param>
/// <param name="encoder">The options for the encoder.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <returns>
/// The <see cref="Image{TPixel}"/>.
/// </returns>
public static Image<TPixel> SaveAsPng<TPixel>(this Image<TPixel> source, Stream stream, IPngEncoderOptions options)
public static Image<TPixel> SaveAsPng<TPixel>(this Image<TPixel> source, Stream stream, PngEncoder encoder)
where TPixel : struct, IPixel<TPixel>
{
var encoder = new PngEncoder();
encoder.Encode(source, stream, options);
encoder = encoder ?? new PngEncoder();
encoder.Encode(source, stream);
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
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using ImageSharp.PixelFormats;
/// <summary>
@ -30,17 +31,17 @@ namespace ImageSharp.Formats
/// </list>
/// </para>
/// </remarks>
public class PngDecoder : IImageDecoder
public sealed class PngDecoder : IImageDecoder, IPngDecoderOptions
{
/// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream, IDecoderOptions options)
where TPixel : struct, IPixel<TPixel>
{
IPngDecoderOptions pngOptions = PngDecoderOptions.Create(options);
/// <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; }
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>
/// 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>
/// <param name="configuration">The configuration for the image.</param>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <param name="options">The options for the decoder.</param>
/// <returns>The decoded image.</returns>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream, IPngDecoderOptions options)
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
where TPixel : struct, IPixel<TPixel>
{
return new PngDecoderCore(options, configuration).Decode<TPixel>(stream);
var decoder = new PngDecoderCore(configuration, this);
return decoder.Decode<TPixel>(stream);
}
}
}

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

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

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

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

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

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

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

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

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

@ -8,40 +8,20 @@ namespace ImageSharp.Formats
using System.Collections.Generic;
/// <summary>
/// Encapsulates the means to encode and decode png images.
/// Registers the image encoders, decoders and mime type detectors for the jpeg format.
/// </summary>
public class PngFormat : IImageFormat
internal sealed class PngFormat : IImageFormat
{
/// <inheritdoc/>
public string MimeType => "image/png";
public string Name => "PNG";
/// <inheritdoc/>
public string Extension => "png";
public string DefaultMimeType => "image/png";
/// <inheritdoc/>
public IEnumerable<string> SupportedExtensions => new string[] { "png" };
public IEnumerable<string> MimeTypes => PngConstants.MimeTypes;
/// <inheritdoc/>
public IImageDecoder Decoder => new PngDecoder();
/// <inheritdoc/>
public IImageEncoder Encoder => new PngEncoder();
/// <inheritdoc/>
public int HeaderSize => 8;
/// <inheritdoc/>
public bool IsSupportedFileFormat(byte[] header)
{
return header.Length >= this.HeaderSize &&
header[0] == 0x89 &&
header[1] == 0x50 && // P
header[2] == 0x4E && // N
header[3] == 0x47 && // G
header[4] == 0x0D && // CR
header[5] == 0x0A && // LF
header[6] == 0x1A && // EOF
header[7] == 0x0A; // LF
}
public IEnumerable<string> FileExtensions => PngConstants.FileExtensions;
}
}
}

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

@ -8,7 +8,7 @@ namespace ImageSharp.Formats
/// <summary>
/// Represents the png header chunk.
/// </summary>
public sealed class PngHeader
internal sealed class PngHeader
{
/// <summary>
/// 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>
/// Provides enumeration of available PNG interlace modes.
/// </summary>
public enum PngInterlaceMode : byte
internal enum PngInterlaceMode : byte
{
/// <summary>
/// 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
/// so it can be used again with new data.
/// </summary>
public interface IChecksum
internal interface IChecksum
{
/// <summary>
/// Gets the data checksum computed so far.

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

@ -9,7 +9,7 @@
/// <summary>
/// Provides methods and properties for deframing streams from PNGs.
/// </summary>
internal class ZlibInflateStream : Stream
internal sealed class ZlibInflateStream : Stream
{
/// <summary>
/// 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>
internal interface IImage : IImageBase
{
/// <summary>
/// Gets the currently loaded image format.
/// </summary>
IImageFormat CurrentImageFormat { get; }
/// <summary>
/// Gets the meta data of the image.
/// </summary>

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

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

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

@ -15,54 +15,78 @@ namespace ImageSharp
/// </content>
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>
/// Create a new instance of the <see cref="Image{Rgba32}"/> class from the given byte array.
/// </summary>
/// <param name="data">The byte array containing image data.</param>
/// <returns>A new <see cref="Image{Rgba32}"/>.</returns>
public static Image<Rgba32> Load(byte[] data) => Load<Rgba32>(null, data, null);
public static Image<Rgba32> Load(byte[] data) => Load<Rgba32>(null, data);
/// <summary>
/// Create a new instance of the <see cref="Image{Rgba32}"/> class from the given byte array.
/// </summary>
/// <param name="data">The byte array containing image data.</param>
/// <param name="options">The options for the decoder.</param>
/// <param name="format">The mime type of the decoded image.</param>
/// <returns>A new <see cref="Image{Rgba32}"/>.</returns>
public static Image<Rgba32> Load(byte[] data, IDecoderOptions options) => Load<Rgba32>(null, data, options);
public static Image<Rgba32> Load(byte[] data, out IImageFormat format) => Load<Rgba32>(null, data, out format);
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given byte array.
/// Create a new instance of the <see cref="Image{Rgba32}"/> class from the given byte array.
/// </summary>
/// <param name="config">The config for the decoder.</param>
/// <param name="data">The byte array containing image data.</param>
/// <returns>A new <see cref="Image{Rgba32}"/>.</returns>
public static Image<Rgba32> Load(Configuration config, byte[] data) => Load<Rgba32>(config, data, null);
public static Image<Rgba32> Load(Configuration config, byte[] data) => Load<Rgba32>(config, data);
/// <summary>
/// Create a new instance of the <see cref="Image{Rgba32}"/> class from the given byte array.
/// </summary>
/// <param name="config">The config for the decoder.</param>
/// <param name="data">The byte array containing image data.</param>
/// <param name="decoder">The decoder.</param>
/// <param name="format">The mime type of the decoded image.</param>
/// <returns>A new <see cref="Image{Rgba32}"/>.</returns>
public static Image<Rgba32> Load(byte[] data, IImageDecoder decoder) => Load<Rgba32>(data, decoder, null);
public static Image<Rgba32> Load(Configuration config, byte[] data, out IImageFormat format) => Load<Rgba32>(config, data, out format);
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given byte array.
/// Create a new instance of the <see cref="Image{Rgba32}"/> class from the given byte array.
/// </summary>
/// <param name="config">The configuration options.</param>
/// <param name="data">The byte array containing image data.</param>
/// <param name="options">The options for the decoder.</param>
/// <param name="decoder">The decoder.</param>
/// <returns>A new <see cref="Image{Rgba32}"/>.</returns>
public static Image<Rgba32> Load(Configuration config, byte[] data, IDecoderOptions options) => Load<Rgba32>(config, data, options);
public static Image<Rgba32> Load(byte[] data, IImageDecoder decoder) => Load<Rgba32>(data, decoder);
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given byte array.
/// Create a new instance of the <see cref="Image{Rgba32}"/> class from the given byte array.
/// </summary>
/// <param name="config">The config for the decoder.</param>
/// <param name="data">The byte array containing image data.</param>
/// <param name="decoder">The decoder.</param>
/// <param name="options">The options for the decoder.</param>
/// <returns>A new <see cref="Image{Rgba32}"/>.</returns>
public static Image<Rgba32> Load(byte[] data, IImageDecoder decoder, IDecoderOptions options) => Load<Rgba32>(data, decoder, options);
public static Image<Rgba32> Load(Configuration config, byte[] data, IImageDecoder decoder) => Load<Rgba32>(config, data, decoder);
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given byte array.
@ -73,79 +97,85 @@ namespace ImageSharp
public static Image<TPixel> Load<TPixel>(byte[] data)
where TPixel : struct, IPixel<TPixel>
{
return Load<TPixel>(null, data, null);
return Load<TPixel>(null, data);
}
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given byte array.
/// </summary>
/// <param name="data">The byte array containing image data.</param>
/// <param name="options">The options for the decoder.</param>
/// <param name="format">The mime type of the decoded image.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(byte[] data, IDecoderOptions options)
public static Image<TPixel> Load<TPixel>(byte[] data, out IImageFormat format)
where TPixel : struct, IPixel<TPixel>
{
return Load<TPixel>(null, data, options);
return Load<TPixel>(null, data, out format);
}
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given byte array.
/// </summary>
/// <param name="config">The config for the decoder.</param>
/// <param name="config">The configuration options.</param>
/// <param name="data">The byte array containing image data.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(Configuration config, byte[] data)
where TPixel : struct, IPixel<TPixel>
{
return Load<TPixel>(config, data, null);
using (var memoryStream = new MemoryStream(data))
{
return Load<TPixel>(config, memoryStream);
}
}
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given byte array.
/// </summary>
/// <param name="config">The configuration options.</param>
/// <param name="data">The byte array containing image data.</param>
/// <param name="decoder">The decoder.</param>
/// <param name="format">The mime type of the decoded image.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(byte[] data, IImageDecoder decoder)
public static Image<TPixel> Load<TPixel>(Configuration config, byte[] data, out IImageFormat format)
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>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given byte array.
/// </summary>
/// <param name="config">The configuration options.</param>
/// <param name="data">The byte array containing image data.</param>
/// <param name="options">The options for the decoder.</param>
/// <param name="decoder">The decoder.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(Configuration config, byte[] data, IDecoderOptions options)
public static Image<TPixel> Load<TPixel>(byte[] data, IImageDecoder decoder)
where TPixel : struct, IPixel<TPixel>
{
using (var ms = new MemoryStream(data))
using (var memoryStream = new MemoryStream(data))
{
return Load<TPixel>(config, ms, options);
return Load<TPixel>(memoryStream, decoder);
}
}
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given byte array.
/// </summary>
/// <param name="config">The Configuration.</param>
/// <param name="data">The byte array containing image data.</param>
/// <param name="decoder">The decoder.</param>
/// <param name="options">The options for the decoder.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(byte[] data, IImageDecoder decoder, IDecoderOptions options)
public static Image<TPixel> Load<TPixel>(Configuration config, byte[] data, IImageDecoder decoder)
where TPixel : struct, IPixel<TPixel>
{
using (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>
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>
/// Create a new instance of the <see cref="Image{Rgba32}"/> class from the given file.
/// </summary>
@ -30,12 +55,12 @@ namespace ImageSharp
/// Create a new instance of the <see cref="Image{Rgba32}"/> class from the given file.
/// </summary>
/// <param name="path">The file path to the image.</param>
/// <param name="options">The options for the decoder.</param>
/// <param name="format">The mime type of the decoded image.</param>
/// <exception cref="NotSupportedException">
/// Thrown if the stream is not readable nor seekable.
/// </exception>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<Rgba32> Load(string path, IDecoderOptions options) => Load<Rgba32>(path, options);
/// <returns>A new <see cref="Image{Rgba32}"/>.</returns>
public static Image<Rgba32> Load(string path, out IImageFormat format) => Load<Rgba32>(path, out format);
/// <summary>
/// Create a new instance of the <see cref="Image{Rgba32}"/> class from the given file.
@ -51,37 +76,37 @@ namespace ImageSharp
/// <summary>
/// Create a new instance of the <see cref="Image{Rgba32}"/> class from the given file.
/// </summary>
/// <param name="config">The config for the decoder.</param>
/// <param name="path">The file path to the image.</param>
/// <param name="decoder">The decoder.</param>
/// <param name="format">The mime type of the decoded image.</param>
/// <exception cref="NotSupportedException">
/// Thrown if the stream is not readable nor seekable.
/// </exception>
/// <returns>A new <see cref="Image{Rgba32}"/>.</returns>
public static Image<Rgba32> Load(string path, IImageDecoder decoder) => Load<Rgba32>(path, decoder);
public static Image<Rgba32> Load(Configuration config, string path, out IImageFormat format) => Load<Rgba32>(config, path, out format);
/// <summary>
/// Create a new instance of the <see cref="Image{Rgba32}"/> class from the given file.
/// </summary>
/// <param name="config">The configuration options.</param>
/// <param name="config">The Configuration.</param>
/// <param name="path">The file path to the image.</param>
/// <param name="options">The options for the decoder.</param>
/// <param name="decoder">The decoder.</param>
/// <exception cref="NotSupportedException">
/// Thrown if the stream is not readable nor seekable.
/// </exception>
/// <returns>A new <see cref="Image{Rgba32}"/>.</returns>
public static Image<Rgba32> Load(Configuration config, string path, IDecoderOptions options) => Load<Rgba32>(config, path, options);
public static Image<Rgba32> Load(Configuration config, string path, IImageDecoder decoder) => Load<Rgba32>(config, path, decoder);
/// <summary>
/// Create a new instance of the <see cref="Image{Rgba32}"/> class from the given file.
/// </summary>
/// <param name="path">The file path to the image.</param>
/// <param name="decoder">The decoder.</param>
/// <param name="options">The options for the decoder.</param>
/// <exception cref="NotSupportedException">
/// Thrown if the stream is not readable nor seekable.
/// </exception>
/// <returns>A new <see cref="Image{Rgba32}"/>.</returns>
public static Image<Rgba32> Load(string path, IImageDecoder decoder, IDecoderOptions options) => Load<Rgba32>(path, decoder, options);
public static Image<Rgba32> Load(string path, IImageDecoder decoder) => Load<Rgba32>(path, decoder);
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given file.
@ -95,29 +120,29 @@ namespace ImageSharp
public static Image<TPixel> Load<TPixel>(string path)
where TPixel : struct, IPixel<TPixel>
{
return Load<TPixel>(null, path, null);
return Load<TPixel>(null, path);
}
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given file.
/// </summary>
/// <param name="path">The file path to the image.</param>
/// <param name="options">The options for the decoder.</param>
/// <param name="format">The mime type of the decoded image.</param>
/// <exception cref="NotSupportedException">
/// Thrown if the stream is not readable nor seekable.
/// </exception>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(string path, IDecoderOptions options)
public static Image<TPixel> Load<TPixel>(string path, out IImageFormat format)
where TPixel : struct, IPixel<TPixel>
{
return Load<TPixel>(null, path, options);
return Load<TPixel>(null, path, out format);
}
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given file.
/// </summary>
/// <param name="config">The config for the decoder.</param>
/// <param name="config">The configuration options.</param>
/// <param name="path">The file path to the image.</param>
/// <exception cref="NotSupportedException">
/// Thrown if the stream is not readable nor seekable.
@ -127,64 +152,68 @@ namespace ImageSharp
public static Image<TPixel> Load<TPixel>(Configuration config, string path)
where TPixel : struct, IPixel<TPixel>
{
return Load<TPixel>(config, path, null);
config = config ?? Configuration.Default;
using (Stream stream = config.FileSystem.OpenRead(path))
{
return Load<TPixel>(config, stream);
}
}
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given file.
/// </summary>
/// <param name="config">The configuration options.</param>
/// <param name="path">The file path to the image.</param>
/// <param name="decoder">The decoder.</param>
/// <param name="format">The mime type of the decoded image.</param>
/// <exception cref="NotSupportedException">
/// Thrown if the stream is not readable nor seekable.
/// </exception>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(string path, IImageDecoder decoder)
public static Image<TPixel> Load<TPixel>(Configuration config, string path, out IImageFormat format)
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>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given file.
/// </summary>
/// <param name="config">The configuration options.</param>
/// <param name="path">The file path to the image.</param>
/// <param name="options">The options for the decoder.</param>
/// <param name="decoder">The decoder.</param>
/// <exception cref="NotSupportedException">
/// Thrown if the stream is not readable nor seekable.
/// </exception>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(Configuration config, string path, IDecoderOptions options)
public static Image<TPixel> Load<TPixel>(string path, IImageDecoder decoder)
where TPixel : struct, IPixel<TPixel>
{
config = config ?? Configuration.Default;
using (Stream s = config.FileSystem.OpenRead(path))
{
return Load<TPixel>(config, s, options);
}
return Load<TPixel>(null, path, decoder);
}
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given file.
/// </summary>
/// <param name="config">The Configuration.</param>
/// <param name="path">The file path to the image.</param>
/// <param name="decoder">The decoder.</param>
/// <param name="options">The options for the decoder.</param>
/// <exception cref="NotSupportedException">
/// Thrown if the stream is not readable nor seekable.
/// </exception>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(string path, IImageDecoder decoder, IDecoderOptions options)
public static Image<TPixel> Load<TPixel>(Configuration config, string path, IImageDecoder decoder)
where TPixel : struct, IPixel<TPixel>
{
Configuration config = Configuration.Default;
using (Stream s = config.FileSystem.OpenRead(path))
config = config ?? Configuration.Default;
using (Stream stream = config.FileSystem.OpenRead(path))
{
return Load<TPixel>(s, decoder, options);
return Load<TPixel>(config, stream, decoder);
}
}
}

92
src/ImageSharp/Image/Image.FromStream.cs

@ -6,6 +6,7 @@
namespace ImageSharp
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using Formats;
@ -17,26 +18,47 @@ namespace ImageSharp
/// </content>
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>
/// Create a new instance of the <see cref="Image{Rgba32}"/> class from the given stream.
/// </summary>
/// <param name="stream">The stream containing image information.</param>
/// <param name="format">the mime type of the decoded image.</param>
/// <exception cref="NotSupportedException">
/// Thrown if the stream is not readable nor seekable.
/// </exception>
/// <returns>A new <see cref="Image{Rgba32}"/>.</returns>>
public static Image<Rgba32> Load(Stream stream) => Load<Rgba32>(stream);
public static Image<Rgba32> Load(Stream stream, out IImageFormat format) => Load<Rgba32>(stream, out format);
/// <summary>
/// Create a new instance of the <see cref="Image{Rgba32}"/> class from the given stream.
/// </summary>
/// <param name="stream">The stream containing image information.</param>
/// <param name="options">The options for the decoder.</param>
/// <exception cref="NotSupportedException">
/// Thrown if the stream is not readable nor seekable.
/// </exception>
/// <returns>A new <see cref="Image{Rgba32}"/>.</returns>>
public static Image<Rgba32> Load(Stream stream, IDecoderOptions options) => Load<Rgba32>(stream, options);
public static Image<Rgba32> Load(Stream stream) => Load<Rgba32>(stream);
/// <summary>
/// Create a new instance of the <see cref="Image{Rgba32}"/> class from the given stream.
@ -63,14 +85,14 @@ namespace ImageSharp
/// <summary>
/// Create a new instance of the <see cref="Image{Rgba32}"/> class from the given stream.
/// </summary>
/// <param name="config">The config for the decoder.</param>
/// <param name="stream">The stream containing image information.</param>
/// <param name="decoder">The decoder.</param>
/// <param name="options">The options for the decoder.</param>
/// <param name="format">the mime type of the decoded image.</param>
/// <exception cref="NotSupportedException">
/// Thrown if the stream is not readable nor seekable.
/// </exception>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>>
public static Image<Rgba32> Load(Stream stream, IImageDecoder decoder, IDecoderOptions options) => Load<Rgba32>(stream, decoder, options);
/// <returns>A new <see cref="Image{Rgba32}"/>.</returns>>
public static Image<Rgba32> Load(Configuration config, Stream stream, out IImageFormat format) => Load<Rgba32>(config, stream, out format);
/// <summary>
/// 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)
where TPixel : struct, IPixel<TPixel>
{
return Load<TPixel>(null, stream, null);
return Load<TPixel>(null, stream);
}
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given stream.
/// </summary>
/// <param name="stream">The stream containing image information.</param>
/// <param name="options">The options for the decoder.</param>
/// <param name="format">the mime type of the decoded image.</param>
/// <exception cref="NotSupportedException">
/// Thrown if the stream is not readable nor seekable.
/// </exception>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>>
public static Image<TPixel> Load<TPixel>(Stream stream, IDecoderOptions options)
public static Image<TPixel> Load<TPixel>(Stream stream, out IImageFormat format)
where TPixel : struct, IPixel<TPixel>
{
return Load<TPixel>(null, stream, options);
return Load<TPixel>(null, stream, out format);
}
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given stream.
/// </summary>
/// <param name="config">The config for the decoder.</param>
/// <param name="stream">The stream containing image information.</param>
/// <param name="decoder">The decoder.</param>
/// <exception cref="NotSupportedException">
/// Thrown if the stream is not readable nor seekable.
/// </exception>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>>
public static Image<TPixel> Load<TPixel>(Configuration config, Stream stream)
public static Image<TPixel> Load<TPixel>(Stream stream, IImageDecoder decoder)
where TPixel : struct, IPixel<TPixel>
{
return Load<TPixel>(config, stream, null);
return WithSeekableStream(stream, s => decoder.Decode<TPixel>(Configuration.Default, s));
}
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given stream.
/// </summary>
/// <param name="config">The Configuration.</param>
/// <param name="stream">The stream containing image information.</param>
/// <param name="decoder">The decoder.</param>
/// <exception cref="NotSupportedException">
@ -129,27 +152,26 @@ namespace ImageSharp
/// </exception>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>>
public static Image<TPixel> Load<TPixel>(Stream stream, IImageDecoder decoder)
public static Image<TPixel> Load<TPixel>(Configuration config, Stream stream, IImageDecoder decoder)
where TPixel : struct, IPixel<TPixel>
{
return Load<TPixel>(stream, decoder, null);
return WithSeekableStream(stream, s => decoder.Decode<TPixel>(config, s));
}
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given stream.
/// </summary>
/// <param name="config">The configuration options.</param>
/// <param name="stream">The stream containing image information.</param>
/// <param name="decoder">The decoder.</param>
/// <param name="options">The options for the decoder.</param>
/// <exception cref="NotSupportedException">
/// Thrown if the stream is not readable nor seekable.
/// </exception>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>>
public static Image<TPixel> Load<TPixel>(Stream stream, IImageDecoder decoder, IDecoderOptions options)
public static Image<TPixel> Load<TPixel>(Configuration config, Stream stream)
where TPixel : struct, IPixel<TPixel>
{
return WithSeekableStream(stream, s => decoder.Decode<TPixel>(Configuration.Default, s, options));
return Load<TPixel>(config, stream, out var _);
}
/// <summary>
@ -157,29 +179,31 @@ namespace ImageSharp
/// </summary>
/// <param name="config">The configuration options.</param>
/// <param name="stream">The stream containing image information.</param>
/// <param name="options">The options for the decoder.</param>
/// <param name="format">the mime type of the decoded image.</param>
/// <exception cref="NotSupportedException">
/// Thrown if the stream is not readable nor seekable.
/// </exception>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>>
public static Image<TPixel> Load<TPixel>(Configuration config, Stream stream, IDecoderOptions options)
where TPixel : struct, IPixel<TPixel>
public static Image<TPixel> Load<TPixel>(Configuration config, Stream stream, out IImageFormat format)
where TPixel : struct, IPixel<TPixel>
{
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();
stringBuilder.AppendLine("Image cannot be loaded. Available formats:");
var stringBuilder = new StringBuilder();
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());
@ -198,12 +222,12 @@ namespace ImageSharp
}
// 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);
ms.Position = 0;
stream.CopyTo(memoryStream);
memoryStream.Position = 0;
return action(ms);
return action(memoryStream);
}
}
}

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

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

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>
/// Converts <see cref="Buffer{T}"/> to an <see cref="Span{T}"/>.
/// </summary>

6
src/ImageSharp/MetaData/ImageMetaData.cs

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

4
tests/ImageSharp.Benchmarks/BenchmarkBase.cs

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

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

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

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

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

249
tests/ImageSharp.Tests/ConfigurationTests.cs

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Loading…
Cancel
Save