Browse Source

Merge remote-tracking branch 'origin/master' into antonfirsov/qa-lab

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

4
.travis.yml

@ -26,7 +26,7 @@ env:
global:
# The next declaration is the encrypted COVERITY_SCAN_TOKEN, created
# via the "travis encrypt" command using the project repo's public key
- secure: "aim+fUyx7kDQQcAYV1mX16cvyFEYsxiW3y26xjmeuKzsOf6DIUK328pE8KnO50bMWhfVPjuW7jWc43jI+nbUeIW5018aFcjoOrEK2F8JvJ0UKtEo+ONchypJSXA2TSdL0iIlufMBepsmlBsSLkCwHCJYohYcueZV0u9NVPc3n282KLL8ItRZeSFG/cL/a2yrkFnTFhq9OtkUtP4CcVE7BOtzjfftNcn4Rup73e5JkLT7L9AZS7eCYkIYV0KRlT2pOa/FuOHlfP9NP+NVtd83GXUY2FKBsmN3EmrQgGDTfwfwcJjN5dqIqzkIXmLV8IKQ3aiW2//02pIe5VrdqHQG+EVMRcdpCWyKUkMj0g4rGYkqKCtVJojKtOR93ycOGUDc6+cMMoyn3J2qFydkp278dGWeLuwtGfD25fHXorqK1aL9/bGPcwdinrBmcwnuy1IECtuTkEfAPsb6O4nArnDsTEzeQxwa/MAicmpux//TNKgkQGqzCPeHKbl4vOfyyI6kCsf8edWv8fOSPvJUGvL14+/TZ6lY8S+30fosOmwMCe7xlbtcVlBVtOsKx/XUufrP2Vuptlc8INaq6++XtgpCoMLL0SJfBFQKZRmBGavv1Ztyf0aL6Qp303HKGTyXOEq2k18iJmukB6JcnEGVsaAyteGlruQIbPgHWbxhZSoJZPw="
- secure: "rjMvEMN9rpvIXqXqCAAKzbHyABzr7E4wPU/dYJ/mHBqlCccFpQrEXVVM1MfRFXYuWZSaIioknhLATZjT5xvIYpTNM6D57z4OTmqeRHhYm80="
before_install:
- echo -n | openssl s_client -connect scan.coverity.com:443 | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' | sudo tee -a /etc/ssl/certs/ca-
@ -34,7 +34,7 @@ before_install:
addons:
coverity_scan:
project:
name: "JimBobSquarePants/ImageSharp"
name: "SixLabors/ImageSharp"
description: "Build submitted via Travis CI"
notification_email: james_south@hotmail.com
build_command_prepend: "dotnet restore"

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

20
README.md

@ -1,5 +1,5 @@
# <img src="https://raw.githubusercontent.com/JimBobSquarePants/ImageSharp/master/build/icons/imagesharp-logo-256.png" alt="ImageSharp" width="52"/> ImageSharp
# <img src="https://raw.githubusercontent.com/SixLabors/ImageSharp/master/build/icons/imagesharp-logo-256.png" alt="ImageSharp" width="52"/> ImageSharp
**ImageSharp** is a new, fully featured, fully managed, cross-platform, 2D graphics API designed to allow the processing of images without the use of `System.Drawing`.
@ -9,12 +9,12 @@ Built against .Net Standard 1.1 ImageSharp can be used in device, cloud, and emb
>
> Pre-release downloads are available from the [MyGet package repository](https://www.myget.org/gallery/imagesharp).
[![GitHub license](https://img.shields.io/badge/license-Apache%202-blue.svg)](https://raw.githubusercontent.com/JimBobSquarePants/ImageSharp/master/APACHE-2.0-LICENSE.txt)
[![GitHub issues](https://img.shields.io/github/issues/JimBobSquarePants/ImageSharp.svg)](https://github.com/JimBobSquarePants/ImageSharp/issues)
[![GitHub stars](https://img.shields.io/github/stars/JimBobSquarePants/ImageSharp.svg)](https://github.com/JimBobSquarePants/ImageSharp/stargazers)
[![GitHub forks](https://img.shields.io/github/forks/JimBobSquarePants/ImageSharp.svg)](https://github.com/JimBobSquarePants/ImageSharp/network)
[![GitHub license](https://img.shields.io/badge/license-Apache%202-blue.svg)](https://raw.githubusercontent.com/SixLabors/ImageSharp/master/APACHE-2.0-LICENSE.txt)
[![GitHub issues](https://img.shields.io/github/issues/SixLabors/ImageSharp.svg)](https://github.com/SixLabors/ImageSharp/issues)
[![GitHub stars](https://img.shields.io/github/stars/SixLabors/ImageSharp.svg)](https://github.com/SixLabors/ImageSharp/stargazers)
[![GitHub forks](https://img.shields.io/github/forks/SixLabors/ImageSharp.svg)](https://github.com/SixLabors/ImageSharp/network)
[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/ImageSharp/General?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Twitter](https://img.shields.io/twitter/url/https/github.com/JimBobSquarePants/ImageSharp.svg?style=social)](https://twitter.com/intent/tweet?hashtags=imagesharp,dotnet,oss&text=ImageSharp.+A+new+cross-platform+2D+graphics+API+in+C%23&url=https%3a%2f%2fgithub.com%2fJimBobSquarePants%2fImageSharp&via=james_m_south)
[![Twitter](https://img.shields.io/twitter/url/https/github.com/SixLabors/ImageSharp.svg?style=social)](https://twitter.com/intent/tweet?hashtags=imagesharp,dotnet,oss&text=ImageSharp.+A+new+cross-platform+2D+graphics+API+in+C%23&url=https%3a%2f%2fgithub.com%2fSixLabors%2fImageSharp&via=sixlabors)
[![OpenCollective](https://opencollective.com/imagesharp/backers/badge.svg)](#backers)
[![OpenCollective](https://opencollective.com/imagesharp/sponsors/badge.svg)](#sponsors)
@ -22,8 +22,8 @@ Built against .Net Standard 1.1 ImageSharp can be used in device, cloud, and emb
| |Build Status|Code Coverage|
|-------------|:----------:|:-----------:|
|**Linux/Mac**|[![Build Status](https://travis-ci.org/JimBobSquarePants/ImageSharp.svg)](https://travis-ci.org/JimBobSquarePants/ImageSharp)|[![Code coverage](https://codecov.io/gh/JimBobSquarePants/ImageSharp/branch/master/graph/badge.svg)](https://codecov.io/gh/JimBobSquarePants/ImageSharp)|
|**Windows** |[![Build Status](https://ci.appveyor.com/api/projects/status/hu6d1gdpxdw0q360/branch/master?svg=true)](https://ci.appveyor.com/project/JamesSouth/imagesharp/branch/master)|[![Code coverage](https://codecov.io/gh/JimBobSquarePants/ImageSharp/branch/master/graph/badge.svg)](https://codecov.io/gh/JimBobSquarePants/ImageSharp)|
|**Linux/Mac**|[![Build Status](https://travis-ci.org/SixLabors/ImageSharp.svg)](https://travis-ci.org/SixLabors/ImageSharp)|[![Code coverage](https://codecov.io/gh/SixLabors/ImageSharp/branch/master/graph/badge.svg)](https://codecov.io/gh/SixLabors/ImageSharp)|
|**Windows** |[![Build Status](https://ci.appveyor.com/api/projects/status/m9pn907xdah3ca39/branch/master?svg=true)](https://ci.appveyor.com/project/six-labors/imagesharp/branch/master)|[![Code coverage](https://codecov.io/gh/SixLabors/ImageSharp/branch/master/graph/badge.svg)](https://codecov.io/gh/SixLabors/ImageSharp)|
### Installation
@ -64,7 +64,7 @@ Alternatively on Linux you can use:
To clone it locally click the "Clone in Windows" button above or run the following git commands.
```bash
git clone https://github.com/JimBobSquarePants/ImageSharp
git clone https://github.com/SixLabors/ImageSharp
```
### Features
@ -121,7 +121,7 @@ For optimized access within a loop it is recommended that the following methods
1. `image.GetRowSpan(y)`
2. `image.GetRowSpan(x, y)`
For advanced pixel format usage there are multiple [PixelFormat implementations](https://github.com/JimBobSquarePants/ImageSharp/tree/master/src/ImageSharp/PixelFormats) available allowing developers to implement their own color models in the same manner as Microsoft XNA Game Studio and MonoGame.
For advanced pixel format usage there are multiple [PixelFormat implementations](https://github.com/SixLabors/ImageSharp/tree/master/src/ImageSharp/PixelFormats) available allowing developers to implement their own color models in the same manner as Microsoft XNA Game Studio and MonoGame.
All in all this should allow image processing to be much more accessible to developers which has always been my goal from the start.

2
appveyor.yml

@ -22,7 +22,7 @@ deploy:
server: https://www.myget.org/F/imagesharp/api/v2/package
symbol_server: https://www.myget.org/F/imagesharp/symbols/api/v2/package
api_key:
secure: fz0rUrt3B1HczUC1ZehwVsrFSWX9WZGDQoueDztLte9/+yQG+BBU7UrO+coE8lUf
secure: P2Fz82nty+itjL+kNRCsMQcqzngmVtkU0R4CZqgST7zgUaE6/1q9ekh5MKKlZLkD
artifact: /.*\.nupkg/
on:
branch: master

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="IImageFormat"/>s.
/// </summary>
private readonly ConcurrentBag<IImageFormat> imageFormats = new ConcurrentBag<IImageFormat>();
/// <summary>
/// The list of supported <see cref="IImageFormatDetector"/>s.
/// </summary>
private ConcurrentBag<IImageFormatDetector> imageFormatDetectors = new ConcurrentBag<IImageFormatDetector>();
/// <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 = new ConcurrentBag<IImageFormatDetector>();
}
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 bmp 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);
}
}
}

34
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>
@ -151,6 +157,10 @@ namespace ImageSharp.Formats
}
nextFlag = stream.ReadByte();
if (nextFlag == -1)
{
break;
}
}
}
finally
@ -269,7 +279,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 +290,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 +374,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 gif 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>

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

@ -6,6 +6,7 @@
namespace ImageSharp.Formats
{
using System;
using System.Collections.Generic;
using System.IO;
using ImageSharp.PixelFormats;
@ -13,15 +14,20 @@ 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 (JpegDecoderCore decoder = new JpegDecoderCore(options, configuration))
using (JpegDecoderCore decoder = new 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;
}
}
}

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

205
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];
@ -262,45 +267,48 @@ namespace ImageSharp.Formats
/// <param name="source">The bytes to convert from. Cannot be null.</param>
/// <param name="bytesPerScanline">The number of bytes per scanline</param>
/// <param name="bits">The number of bits per value.</param>
/// <returns>The resulting <see cref="T:byte[]"/> array. Is never null.</returns>
/// <returns>The resulting <see cref="Span{Byte}"/> array. Is never null.</returns>
/// <exception cref="System.ArgumentNullException"><paramref name="source"/> is null.</exception>
/// <exception cref="System.ArgumentException"><paramref name="bits"/> is less than or equals than zero.</exception>
private static byte[] ToArrayByBitsLength(byte[] source, int bytesPerScanline, int bits)
private static Span<byte> ToArrayByBitsLength(Span<byte> source, int bytesPerScanline, int bits)
{
Guard.NotNull(source, nameof(source));
Guard.MustBeGreaterThan(bits, 0, nameof(bits));
byte[] result;
if (bits < 8)
if (bits >= 8)
{
result = new byte[bytesPerScanline * 8 / bits];
int mask = 0xFF >> (8 - bits);
int resultOffset = 0;
return source;
}
// ReSharper disable once ForCanBeConvertedToForeach
// First byte is the marker so skip.
for (int i = 1; i < bytesPerScanline; i++)
byte[] result = new byte[bytesPerScanline * 8 / bits];
int mask = 0xFF >> (8 - bits);
int resultOffset = 0;
for (int i = 0; i < bytesPerScanline; i++)
{
byte b = source[i];
for (int shift = 0; shift < 8; shift += bits)
{
byte b = source[i];
for (int shift = 0; shift < 8; shift += bits)
{
int colorIndex = (b >> (8 - bits - shift)) & mask;
int colorIndex = (b >> (8 - bits - shift)) & mask;
result[resultOffset] = (byte)colorIndex;
result[resultOffset] = (byte)colorIndex;
resultOffset++;
}
resultOffset++;
}
}
else
{
result = source;
}
return result;
}
private static bool IsCriticalChunk(PngChunk chunk)
{
return
chunk.Type == PngChunkTypes.Header ||
chunk.Type == PngChunkTypes.Palette ||
chunk.Type == PngChunkTypes.Data ||
chunk.Type == PngChunkTypes.End;
}
/// <summary>
/// Reads the data chunk containing physical dimension data.
/// </summary>
@ -344,7 +352,7 @@ namespace ImageSharp.Formats
/// <returns>The <see cref="int"/></returns>
private int CalculateBytesPerPixel()
{
switch (this.PngColorType)
switch (this.pngColorType)
{
case PngColorType.Grayscale:
return 1;
@ -570,13 +578,15 @@ namespace ImageSharp.Formats
{
var color = default(TPixel);
Span<TPixel> rowSpan = pixels.GetRowSpan(this.currentRow);
// Trim the first marker byte from the buffer
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);
byte[] newScanline1 = ToArrayByBitsLength(defilteredScanline, this.bytesPerScanline, this.header.BitDepth);
Span<byte> newScanline1 = ToArrayByBitsLength(scanlineBuffer, this.bytesPerScanline, this.header.BitDepth);
for (int x = 0; x < this.header.Width; x++)
{
byte intensity = (byte)(newScanline1[x] * factor);
@ -590,10 +600,10 @@ namespace ImageSharp.Formats
for (int x = 0; x < this.header.Width; x++)
{
int offset = 1 + (x * this.bytesPerPixel);
int offset = x * this.bytesPerPixel;
byte intensity = defilteredScanline[offset];
byte alpha = defilteredScanline[offset + this.bytesPerSample];
byte intensity = scanlineBuffer[offset];
byte alpha = scanlineBuffer[offset + this.bytesPerSample];
color.PackFromRgba32(new Rgba32(intensity, intensity, intensity, alpha));
rowSpan[x] = color;
@ -603,7 +613,7 @@ namespace ImageSharp.Formats
case PngColorType.Palette:
this.ProcessScanlineFromPalette(defilteredScanline, rowSpan);
this.ProcessScanlineFromPalette(scanlineBuffer, rowSpan);
break;
@ -668,10 +678,10 @@ namespace ImageSharp.Formats
/// <typeparam name="TPixel">The type of pixel we are expanding to</typeparam>
/// <param name="defilteredScanline">The scanline</param>
/// <param name="row">Thecurrent output image row</param>
private void ProcessScanlineFromPalette<TPixel>(byte[] defilteredScanline, Span<TPixel> row)
private void ProcessScanlineFromPalette<TPixel>(Span<byte> defilteredScanline, Span<TPixel> row)
where TPixel : struct, IPixel<TPixel>
{
byte[] newScanline = ToArrayByBitsLength(defilteredScanline, this.bytesPerScanline, this.header.BitDepth);
Span<byte> newScanline = ToArrayByBitsLength(defilteredScanline, this.bytesPerScanline, this.header.BitDepth);
byte[] pal = this.palette;
var color = default(TPixel);
@ -683,19 +693,11 @@ namespace ImageSharp.Formats
// channel and we should try to read it.
for (int x = 0; x < this.header.Width; x++)
{
int index = newScanline[x + 1];
int index = newScanline[x];
int pixelOffset = index * 3;
rgba.A = this.paletteAlpha.Length > index ? this.paletteAlpha[index] : (byte)255;
if (rgba.A > 0)
{
rgba.Rgb = pal.GetRgb24(pixelOffset);
}
else
{
rgba = default(Rgba32);
}
rgba.Rgb = pal.GetRgb24(pixelOffset);
color.PackFromRgba32(rgba);
row[x] = color;
@ -707,7 +709,7 @@ namespace ImageSharp.Formats
for (int x = 0; x < this.header.Width; x++)
{
int index = newScanline[x + 1];
int index = newScanline[x];
int pixelOffset = index * 3;
rgba.Rgb = pal.GetRgb24(pixelOffset);
@ -731,15 +733,15 @@ namespace ImageSharp.Formats
{
var color = default(TPixel);
switch (this.PngColorType)
// Trim the first marker byte from the buffer
var scanlineBuffer = new Span<byte>(defilteredScanline, 1);
switch (this.pngColorType)
{
case PngColorType.Grayscale:
int factor = 255 / ((int)Math.Pow(2, this.header.BitDepth) - 1);
byte[] newScanline1 = ToArrayByBitsLength(
defilteredScanline,
this.bytesPerScanline,
this.header.BitDepth);
for (int x = pixelOffset, o = 1; x < this.header.Width; x += increment, o++)
Span<byte> newScanline1 = ToArrayByBitsLength(scanlineBuffer, this.bytesPerScanline, this.header.BitDepth);
for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o++)
{
byte intensity = (byte)(newScanline1[o] * factor);
color.PackFromRgba32(new Rgba32(intensity, intensity, intensity));
@ -750,10 +752,10 @@ namespace ImageSharp.Formats
case PngColorType.GrayscaleWithAlpha:
for (int x = pixelOffset, o = 1; x < this.header.Width; x += increment, o += this.bytesPerPixel)
for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += this.bytesPerPixel)
{
byte intensity = defilteredScanline[o];
byte alpha = defilteredScanline[o + this.bytesPerSample];
byte intensity = scanlineBuffer[o];
byte alpha = scanlineBuffer[o + this.bytesPerSample];
color.PackFromRgba32(new Rgba32(intensity, intensity, intensity, alpha));
rowSpan[x] = color;
}
@ -762,31 +764,20 @@ namespace ImageSharp.Formats
case PngColorType.Palette:
byte[] newScanline = ToArrayByBitsLength(
defilteredScanline,
this.bytesPerScanline,
this.header.BitDepth);
Span<byte> newScanline = ToArrayByBitsLength(scanlineBuffer, this.bytesPerScanline, this.header.BitDepth);
var rgba = default(Rgba32);
if (this.paletteAlpha != null && this.paletteAlpha.Length > 0)
{
// If the alpha palette is not null and has one or more entries, this means, that the image contains an alpha
// channel and we should try to read it.
for (int x = pixelOffset, o = 1; x < this.header.Width; x += increment, o++)
for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o++)
{
int index = newScanline[o];
int offset = index * 3;
rgba.A = this.paletteAlpha.Length > index ? this.paletteAlpha[index] : (byte)255;
if (rgba.A > 0)
{
rgba.Rgb = this.palette.GetRgb24(offset);
}
else
{
rgba = default(Rgba32);
}
rgba.Rgb = this.palette.GetRgb24(offset);
color.PackFromRgba32(rgba);
rowSpan[x] = color;
@ -796,7 +787,7 @@ namespace ImageSharp.Formats
{
rgba.A = 255;
for (int x = pixelOffset, o = 1; x < this.header.Width; x += increment, o++)
for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o++)
{
int index = newScanline[o];
int offset = index * 3;
@ -820,14 +811,12 @@ namespace ImageSharp.Formats
using (var compressed = new Buffer<byte>(length))
{
// TODO: Should we use pack from vector here instead?
this.From16BitTo8Bit(new Span<byte>(defilteredScanline), compressed, length);
for (int x = pixelOffset, o = 1;
x < this.header.Width;
x += increment, o += this.bytesPerPixel)
this.From16BitTo8Bit(scanlineBuffer, compressed, length);
for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += 3)
{
rgba.R = compressed[o];
rgba.G = compressed[o + this.bytesPerSample];
rgba.B = compressed[o + (2 * this.bytesPerSample)];
rgba.G = compressed[o + 1];
rgba.B = compressed[o + 2];
color.PackFromRgba32(rgba);
rowSpan[x] = color;
@ -836,11 +825,11 @@ namespace ImageSharp.Formats
}
else
{
for (int x = pixelOffset, o = 1; x < this.header.Width; x += increment, o += this.bytesPerPixel)
for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += this.bytesPerPixel)
{
rgba.R = defilteredScanline[o];
rgba.G = defilteredScanline[o + this.bytesPerSample];
rgba.B = defilteredScanline[o + (2 * this.bytesPerSample)];
rgba.R = scanlineBuffer[o];
rgba.G = scanlineBuffer[o + this.bytesPerSample];
rgba.B = scanlineBuffer[o + (2 * this.bytesPerSample)];
color.PackFromRgba32(rgba);
rowSpan[x] = color;
@ -857,13 +846,13 @@ namespace ImageSharp.Formats
using (var compressed = new Buffer<byte>(length))
{
// TODO: Should we use pack from vector here instead?
this.From16BitTo8Bit(new Span<byte>(defilteredScanline), compressed, length);
for (int x = pixelOffset, o = 1; x < this.header.Width; x += increment, o += this.bytesPerPixel)
this.From16BitTo8Bit(scanlineBuffer, compressed, length);
for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += 4)
{
rgba.R = compressed[o];
rgba.G = compressed[o + this.bytesPerSample];
rgba.B = compressed[o + (2 * this.bytesPerSample)];
rgba.A = compressed[o + (3 * this.bytesPerSample)];
rgba.G = compressed[o + 1];
rgba.B = compressed[o + 2];
rgba.A = compressed[o + 3];
color.PackFromRgba32(rgba);
rowSpan[x] = color;
@ -872,12 +861,12 @@ namespace ImageSharp.Formats
}
else
{
for (int x = pixelOffset, o = 1; x < this.header.Width; x += increment, o += this.bytesPerPixel)
for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += this.bytesPerPixel)
{
rgba.R = defilteredScanline[o];
rgba.G = defilteredScanline[o + this.bytesPerSample];
rgba.B = defilteredScanline[o + (2 * this.bytesPerSample)];
rgba.A = defilteredScanline[o + (3 * this.bytesPerSample)];
rgba.R = scanlineBuffer[o];
rgba.G = scanlineBuffer[o + this.bytesPerSample];
rgba.B = scanlineBuffer[o + (2 * this.bytesPerSample)];
rgba.A = scanlineBuffer[o + (3 * this.bytesPerSample)];
color.PackFromRgba32(rgba);
rowSpan[x] = color;
@ -896,7 +885,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 +901,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 +956,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>
@ -1020,9 +1009,9 @@ namespace ImageSharp.Formats
this.crc.Update(this.chunkTypeBuffer);
this.crc.Update(chunk.Data, 0, chunk.Length);
if (this.crc.Value != chunk.Crc)
if (this.crc.Value != chunk.Crc && IsCriticalChunk(chunk))
{
throw new ImageFormatException("CRC Error. PNG Image chunk is corrupt!");
throw new ImageFormatException($"CRC Error. PNG {chunk.Type} chunk is corrupt!");
}
}

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 png 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>

2
src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs

@ -79,7 +79,7 @@ namespace ImageSharp.Processing.Processors
return;
}
this.processMatrix = Matrix3x2Extensions.CreateRotation(-this.Angle, new Point(0, 0));
this.processMatrix = Matrix3x2Extensions.CreateRotationDegrees(-this.Angle, new Point(0, 0));
if (this.Expand)
{
this.CreateNewCanvas(sourceRectangle, this.processMatrix);

12
src/ImageSharp/Processing/Transforms/AutoOrient.cs

@ -5,6 +5,7 @@
namespace ImageSharp
{
using System;
using ImageSharp.PixelFormats;
using ImageSharp.Processing;
@ -77,7 +78,16 @@ namespace ImageSharp
return Orientation.Unknown;
}
var orientation = (Orientation)value.Value;
Orientation orientation;
if (value.DataType == ExifDataType.Short)
{
orientation = (Orientation)value.Value;
}
else
{
orientation = (Orientation)Convert.ToUInt16(value.Value);
source.MetaData.ExifProfile.RemoveValue(ExifTag.Orientation);
}
source.MetaData.ExifProfile.SetValue(ExifTag.Orientation, (ushort)Orientation.TopLeft);

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

4
tests/ImageSharp.Tests/FileTestBase.cs

@ -79,8 +79,8 @@ namespace ImageSharp.Tests
// TestFile.Create(TestImages.Bmp.NegHeight), // Perf: Enable for local testing only
TestFile.Create(TestImages.Png.Splash),
// TestFile.Create(TestImages.Png.Cross), // Perf: Enable for local testing only
// TestFile.Create(TestImages.Png.ChunkLength1), // Perf: Enable for local testing only
// TestFile.Create(TestImages.Png.ChunkLength2), // Perf: Enable for local testing only
// TestFile.Create(TestImages.Png.Bad.ChunkLength1), // Perf: Enable for local testing only
// TestFile.Create(TestImages.Png.Bad.ChunkLength2), // Perf: Enable for local testing only
// TestFile.Create(TestImages.Png.Powerpoint), // Perf: Enable for local testing only
// TestFile.Create(TestImages.Png.Blur), // Perf: Enable for local testing only
// TestFile.Create(TestImages.Png.Indexed), // Perf: Enable for local testing only

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

Loading…
Cancel
Save