Browse Source

Merge branch 'master' into feature/adaptiveHistogramEqualization

pull/673/head
Brian Popow 8 years ago
committed by GitHub
parent
commit
d9dcfc7510
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 13
      .github/ISSUE_TEMPLATE/ask-question.md
  2. 6
      .github/ISSUE_TEMPLATE/bug-report.md
  3. 13
      .github/ISSUE_TEMPLATE/feature-request.md
  4. 4
      ImageSharp.sln
  5. 9
      README.md
  6. 2
      appveyor.yml
  7. 2
      build.ps1
  8. 210
      src/ImageSharp.Drawing/Processing/DrawImageExtensions.cs
  9. 53
      src/ImageSharp.Drawing/Processing/Processors/Drawing/DrawImageProcessor.cs
  10. 2
      src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLchuv.cs
  11. 11
      src/ImageSharp/Common/Extensions/ComparableExtensions.cs
  12. 18
      src/ImageSharp/Common/Helpers/ImageMaths.cs
  13. 2
      src/ImageSharp/Configuration.cs
  14. 6
      src/ImageSharp/Formats/Bmp/BmpBitsPerPixel.cs
  15. 8
      src/ImageSharp/Formats/Bmp/BmpConfigurationModule.cs
  16. 34
      src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
  17. 2
      src/ImageSharp/Formats/Bmp/BmpEncoder.cs
  18. 37
      src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
  19. 14
      src/ImageSharp/Formats/Bmp/BmpFormat.cs
  20. 2
      src/ImageSharp/Formats/Bmp/BmpImageFormatDetector.cs
  21. 34
      src/ImageSharp/Formats/Bmp/BmpMetaData.cs
  22. 2
      src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs
  23. 2
      src/ImageSharp/Formats/Bmp/ImageExtensions.cs
  24. 2
      src/ImageSharp/Formats/Gif/GifColorTableMode.cs
  25. 9
      src/ImageSharp/Formats/Gif/GifConfigurationModule.cs
  26. 13
      src/ImageSharp/Formats/Gif/GifConstants.cs
  27. 1
      src/ImageSharp/Formats/Gif/GifDecoder.cs
  28. 119
      src/ImageSharp/Formats/Gif/GifDecoderCore.cs
  29. 2
      src/ImageSharp/Formats/Gif/GifDisposalMethod.cs
  30. 7
      src/ImageSharp/Formats/Gif/GifEncoder.cs
  31. 116
      src/ImageSharp/Formats/Gif/GifEncoderCore.cs
  32. 17
      src/ImageSharp/Formats/Gif/GifFormat.cs
  33. 54
      src/ImageSharp/Formats/Gif/GifFrameMetaData.cs
  34. 2
      src/ImageSharp/Formats/Gif/GifImageFormatDetector.cs
  35. 50
      src/ImageSharp/Formats/Gif/GifMetaData.cs
  36. 1
      src/ImageSharp/Formats/Gif/IGifDecoderOptions.cs
  37. 7
      src/ImageSharp/Formats/Gif/IGifEncoderOptions.cs
  38. 2
      src/ImageSharp/Formats/Gif/ImageExtensions.cs
  39. 4
      src/ImageSharp/Formats/Gif/Sections/GifGraphicControlExtension.cs
  40. 4
      src/ImageSharp/Formats/Gif/Sections/GifImageDescriptor.cs
  41. 44
      src/ImageSharp/Formats/Gif/Sections/GifNetscapeLoopingApplicationExtension.cs
  42. 32
      src/ImageSharp/Formats/IImageFormat.cs
  43. 7
      src/ImageSharp/Formats/Jpeg/Components/Block8x8F.CopyTo.cs
  44. 2
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmyk.cs
  45. 2
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScale.cs
  46. 2
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgb.cs
  47. 4
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrBasic.cs
  48. 4
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimd.cs
  49. 4
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimdAvx2.cs
  50. 2
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccK.cs
  51. 2
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs
  52. 8
      src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBlockPostProcessor.cs
  53. 140
      src/ImageSharp/Formats/Jpeg/Components/Decoder/QualityEvaluator.cs
  54. 7
      src/ImageSharp/Formats/Jpeg/IJpegEncoderOptions.cs
  55. 2
      src/ImageSharp/Formats/Jpeg/ImageExtensions.cs
  56. 9
      src/ImageSharp/Formats/Jpeg/JpegConfigurationModule.cs
  57. 53
      src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
  58. 7
      src/ImageSharp/Formats/Jpeg/JpegEncoder.cs
  59. 52
      src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
  60. 14
      src/ImageSharp/Formats/Jpeg/JpegFormat.cs
  61. 2
      src/ImageSharp/Formats/Jpeg/JpegImageFormatDetector.cs
  62. 32
      src/ImageSharp/Formats/Jpeg/JpegMetaData.cs
  63. 18
      src/ImageSharp/Formats/Png/IPngEncoderOptions.cs
  64. 2
      src/ImageSharp/Formats/Png/ImageExtensions.cs
  65. 15
      src/ImageSharp/Formats/Png/PngBitDepth.cs
  66. 52
      src/ImageSharp/Formats/Png/PngChunkType.cs
  67. 4
      src/ImageSharp/Formats/Png/PngConfigurationModule.cs
  68. 190
      src/ImageSharp/Formats/Png/PngDecoderCore.cs
  69. 19
      src/ImageSharp/Formats/Png/PngEncoder.cs
  70. 139
      src/ImageSharp/Formats/Png/PngEncoderCore.cs
  71. 14
      src/ImageSharp/Formats/Png/PngFormat.cs
  72. 2
      src/ImageSharp/Formats/Png/PngImageFormatDetector.cs
  73. 48
      src/ImageSharp/Formats/Png/PngMetaData.cs
  74. 48
      src/ImageSharp/Formats/Png/Zlib/ZlibInflateStream.cs
  75. 31
      src/ImageSharp/IDeepCloneable.cs
  76. 37
      src/ImageSharp/ImageFormats.cs
  77. 18
      src/ImageSharp/ImageFrameCollection.cs
  78. 73
      src/ImageSharp/ImageFrame{TPixel}.cs
  79. 88
      src/ImageSharp/Image{TPixel}.cs
  80. 2
      src/ImageSharp/MetaData/FrameDecodingMode.cs
  81. 51
      src/ImageSharp/MetaData/ImageFrameMetaData.cs
  82. 66
      src/ImageSharp/MetaData/ImageMetaData.cs
  83. 27
      src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs
  84. 24
      src/ImageSharp/MetaData/Profiles/Exif/ExifReader.cs
  85. 69
      src/ImageSharp/MetaData/Profiles/Exif/ExifValue.cs
  86. 2
      src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Matrix.cs
  87. 36
      src/ImageSharp/MetaData/Profiles/ICC/IccProfile.cs
  88. 8166
      src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs
  89. 44
      src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt
  90. 838
      src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.cs
  91. 67
      src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.tt
  92. 31
      src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs
  93. 120
      src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs
  94. 2
      src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs
  95. 9
      src/ImageSharp/Processing/Processors/Quantization/IQuantizer.cs
  96. 32
      src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs
  97. 23
      src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer.cs
  98. 2
      src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs
  99. 20
      src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs
  100. 165
      src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs

13
.github/ISSUE_TEMPLATE/ask-question.md

@ -0,0 +1,13 @@
---
name: Ask question
about: Ask a question about this project.
---
You should not create an issue but use Gitter instead: https://gitter.im/ImageSharp/General
You should not create an issue but use Gitter instead: https://gitter.im/ImageSharp/General
You should not create an issue but use Gitter instead: https://gitter.im/ImageSharp/General
You should not create an issue but use Gitter instead: https://gitter.im/ImageSharp/General

6
.github/ISSUE_TEMPLATE.md → .github/ISSUE_TEMPLATE/bug-report.md

@ -1,3 +1,9 @@
---
name: Bug report
about: Create a report to help us improve
---
### Prerequisites
- [ ] I have written a descriptive issue title

13
.github/ISSUE_TEMPLATE/feature-request.md

@ -0,0 +1,13 @@
---
name: Feature request
about: Suggest an idea for this project
---
You should first discuss the feature on Gitter: https://gitter.im/ImageSharp/General
You should first discuss the feature on Gitter: https://gitter.im/ImageSharp/General
You should first discuss the feature on Gitter: https://gitter.im/ImageSharp/General
You should first discuss the feature on Gitter: https://gitter.im/ImageSharp/General

4
ImageSharp.sln

@ -8,13 +8,15 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SolutionItems", "SolutionIt
.editorconfig = .editorconfig
.travis.yml = .travis.yml
appveyor.yml = appveyor.yml
.github\ISSUE_TEMPLATE\ask-question.md = .github\ISSUE_TEMPLATE\ask-question.md
.github\ISSUE_TEMPLATE\bug-report.md = .github\ISSUE_TEMPLATE\bug-report.md
codecov.yml = codecov.yml
CodeCoverage.runsettings = CodeCoverage.runsettings
.github\CONTRIBUTING.md = .github\CONTRIBUTING.md
.github\ISSUE_TEMPLATE\feature-request.md = .github\ISSUE_TEMPLATE\feature-request.md
features.md = features.md
ImageSharp.ruleset = ImageSharp.ruleset
ImageSharp.sln.DotSettings = ImageSharp.sln.DotSettings
.github\ISSUE_TEMPLATE.md = .github\ISSUE_TEMPLATE.md
NuGet.config = NuGet.config
.github\PULL_REQUEST_TEMPLATE.md = .github\PULL_REQUEST_TEMPLATE.md
README.md = README.md

9
README.md

@ -70,6 +70,9 @@ Our API is designed to be simple to consume. Here's an example of the code requi
On platforms supporting netstandard 1.3+
```csharp
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Processing;
// Image.Load(string path) is a shortcut for our default type.
// Other pixel formats use Image.Load<TPixel>(string path))
using (Image<Rgba32> image = Image.Load("foo.jpg"))
@ -83,6 +86,9 @@ using (Image<Rgba32> image = Image.Load("foo.jpg"))
On netstandard 1.1 - 1.2
```csharp
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Processing;
// Image.Load(Stream stream) is a shortcut for our default type.
// Other pixel formats use Image.Load<TPixel>(Stream stream))
using (FileStream stream = File.OpenRead("foo.jpg"))
@ -99,6 +105,9 @@ using (Image<Rgba32> image = Image.Load(stream))
Setting individual pixel values can be performed as follows:
```csharp
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
// Individual pixels
using (Image<Rgba32> image = new Image<Rgba32>(400, 400))
{

2
appveyor.yml

@ -1,5 +1,5 @@
version: 1.0.0.{build}
image: Previous Visual Studio 2017
image: Visual Studio 2017
# prevent the double build when a branch has an active PR
skip_branch_with_pr: true

2
build.ps1

@ -94,7 +94,7 @@ if("$env:APPVEYOR_API_URL" -ne ""){
}
Write-Host "Building version '${version}'"
dotnet restore /p:packageversion=$version
dotnet restore /p:packageversion=$version /p:DisableImplicitNuGetFallbackFolder=true
Write-Host "Building projects"
dotnet build -c Release /p:packageversion=$version

210
src/ImageSharp.Drawing/Processing/DrawImageExtensions.cs

@ -1,121 +1,137 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Drawing;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing
{
/// <summary>
/// Adds extensions that allow the drawing of images to the <see cref="Image{TPixel}"/> type.
/// </summary>
public static class DrawImageExtensions
{
/// <summary>
/// Draws the given image together with the current one by blending their pixels.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="image">The image to blend with the currently processing image.</param>
/// <param name="opacity">The opacity of the image to blend. Must be between 0 and 1.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> DrawImage<TPixel>(this IImageProcessingContext<TPixel> source, Image<TPixel> image, float opacity)
where TPixel : struct, IPixel<TPixel>
=> source.ApplyProcessor(new DrawImageProcessor<TPixel>(image, Point.Empty, GraphicsOptions.Default.ColorBlendingMode, GraphicsOptions.Default.AlphaCompositionMode, opacity));
/// <summary>
/// Draws the given image together with the current one by blending their pixels.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="image">The image to blend with the currently processing image.</param>
/// <param name="colorBlending">The blending mode.</param>
/// <param name="opacity">The opacity of the image to blend. Must be between 0 and 1.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> DrawImage<TPixel>(this IImageProcessingContext<TPixel> source, Image<TPixel> image, PixelColorBlendingMode colorBlending, float opacity)
where TPixel : struct, IPixel<TPixel>
=> source.ApplyProcessor(new DrawImageProcessor<TPixel>(image, Point.Empty, colorBlending, GraphicsOptions.Default.AlphaCompositionMode, opacity));
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
/// <summary>
/// Draws the given image together with the current one by blending their pixels.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="image">The image to blend with the currently processing image.</param>
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Drawing;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing
{
/// <summary>
/// Adds extensions that allow the drawing of images to the <see cref="Image{TPixel}"/> type.
/// </summary>
public static class DrawImageExtensions
{
/// <summary>
/// Draws the given image together with the current one by blending their pixels.
/// </summary>
/// <typeparam name="TPixelDst">The pixel format of the destination image.</typeparam>
/// <typeparam name="TPixelSrc">The pixel format of the source image.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="image">The image to blend with the currently processing image.</param>
/// <param name="opacity">The opacity of the image to blend. Must be between 0 and 1.</param>
/// <returns>The <see cref="Image{TPixelDst}"/>.</returns>
public static IImageProcessingContext<TPixelDst> DrawImage<TPixelDst, TPixelSrc>(this IImageProcessingContext<TPixelDst> source, Image<TPixelSrc> image, float opacity)
where TPixelDst : struct, IPixel<TPixelDst>
where TPixelSrc : struct, IPixel<TPixelSrc>
=> source.ApplyProcessor(new DrawImageProcessor<TPixelDst, TPixelSrc>(image, Point.Empty, GraphicsOptions.Default.ColorBlendingMode, GraphicsOptions.Default.AlphaCompositionMode, opacity));
/// <summary>
/// Draws the given image together with the current one by blending their pixels.
/// </summary>
/// <typeparam name="TPixelDst">The pixel format of the destination image.</typeparam>
/// <typeparam name="TPixelSrc">The pixel format of the source image.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="image">The image to blend with the currently processing image.</param>
/// <param name="colorBlending">The blending mode.</param>
/// <param name="opacity">The opacity of the image to blend. Must be between 0 and 1.</param>
/// <returns>The <see cref="Image{TPixelDst}"/>.</returns>
public static IImageProcessingContext<TPixelDst> DrawImage<TPixelDst, TPixelSrc>(this IImageProcessingContext<TPixelDst> source, Image<TPixelSrc> image, PixelColorBlendingMode colorBlending, float opacity)
where TPixelDst : struct, IPixel<TPixelDst>
where TPixelSrc : struct, IPixel<TPixelSrc>
=> source.ApplyProcessor(new DrawImageProcessor<TPixelDst, TPixelSrc>(image, Point.Empty, colorBlending, GraphicsOptions.Default.AlphaCompositionMode, opacity));
/// <summary>
/// Draws the given image together with the current one by blending their pixels.
/// </summary>
/// <typeparam name="TPixelDst">The pixel format of the destination image.</typeparam>
/// <typeparam name="TPixelSrc">The pixel format of the source image.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="image">The image to blend with the currently processing image.</param>
/// <param name="colorBlending">The color blending mode.</param>
/// <param name="alphaComposition">The alpha composition mode.</param>
/// <param name="opacity">The opacity of the image to blend. Must be between 0 and 1.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> DrawImage<TPixel>(this IImageProcessingContext<TPixel> source, Image<TPixel> image, PixelColorBlendingMode colorBlending, PixelAlphaCompositionMode alphaComposition, float opacity)
where TPixel : struct, IPixel<TPixel>
=> source.ApplyProcessor(new DrawImageProcessor<TPixel>(image, Point.Empty, colorBlending, alphaComposition, opacity));
/// <param name="alphaComposition">The alpha composition mode.</param>
/// <param name="opacity">The opacity of the image to blend. Must be between 0 and 1.</param>
/// <returns>The <see cref="Image{TPixelDst}"/>.</returns>
public static IImageProcessingContext<TPixelDst> DrawImage<TPixelDst, TPixelSrc>(this IImageProcessingContext<TPixelDst> source, Image<TPixelSrc> image, PixelColorBlendingMode colorBlending, PixelAlphaCompositionMode alphaComposition, float opacity)
where TPixelDst : struct, IPixel<TPixelDst>
where TPixelSrc : struct, IPixel<TPixelSrc>
=> source.ApplyProcessor(new DrawImageProcessor<TPixelDst, TPixelSrc>(image, Point.Empty, colorBlending, alphaComposition, opacity));
/// <summary>
/// Draws the given image together with the current one by blending their pixels.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <summary>
/// Draws the given image together with the current one by blending their pixels.
/// </summary>
/// <typeparam name="TPixelDst">The pixel format of the destination image.</typeparam>
/// <typeparam name="TPixelSrc">The pixel format of the source image.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="image">The image to blend with the currently processing image.</param>
/// <param name="image">The image to blend with the currently processing image.</param>
/// <param name="options">The options, including the blending type and blending amount.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> DrawImage<TPixel>(this IImageProcessingContext<TPixel> source, Image<TPixel> image, GraphicsOptions options)
where TPixel : struct, IPixel<TPixel>
=> source.ApplyProcessor(new DrawImageProcessor<TPixel>(image, Point.Empty, options.ColorBlendingMode, options.AlphaCompositionMode, options.BlendPercentage));
/// <returns>The <see cref="Image{TPixelDst}"/>.</returns>
public static IImageProcessingContext<TPixelDst> DrawImage<TPixelDst, TPixelSrc>(this IImageProcessingContext<TPixelDst> source, Image<TPixelSrc> image, GraphicsOptions options)
where TPixelDst : struct, IPixel<TPixelDst>
where TPixelSrc : struct, IPixel<TPixelSrc>
=> source.ApplyProcessor(new DrawImageProcessor<TPixelDst, TPixelSrc>(image, Point.Empty, options.ColorBlendingMode, options.AlphaCompositionMode, options.BlendPercentage));
/// <summary>
/// Draws the given image together with the current one by blending their pixels.
/// <summary>
/// Draws the given image together with the current one by blending their pixels.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <typeparam name="TPixelDst">The pixel format of the destination image.</typeparam>
/// <typeparam name="TPixelSrc">The pixel format of the source image.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="image">The image to blend with the currently processing image.</param>
/// <param name="location">The location to draw the blended image.</param>
/// <param name="location">The location to draw the blended image.</param>
/// <param name="opacity">The opacity of the image to blend. Must be between 0 and 1.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> DrawImage<TPixel>(this IImageProcessingContext<TPixel> source, Image<TPixel> image, Point location, float opacity)
where TPixel : struct, IPixel<TPixel>
=> source.ApplyProcessor(new DrawImageProcessor<TPixel>(image, location, GraphicsOptions.Default.ColorBlendingMode, GraphicsOptions.Default.AlphaCompositionMode, opacity));
/// <returns>The <see cref="Image{TPixelDst}"/>.</returns>
public static IImageProcessingContext<TPixelDst> DrawImage<TPixelDst, TPixelSrc>(this IImageProcessingContext<TPixelDst> source, Image<TPixelSrc> image, Point location, float opacity)
where TPixelDst : struct, IPixel<TPixelDst>
where TPixelSrc : struct, IPixel<TPixelSrc>
=> source.ApplyProcessor(new DrawImageProcessor<TPixelDst, TPixelSrc>(image, location, GraphicsOptions.Default.ColorBlendingMode, GraphicsOptions.Default.AlphaCompositionMode, opacity));
/// <summary>
/// Draws the given image together with the current one by blending their pixels.
/// <summary>
/// Draws the given image together with the current one by blending their pixels.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <typeparam name="TPixelDst">The pixel format of the destination image.</typeparam>
/// <typeparam name="TPixelSrc">The pixel format of the source image.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="image">The image to blend with the currently processing image.</param>
/// <param name="location">The location to draw the blended image.</param>
/// <param name="colorBlending">The color blending to apply.</param>
/// <param name="location">The location to draw the blended image.</param>
/// <param name="colorBlending">The color blending to apply.</param>
/// <param name="opacity">The opacity of the image to blend. Must be between 0 and 1.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> DrawImage<TPixel>(this IImageProcessingContext<TPixel> source, Image<TPixel> image, Point location, PixelColorBlendingMode colorBlending, float opacity)
where TPixel : struct, IPixel<TPixel>
=> source.ApplyProcessor(new DrawImageProcessor<TPixel>(image, location, colorBlending, GraphicsOptions.Default.AlphaCompositionMode, opacity));
/// <returns>The <see cref="Image{TPixelDst}"/>.</returns>
public static IImageProcessingContext<TPixelDst> DrawImage<TPixelDst, TPixelSrc>(this IImageProcessingContext<TPixelDst> source, Image<TPixelSrc> image, Point location, PixelColorBlendingMode colorBlending, float opacity)
where TPixelDst : struct, IPixel<TPixelDst>
where TPixelSrc : struct, IPixel<TPixelSrc>
=> source.ApplyProcessor(new DrawImageProcessor<TPixelDst, TPixelSrc>(image, location, colorBlending, GraphicsOptions.Default.AlphaCompositionMode, opacity));
/// <summary>
/// Draws the given image together with the current one by blending their pixels.
/// <summary>
/// Draws the given image together with the current one by blending their pixels.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <typeparam name="TPixelDst">The pixel format of the destination image.</typeparam>
/// <typeparam name="TPixelSrc">The pixel format of the source image.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="image">The image to blend with the currently processing image.</param>
/// <param name="location">The location to draw the blended image.</param>
/// <param name="location">The location to draw the blended image.</param>
/// <param name="colorBlending">The color blending to apply.</param>
/// <param name="alphaComposition">The alpha composition mode.</param>
/// <param name="alphaComposition">The alpha composition mode.</param>
/// <param name="opacity">The opacity of the image to blend. Must be between 0 and 1.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> DrawImage<TPixel>(this IImageProcessingContext<TPixel> source, Image<TPixel> image, Point location, PixelColorBlendingMode colorBlending, PixelAlphaCompositionMode alphaComposition, float opacity)
where TPixel : struct, IPixel<TPixel>
=> source.ApplyProcessor(new DrawImageProcessor<TPixel>(image, location, colorBlending, alphaComposition, opacity));
/// <returns>The <see cref="Image{TPixelDst}"/>.</returns>
public static IImageProcessingContext<TPixelDst> DrawImage<TPixelDst, TPixelSrc>(this IImageProcessingContext<TPixelDst> source, Image<TPixelSrc> image, Point location, PixelColorBlendingMode colorBlending, PixelAlphaCompositionMode alphaComposition, float opacity)
where TPixelDst : struct, IPixel<TPixelDst>
where TPixelSrc : struct, IPixel<TPixelSrc>
=> source.ApplyProcessor(new DrawImageProcessor<TPixelDst, TPixelSrc>(image, location, colorBlending, alphaComposition, opacity));
/// <summary>
/// Draws the given image together with the current one by blending their pixels.
/// <summary>
/// Draws the given image together with the current one by blending their pixels.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <typeparam name="TPixelDst">The pixel format of the destination image.</typeparam>
/// <typeparam name="TPixelSrc">The pixel format of the source image.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="image">The image to blend with the currently processing image.</param>
/// <param name="location">The location to draw the blended image.</param>
/// <param name="options">The options containing the blend mode and opacity.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> DrawImage<TPixel>(this IImageProcessingContext<TPixel> source, Image<TPixel> image, Point location, GraphicsOptions options)
where TPixel : struct, IPixel<TPixel>
=> source.ApplyProcessor(new DrawImageProcessor<TPixel>(image, location, options.ColorBlendingMode, options.AlphaCompositionMode, options.BlendPercentage));
}
/// <param name="options">The options containing the blend mode and opacity.</param>
/// <returns>The <see cref="Image{TPixelDst}"/>.</returns>
public static IImageProcessingContext<TPixelDst> DrawImage<TPixelDst, TPixelSrc>(this IImageProcessingContext<TPixelDst> source, Image<TPixelSrc> image, Point location, GraphicsOptions options)
where TPixelDst : struct, IPixel<TPixelDst>
where TPixelSrc : struct, IPixel<TPixelSrc>
=> source.ApplyProcessor(new DrawImageProcessor<TPixelDst, TPixelSrc>(image, location, options.ColorBlendingMode, options.AlphaCompositionMode, options.BlendPercentage));
}
}

53
src/ImageSharp.Drawing/Processing/Processors/Drawing/DrawImageProcessor.cs

@ -15,32 +15,34 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing
/// <summary>
/// Combines two images together by blending the pixels.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
internal class DrawImageProcessor<TPixel> : ImageProcessor<TPixel>
where TPixel : struct, IPixel<TPixel>
/// <typeparam name="TPixelDst">The pixel format of destination image.</typeparam>
/// <typeparam name="TPixelSrc">The pixel format os source image.</typeparam>
internal class DrawImageProcessor<TPixelDst, TPixelSrc> : ImageProcessor<TPixelDst>
where TPixelDst : struct, IPixel<TPixelDst>
where TPixelSrc : struct, IPixel<TPixelSrc>
{
/// <summary>
/// Initializes a new instance of the <see cref="DrawImageProcessor{TPixel}"/> class.
/// Initializes a new instance of the <see cref="DrawImageProcessor{TPixelDst, TPixelSrc}"/> class.
/// </summary>
/// <param name="image">The image to blend with the currently processing image.</param>
/// <param name="location">The location to draw the blended image.</param>
/// <param name="colorBlendingMode">The blending mode to use when drawing the image.</param>
/// <param name="alphaCompositionMode">The Alpha blending mode to use when drawing the image.</param>
/// <param name="opacity">The opacity of the image to blend. Must be between 0 and 1.</param>
public DrawImageProcessor(Image<TPixel> image, Point location, PixelColorBlendingMode colorBlendingMode, PixelAlphaCompositionMode alphaCompositionMode, float opacity)
public DrawImageProcessor(Image<TPixelSrc> image, Point location, PixelColorBlendingMode colorBlendingMode, PixelAlphaCompositionMode alphaCompositionMode, float opacity)
{
Guard.MustBeBetweenOrEqualTo(opacity, 0, 1, nameof(opacity));
this.Image = image;
this.Opacity = opacity;
this.Blender = PixelOperations<TPixel>.Instance.GetPixelBlender(colorBlendingMode, alphaCompositionMode);
this.Blender = PixelOperations<TPixelDst>.Instance.GetPixelBlender(colorBlendingMode, alphaCompositionMode);
this.Location = location;
}
/// <summary>
/// Gets the image to blend
/// </summary>
public Image<TPixel> Image { get; }
public Image<TPixelSrc> Image { get; }
/// <summary>
/// Gets the opacity of the image to blend
@ -50,7 +52,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing
/// <summary>
/// Gets the pixel blender
/// </summary>
public PixelBlender<TPixel> Blender { get; }
public PixelBlender<TPixelDst> Blender { get; }
/// <summary>
/// Gets the location to draw the blended image
@ -58,10 +60,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing
public Point Location { get; }
/// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source, Rectangle sourceRectangle, Configuration configuration)
protected override void OnFrameApply(ImageFrame<TPixelDst> source, Rectangle sourceRectangle, Configuration configuration)
{
Image<TPixel> targetImage = this.Image;
PixelBlender<TPixel> blender = this.Blender;
Image<TPixelSrc> targetImage = this.Image;
PixelBlender<TPixelDst> blender = this.Blender;
int locationY = this.Location.Y;
// Align start/end positions.
@ -76,23 +78,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing
int width = maxX - minX;
MemoryAllocator memoryAllocator = this.Image.GetConfiguration().MemoryAllocator;
using (IMemoryOwner<float> amount = memoryAllocator.Allocate<float>(width))
{
amount.GetSpan().Fill(this.Opacity);
ParallelFor.WithConfiguration(
minY,
maxY,
configuration,
y =>
{
Span<TPixel> background = source.GetPixelRowSpan(y).Slice(minX, width);
Span<TPixel> foreground = targetImage.GetPixelRowSpan(y - locationY).Slice(targetX, width);
blender.Blend(memoryAllocator, background, background, foreground, amount.GetSpan());
});
}
MemoryAllocator memoryAllocator = this.Image.GetConfiguration().MemoryAllocator;
ParallelFor.WithConfiguration(
minY,
maxY,
configuration,
y =>
{
Span<TPixelDst> background = source.GetPixelRowSpan(y).Slice(minX, width);
Span<TPixelSrc> foreground = targetImage.GetPixelRowSpan(y - locationY).Slice(targetX, width);
blender.Blend<TPixelSrc>(memoryAllocator, background, background, foreground, this.Opacity);
});
}
}
}

2
src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLchuv.cs

@ -144,7 +144,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieLchuv"/></returns>
public CieLchuv ToCieLchuv(Rgb color)
public CieLchuv ToCieLchuv(in Rgb color)
{
CieXyz xyzColor = this.ToCieXyz(color);
return this.ToCieLchuv(xyzColor);

11
src/ImageSharp/Common/Extensions/ComparableExtensions.cs

@ -137,17 +137,6 @@ namespace SixLabors.ImageSharp
return value;
}
/// <summary>
/// Converts an <see cref="int"/> to a <see cref="byte"/> first restricting the value between the
/// minimum and maximum allowable ranges.
/// </summary>
/// <param name="value">The <see cref="int"/> this method extends.</param>
/// <returns>The <see cref="byte"/></returns>
public static byte ToByte(this int value)
{
return (byte)value.Clamp(0, 255);
}
/// <summary>
/// Converts an <see cref="float"/> to a <see cref="byte"/> first restricting the value between the
/// minimum and maximum allowable ranges.

18
src/ImageSharp/Common/Helpers/ImageMaths.cs

@ -36,10 +36,15 @@ namespace SixLabors.ImageSharp
/// The <see cref="int"/>
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int GetBitsNeededForColorDepth(int colors)
{
return Math.Max(1, (int)Math.Ceiling(Math.Log(colors, 2)));
}
public static int GetBitsNeededForColorDepth(int colors) => Math.Max(1, (int)Math.Ceiling(Math.Log(colors, 2)));
/// <summary>
/// Returns how many colors will be created by the specified number of bits.
/// </summary>
/// <param name="bitDepth">The bit depth.</param>
/// <returns>The <see cref="int"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int GetColorCountForBitDepth(int bitDepth) => 1 << bitDepth;
/// <summary>
/// Implementation of 1D Gaussian G(x) function
@ -132,10 +137,7 @@ namespace SixLabors.ImageSharp
/// The bounding <see cref="Rectangle"/>.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Rectangle GetBoundingRectangle(Point topLeft, Point bottomRight)
{
return new Rectangle(topLeft.X, topLeft.Y, bottomRight.X - topLeft.X, bottomRight.Y - topLeft.Y);
}
public static Rectangle GetBoundingRectangle(Point topLeft, Point bottomRight) => new Rectangle(topLeft.X, topLeft.Y, bottomRight.X - topLeft.X, bottomRight.Y - topLeft.Y);
/// <summary>
/// Finds the bounding rectangle based on the first instance of any color component other

2
src/ImageSharp/Configuration.cs

@ -125,7 +125,7 @@ namespace SixLabors.ImageSharp
/// Creates a shallow copy of the <see cref="Configuration"/>
/// </summary>
/// <returns>A new configuration instance</returns>
public Configuration ShallowCopy()
public Configuration Clone()
{
return new Configuration
{

6
src/ImageSharp/Formats/Bmp/BmpBitsPerPixel.cs

@ -6,16 +6,16 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// <summary>
/// Enumerates the available bits per pixel for bitmap.
/// </summary>
public enum BmpBitsPerPixel
public enum BmpBitsPerPixel : short
{
/// <summary>
/// 24 bits per pixel. Each pixel consists of 3 bytes.
/// </summary>
Pixel24 = 3,
Pixel24 = 24,
/// <summary>
/// 32 bits per pixel. Each pixel consists of 4 bytes.
/// </summary>
Pixel32 = 4
Pixel32 = 32
}
}

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

@ -9,11 +9,11 @@ namespace SixLabors.ImageSharp.Formats.Bmp
public sealed class BmpConfigurationModule : IConfigurationModule
{
/// <inheritdoc/>
public void Configure(Configuration config)
public void Configure(Configuration configuration)
{
config.ImageFormatsManager.SetEncoder(ImageFormats.Bmp, new BmpEncoder());
config.ImageFormatsManager.SetDecoder(ImageFormats.Bmp, new BmpDecoder());
config.ImageFormatsManager.AddImageFormatDetector(new BmpImageFormatDetector());
configuration.ImageFormatsManager.SetEncoder(BmpFormat.Instance, new BmpEncoder());
configuration.ImageFormatsManager.SetDecoder(BmpFormat.Instance, new BmpDecoder());
configuration.ImageFormatsManager.AddImageFormatDetector(new BmpImageFormatDetector());
}
}
}

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

@ -175,10 +175,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// <param name="inverted">Whether the bitmap is inverted.</param>
/// <returns>The <see cref="int"/> representing the inverted value.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int Invert(int y, int height, bool inverted)
{
return (!inverted) ? height - y - 1 : y;
}
private static int Invert(int y, int height, bool inverted) => (!inverted) ? height - y - 1 : y;
/// <summary>
/// Calculates the amount of bytes to pad a row.
@ -206,10 +203,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// <param name="value">The masked and shifted value</param>
/// <returns>The <see cref="byte"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static byte GetBytesFrom5BitValue(int value)
{
return (byte)((value << 3) | (value >> 2));
}
private static byte GetBytesFrom5BitValue(int value) => (byte)((value << 3) | (value >> 2));
/// <summary>
/// Looks up color values and builds the image from de-compressed RLE8 data.
@ -524,8 +518,10 @@ namespace SixLabors.ImageSharp.Formats.Bmp
}
// Resolution is stored in PPM.
var meta = new ImageMetaData();
meta.ResolutionUnits = PixelResolutionUnit.PixelsPerMeter;
var meta = new ImageMetaData
{
ResolutionUnits = PixelResolutionUnit.PixelsPerMeter
};
if (this.infoHeader.XPelsPerMeter > 0 && this.infoHeader.YPelsPerMeter > 0)
{
meta.HorizontalResolution = this.infoHeader.XPelsPerMeter;
@ -540,6 +536,16 @@ namespace SixLabors.ImageSharp.Formats.Bmp
this.metaData = meta;
short bitsPerPixel = this.infoHeader.BitsPerPixel;
var bmpMetaData = this.metaData.GetFormatMetaData(BmpFormat.Instance);
// We can only encode at these bit rates so far.
if (bitsPerPixel.Equals((short)BmpBitsPerPixel.Pixel24)
|| bitsPerPixel.Equals((short)BmpBitsPerPixel.Pixel32))
{
bmpMetaData.BitsPerPixel = (BmpBitsPerPixel)bitsPerPixel;
}
// skip the remaining header because we can't read those parts
this.stream.Skip(skipAmount);
}
@ -585,11 +591,11 @@ namespace SixLabors.ImageSharp.Formats.Bmp
if (this.infoHeader.ClrUsed == 0)
{
if (this.infoHeader.BitsPerPixel == 1 ||
this.infoHeader.BitsPerPixel == 4 ||
this.infoHeader.BitsPerPixel == 8)
if (this.infoHeader.BitsPerPixel == 1
|| this.infoHeader.BitsPerPixel == 4
|| this.infoHeader.BitsPerPixel == 8)
{
colorMapSize = (int)Math.Pow(2, this.infoHeader.BitsPerPixel) * 4;
colorMapSize = ImageMaths.GetColorCountForBitDepth(this.infoHeader.BitsPerPixel) * 4;
}
}
else

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

@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// <summary>
/// Gets or sets the number of bits per pixel.
/// </summary>
public BmpBitsPerPixel BitsPerPixel { get; set; } = BmpBitsPerPixel.Pixel24;
public BmpBitsPerPixel? BitsPerPixel { get; set; }
/// <inheritdoc/>
public void Encode<TPixel>(Image<TPixel> image, Stream stream)

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

@ -21,10 +21,10 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// </summary>
private int padding;
private readonly BmpBitsPerPixel bitsPerPixel;
private readonly MemoryAllocator memoryAllocator;
private BmpBitsPerPixel? bitsPerPixel;
/// <summary>
/// Initializes a new instance of the <see cref="BmpEncoderCore"/> class.
/// </summary>
@ -48,37 +48,39 @@ namespace SixLabors.ImageSharp.Formats.Bmp
Guard.NotNull(image, nameof(image));
Guard.NotNull(stream, nameof(stream));
// Cast to int will get the bytes per pixel
short bpp = (short)(8 * (int)this.bitsPerPixel);
ImageMetaData metaData = image.MetaData;
BmpMetaData bmpMetaData = metaData.GetFormatMetaData(BmpFormat.Instance);
this.bitsPerPixel = this.bitsPerPixel ?? bmpMetaData.BitsPerPixel;
short bpp = (short)this.bitsPerPixel;
int bytesPerLine = 4 * (((image.Width * bpp) + 31) / 32);
this.padding = bytesPerLine - (image.Width * (int)this.bitsPerPixel);
this.padding = bytesPerLine - (int)(image.Width * (bpp / 8F));
// Set Resolution.
ImageMetaData meta = image.MetaData;
int hResolution = 0;
int vResolution = 0;
if (meta.ResolutionUnits != PixelResolutionUnit.AspectRatio)
if (metaData.ResolutionUnits != PixelResolutionUnit.AspectRatio)
{
if (meta.HorizontalResolution > 0 && meta.VerticalResolution > 0)
if (metaData.HorizontalResolution > 0 && metaData.VerticalResolution > 0)
{
switch (meta.ResolutionUnits)
switch (metaData.ResolutionUnits)
{
case PixelResolutionUnit.PixelsPerInch:
hResolution = (int)Math.Round(UnitConverter.InchToMeter(meta.HorizontalResolution));
vResolution = (int)Math.Round(UnitConverter.InchToMeter(meta.VerticalResolution));
hResolution = (int)Math.Round(UnitConverter.InchToMeter(metaData.HorizontalResolution));
vResolution = (int)Math.Round(UnitConverter.InchToMeter(metaData.VerticalResolution));
break;
case PixelResolutionUnit.PixelsPerCentimeter:
hResolution = (int)Math.Round(UnitConverter.CmToMeter(meta.HorizontalResolution));
vResolution = (int)Math.Round(UnitConverter.CmToMeter(meta.VerticalResolution));
hResolution = (int)Math.Round(UnitConverter.CmToMeter(metaData.HorizontalResolution));
vResolution = (int)Math.Round(UnitConverter.CmToMeter(metaData.VerticalResolution));
break;
case PixelResolutionUnit.PixelsPerMeter:
hResolution = (int)Math.Round(meta.HorizontalResolution);
vResolution = (int)Math.Round(meta.VerticalResolution);
hResolution = (int)Math.Round(metaData.HorizontalResolution);
vResolution = (int)Math.Round(metaData.VerticalResolution);
break;
}
@ -145,10 +147,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
}
}
private IManagedByteBuffer AllocateRow(int width, int bytesPerPixel)
{
return this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, bytesPerPixel, this.padding);
}
private IManagedByteBuffer AllocateRow(int width, int bytesPerPixel) => this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, bytesPerPixel, this.padding);
/// <summary>
/// Writes the 32bit color palette to the stream.

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

@ -8,8 +8,17 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// <summary>
/// Registers the image encoders, decoders and mime type detectors for the bmp format.
/// </summary>
internal sealed class BmpFormat : IImageFormat
public sealed class BmpFormat : IImageFormat<BmpMetaData>
{
private BmpFormat()
{
}
/// <summary>
/// Gets the current instance.
/// </summary>
public static BmpFormat Instance { get; } = new BmpFormat();
/// <inheritdoc/>
public string Name => "BMP";
@ -21,5 +30,8 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// <inheritdoc/>
public IEnumerable<string> FileExtensions => BmpConstants.FileExtensions;
/// <inheritdoc/>
public BmpMetaData CreateDefaultFormatMetaData() => new BmpMetaData();
}
}

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

@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// <inheritdoc/>
public IImageFormat DetectFormat(ReadOnlySpan<byte> header)
{
return this.IsSupportedFileFormat(header) ? ImageFormats.Bmp : null;
return this.IsSupportedFileFormat(header) ? BmpFormat.Instance : null;
}
private bool IsSupportedFileFormat(ReadOnlySpan<byte> header)

34
src/ImageSharp/Formats/Bmp/BmpMetaData.cs

@ -0,0 +1,34 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Bmp
{
/// <summary>
/// Provides Bmp specific metadata information for the image.
/// </summary>
public class BmpMetaData : IDeepCloneable
{
/// <summary>
/// Initializes a new instance of the <see cref="BmpMetaData"/> class.
/// </summary>
public BmpMetaData()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="BmpMetaData"/> class.
/// </summary>
/// <param name="other">The metadata to create an instance from.</param>
private BmpMetaData(BmpMetaData other) => this.BitsPerPixel = other.BitsPerPixel;
/// <summary>
/// Gets or sets the number of bits per pixel.
/// </summary>
public BmpBitsPerPixel BitsPerPixel { get; set; } = BmpBitsPerPixel.Pixel24;
/// <inheritdoc/>
public IDeepCloneable DeepClone() => new BmpMetaData(this);
// TODO: Colors used once we support encoding palette bmps.
}
}

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

@ -12,6 +12,6 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// <summary>
/// Gets the number of bits per pixel.
/// </summary>
BmpBitsPerPixel BitsPerPixel { get; }
BmpBitsPerPixel? BitsPerPixel { get; }
}
}

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

@ -34,6 +34,6 @@ namespace SixLabors.ImageSharp
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
public static void SaveAsBmp<TPixel>(this Image<TPixel> source, Stream stream, BmpEncoder encoder)
where TPixel : struct, IPixel<TPixel>
=> source.Save(stream, encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(ImageFormats.Bmp));
=> source.Save(stream, encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(BmpFormat.Instance));
}
}

2
src/ImageSharp/Formats/Gif/GifColorTableMode.cs

@ -4,7 +4,7 @@
namespace SixLabors.ImageSharp.Formats.Gif
{
/// <summary>
/// Provides enumeration for the available Gif color table modes.
/// Provides enumeration for the available color table modes.
/// </summary>
public enum GifColorTableMode
{

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

@ -9,12 +9,11 @@ namespace SixLabors.ImageSharp.Formats.Gif
public sealed class GifConfigurationModule : IConfigurationModule
{
/// <inheritdoc/>
public void Configure(Configuration config)
public void Configure(Configuration configuration)
{
config.ImageFormatsManager.SetEncoder(ImageFormats.Gif, new GifEncoder());
config.ImageFormatsManager.SetDecoder(ImageFormats.Gif, new GifDecoder());
config.ImageFormatsManager.AddImageFormatDetector(new GifImageFormatDetector());
configuration.ImageFormatsManager.SetEncoder(GifFormat.Instance, new GifEncoder());
configuration.ImageFormatsManager.SetDecoder(GifFormat.Instance, new GifDecoder());
configuration.ImageFormatsManager.AddImageFormatDetector(new GifImageFormatDetector());
}
}
}

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

@ -41,20 +41,25 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// </summary>
public const byte ApplicationExtensionLabel = 0xFF;
/// <summary>
/// The application block size.
/// </summary>
public const byte ApplicationBlockSize = 11;
/// <summary>
/// The application identification.
/// </summary>
public const string ApplicationIdentification = "NETSCAPE2.0";
public const string NetscapeApplicationIdentification = "NETSCAPE2.0";
/// <summary>
/// The ASCII encoded application identification bytes.
/// </summary>
internal static readonly byte[] ApplicationIdentificationBytes = Encoding.UTF8.GetBytes(ApplicationIdentification);
internal static readonly byte[] NetscapeApplicationIdentificationBytes = Encoding.UTF8.GetBytes(NetscapeApplicationIdentification);
/// <summary>
/// The application block size.
/// The Netscape looping application sub block size.
/// </summary>
public const byte ApplicationBlockSize = 11;
public const byte NetscapeLoopingSubBlockSize = 3;
/// <summary>
/// The comment label.

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

@ -3,6 +3,7 @@
using System.IO;
using System.Text;
using SixLabors.ImageSharp.MetaData;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Gif

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

@ -56,10 +56,20 @@ namespace SixLabors.ImageSharp.Formats.Gif
private GifGraphicControlExtension graphicsControlExtension;
/// <summary>
/// The metadata
/// The image desciptor.
/// </summary>
private GifImageDescriptor imageDescriptor;
/// <summary>
/// The abstract metadata.
/// </summary>
private ImageMetaData metaData;
/// <summary>
/// The gif specific metadata.
/// </summary>
private GifMetaData gifMetaData;
/// <summary>
/// Initializes a new instance of the <see cref="GifDecoderCore"/> class.
/// </summary>
@ -120,8 +130,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
}
else if (nextFlag == GifConstants.ExtensionIntroducer)
{
int label = stream.ReadByte();
switch (label)
switch (stream.ReadByte())
{
case GifConstants.GraphicControlLabel:
this.ReadGraphicalControlExtension();
@ -130,11 +139,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
this.ReadComments();
break;
case GifConstants.ApplicationExtensionLabel:
// The application extension length should be 11 but we've got test images that incorrectly
// set this to 252.
int appLength = stream.ReadByte();
this.Skip(appLength); // No need to read.
this.ReadApplicationExtension();
break;
case GifConstants.PlainTextLabel:
int plainLength = stream.ReadByte();
@ -178,13 +183,11 @@ namespace SixLabors.ImageSharp.Formats.Gif
{
if (nextFlag == GifConstants.ImageLabel)
{
// Skip image block
this.Skip(0);
this.ReadImageDescriptor();
}
else if (nextFlag == GifConstants.ExtensionIntroducer)
{
int label = stream.ReadByte();
switch (label)
switch (stream.ReadByte())
{
case GifConstants.GraphicControlLabel:
@ -195,11 +198,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
this.ReadComments();
break;
case GifConstants.ApplicationExtensionLabel:
// The application extension length should be 11 but we've got test images that incorrectly
// set this to 252.
int appLength = stream.ReadByte();
this.Skip(appLength); // No need to read.
this.ReadApplicationExtension();
break;
case GifConstants.PlainTextLabel:
int plainLength = stream.ReadByte();
@ -224,7 +223,11 @@ namespace SixLabors.ImageSharp.Formats.Gif
this.globalColorTable?.Dispose();
}
return new ImageInfo(new PixelTypeInfo(this.logicalScreenDescriptor.BitsPerPixel), this.logicalScreenDescriptor.Width, this.logicalScreenDescriptor.Height, this.metaData);
return new ImageInfo(
new PixelTypeInfo(this.logicalScreenDescriptor.BitsPerPixel),
this.logicalScreenDescriptor.Width,
this.logicalScreenDescriptor.Height,
this.metaData);
}
/// <summary>
@ -238,14 +241,13 @@ namespace SixLabors.ImageSharp.Formats.Gif
}
/// <summary>
/// Reads the image descriptor
/// Reads the image descriptor.
/// </summary>
/// <returns><see cref="GifImageDescriptor"/></returns>
private GifImageDescriptor ReadImageDescriptor()
private void ReadImageDescriptor()
{
this.stream.Read(this.buffer, 0, 9);
return GifImageDescriptor.Parse(this.buffer);
this.imageDescriptor = GifImageDescriptor.Parse(this.buffer);
}
/// <summary>
@ -258,6 +260,41 @@ namespace SixLabors.ImageSharp.Formats.Gif
this.logicalScreenDescriptor = GifLogicalScreenDescriptor.Parse(this.buffer);
}
/// <summary>
/// Reads the application extension block parsing any animation information
/// if present.
/// </summary>
private void ReadApplicationExtension()
{
int appLength = this.stream.ReadByte();
// If the length is 11 then it's a valid extension and most likely
// a NETSCAPE or ANIMEXTS extension. We want the loop count from this.
if (appLength == GifConstants.ApplicationBlockSize)
{
this.stream.Skip(appLength);
int subBlockSize = this.stream.ReadByte();
// TODO: There's also a NETSCAPE buffer extension.
// http://www.vurdalakov.net/misc/gif/netscape-buffering-application-extension
if (subBlockSize == GifConstants.NetscapeLoopingSubBlockSize)
{
this.stream.Read(this.buffer, 0, GifConstants.NetscapeLoopingSubBlockSize);
this.gifMetaData.RepeatCount = GifNetscapeLoopingApplicationExtension.Parse(this.buffer.AsSpan(1)).RepeatCount;
this.stream.Skip(1); // Skip the terminator.
return;
}
// Could be XMP or something else not supported yet.
// Back up and skip.
this.stream.Position -= appLength + 1;
this.Skip(appLength);
return;
}
this.Skip(appLength); // Not supported by any known decoder.
}
/// <summary>
/// Skips the designated number of bytes in the stream.
/// </summary>
@ -312,25 +349,25 @@ namespace SixLabors.ImageSharp.Formats.Gif
private void ReadFrame<TPixel>(ref Image<TPixel> image, ref ImageFrame<TPixel> previousFrame)
where TPixel : struct, IPixel<TPixel>
{
GifImageDescriptor imageDescriptor = this.ReadImageDescriptor();
this.ReadImageDescriptor();
IManagedByteBuffer localColorTable = null;
IManagedByteBuffer indices = null;
try
{
// Determine the color table for this frame. If there is a local one, use it otherwise use the global color table.
if (imageDescriptor.LocalColorTableFlag)
if (this.imageDescriptor.LocalColorTableFlag)
{
int length = imageDescriptor.LocalColorTableSize * 3;
int length = this.imageDescriptor.LocalColorTableSize * 3;
localColorTable = this.configuration.MemoryAllocator.AllocateManagedByteBuffer(length, AllocationOptions.Clean);
this.stream.Read(localColorTable.Array, 0, length);
}
indices = this.configuration.MemoryAllocator.AllocateManagedByteBuffer(imageDescriptor.Width * imageDescriptor.Height, AllocationOptions.Clean);
indices = this.configuration.MemoryAllocator.AllocateManagedByteBuffer(this.imageDescriptor.Width * this.imageDescriptor.Height, AllocationOptions.Clean);
this.ReadFrameIndices(imageDescriptor, indices.GetSpan());
this.ReadFrameIndices(this.imageDescriptor, indices.GetSpan());
ReadOnlySpan<Rgb24> colorTable = MemoryMarshal.Cast<byte, Rgb24>((localColorTable ?? this.globalColorTable).GetSpan());
this.ReadFrameColors(ref image, ref previousFrame, indices.GetSpan(), colorTable, imageDescriptor);
this.ReadFrameColors(ref image, ref previousFrame, indices.GetSpan(), colorTable, this.imageDescriptor);
// Skip any remaining blocks
this.Skip(0);
@ -388,7 +425,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
}
else
{
if (this.graphicsControlExtension.DisposalMethod == DisposalMethod.RestoreToPrevious)
if (this.graphicsControlExtension.DisposalMethod == GifDisposalMethod.RestoreToPrevious)
{
prevFrame = previousFrame;
}
@ -471,7 +508,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
previousFrame = currentFrame ?? image.Frames.RootFrame;
if (this.graphicsControlExtension.DisposalMethod == DisposalMethod.RestoreToBackground)
if (this.graphicsControlExtension.DisposalMethod == GifDisposalMethod.RestoreToBackground)
{
this.restoreArea = new Rectangle(descriptor.Left, descriptor.Top, descriptor.Width, descriptor.Height);
}
@ -503,12 +540,25 @@ namespace SixLabors.ImageSharp.Formats.Gif
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void SetFrameMetaData(ImageFrameMetaData meta)
{
GifFrameMetaData gifMeta = meta.GetFormatMetaData(GifFormat.Instance);
if (this.graphicsControlExtension.DelayTime > 0)
{
meta.FrameDelay = this.graphicsControlExtension.DelayTime;
gifMeta.FrameDelay = this.graphicsControlExtension.DelayTime;
}
// Frames can either use the global table or their own local table.
if (this.logicalScreenDescriptor.GlobalColorTableFlag
&& this.logicalScreenDescriptor.GlobalColorTableSize > 0)
{
gifMeta.ColorTableLength = this.logicalScreenDescriptor.GlobalColorTableSize;
}
else if (this.imageDescriptor.LocalColorTableFlag
&& this.imageDescriptor.LocalColorTableSize > 0)
{
gifMeta.ColorTableLength = this.imageDescriptor.LocalColorTableSize;
}
meta.DisposalMethod = this.graphicsControlExtension.DisposalMethod;
gifMeta.DisposalMethod = this.graphicsControlExtension.DisposalMethod;
}
/// <summary>
@ -552,10 +602,15 @@ namespace SixLabors.ImageSharp.Formats.Gif
}
this.metaData = meta;
this.gifMetaData = meta.GetFormatMetaData(GifFormat.Instance);
this.gifMetaData.ColorTableMode = this.logicalScreenDescriptor.GlobalColorTableFlag
? GifColorTableMode.Global
: GifColorTableMode.Local;
if (this.logicalScreenDescriptor.GlobalColorTableFlag)
{
int globalColorTableLength = this.logicalScreenDescriptor.GlobalColorTableSize * 3;
this.gifMetaData.GlobalColorTableLength = globalColorTableLength;
this.globalColorTable = this.MemoryAllocator.AllocateManagedByteBuffer(globalColorTableLength, AllocationOptions.Clean);

2
src/ImageSharp/Formats/Gif/DisposalMethod.cs → src/ImageSharp/Formats/Gif/GifDisposalMethod.cs

@ -8,7 +8,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// in an animation sequence.
/// <see href="http://www.w3.org/Graphics/GIF/spec-gif89a.txt"/> section 23
/// </summary>
public enum DisposalMethod
public enum GifDisposalMethod
{
/// <summary>
/// No disposal specified.

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

@ -14,11 +14,6 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// </summary>
public sealed class GifEncoder : IImageEncoder, IGifEncoderOptions
{
/// <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;
/// <summary>
/// Gets or sets the encoding that should be used when writing comments.
/// </summary>
@ -33,7 +28,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <summary>
/// Gets or sets the color table mode: Global or local.
/// </summary>
public GifColorTableMode ColorTableMode { get; set; }
public GifColorTableMode? ColorTableMode { get; set; }
/// <inheritdoc/>
public void Encode<TPixel>(Image<TPixel> image, Stream stream)

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

@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers.Binary;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@ -44,17 +43,17 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <summary>
/// The color table mode: Global or local.
/// </summary>
private readonly GifColorTableMode colorTableMode;
private GifColorTableMode? colorTableMode;
/// <summary>
/// A flag indicating whether to ingore the metadata when writing the image.
/// The number of bits requires to store the color palette.
/// </summary>
private readonly bool ignoreMetadata;
private int bitDepth;
/// <summary>
/// The number of bits requires to store the color palette.
/// Gif specific meta data.
/// </summary>
private int bitDepth;
private GifMetaData gifMetaData;
/// <summary>
/// Initializes a new instance of the <see cref="GifEncoderCore"/> class.
@ -67,7 +66,6 @@ namespace SixLabors.ImageSharp.Formats.Gif
this.textEncoding = options.TextEncoding ?? GifConstants.DefaultEncoding;
this.quantizer = options.Quantizer;
this.colorTableMode = options.ColorTableMode;
this.ignoreMetadata = options.IgnoreMetadata;
}
/// <summary>
@ -82,6 +80,11 @@ namespace SixLabors.ImageSharp.Formats.Gif
Guard.NotNull(image, nameof(image));
Guard.NotNull(stream, nameof(stream));
ImageMetaData metaData = image.MetaData;
this.gifMetaData = metaData.GetFormatMetaData(GifFormat.Instance);
this.colorTableMode = this.colorTableMode ?? this.gifMetaData.ColorTableMode;
bool useGlobalTable = this.colorTableMode.Equals(GifColorTableMode.Global);
// Quantize the image returning a palette.
QuantizedFrame<TPixel> quantized =
this.quantizer.CreateFrameQuantizer<TPixel>().QuantizeFrame(image.Frames.RootFrame);
@ -94,8 +97,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
// Write the LSD.
int index = this.GetTransparentIndex(quantized);
bool useGlobalTable = this.colorTableMode.Equals(GifColorTableMode.Global);
this.WriteLogicalScreenDescriptor(image, index, useGlobalTable, stream);
this.WriteLogicalScreenDescriptor(metaData, image.Width, image.Height, index, useGlobalTable, stream);
if (useGlobalTable)
{
@ -103,12 +105,12 @@ namespace SixLabors.ImageSharp.Formats.Gif
}
// Write the comments.
this.WriteComments(image.MetaData, stream);
this.WriteComments(metaData, stream);
// Write application extension to allow additional frames.
if (image.Frames.Count > 1)
{
this.WriteApplicationExtension(stream, image.MetaData.RepeatCount);
this.WriteApplicationExtension(stream, this.gifMetaData.RepeatCount);
}
if (useGlobalTable)
@ -136,8 +138,9 @@ namespace SixLabors.ImageSharp.Formats.Gif
for (int i = 0; i < image.Frames.Count; i++)
{
ImageFrame<TPixel> frame = image.Frames[i];
this.WriteGraphicalControlExtension(frame.MetaData, transparencyIndex, stream);
ImageFrameMetaData metaData = frame.MetaData;
GifFrameMetaData frameMetaData = metaData.GetFormatMetaData(GifFormat.Instance);
this.WriteGraphicalControlExtension(frameMetaData, transparencyIndex, stream);
this.WriteImageDescriptor(frame, false, stream);
if (i == 0)
@ -146,7 +149,8 @@ namespace SixLabors.ImageSharp.Formats.Gif
}
else
{
using (QuantizedFrame<TPixel> paletteQuantized = palleteQuantizer.CreateFrameQuantizer(() => quantized.Palette).QuantizeFrame(frame))
using (QuantizedFrame<TPixel> paletteQuantized
= palleteQuantizer.CreateFrameQuantizer(() => quantized.Palette).QuantizeFrame(frame))
{
this.WriteImageData(paletteQuantized, stream);
}
@ -157,20 +161,37 @@ namespace SixLabors.ImageSharp.Formats.Gif
private void EncodeLocal<TPixel>(Image<TPixel> image, QuantizedFrame<TPixel> quantized, Stream stream)
where TPixel : struct, IPixel<TPixel>
{
ImageFrame<TPixel> previousFrame = null;
GifFrameMetaData previousMeta = null;
foreach (ImageFrame<TPixel> frame in image.Frames)
{
ImageFrameMetaData metaData = frame.MetaData;
GifFrameMetaData frameMetaData = metaData.GetFormatMetaData(GifFormat.Instance);
if (quantized is null)
{
quantized = this.quantizer.CreateFrameQuantizer<TPixel>().QuantizeFrame(frame);
// Allow each frame to be encoded at whatever color depth the frame designates if set.
if (previousFrame != null
&& previousMeta.ColorTableLength != frameMetaData.ColorTableLength
&& frameMetaData.ColorTableLength > 0)
{
quantized = this.quantizer.CreateFrameQuantizer<TPixel>(frameMetaData.ColorTableLength).QuantizeFrame(frame);
}
else
{
quantized = this.quantizer.CreateFrameQuantizer<TPixel>().QuantizeFrame(frame);
}
}
this.WriteGraphicalControlExtension(frame.MetaData, this.GetTransparentIndex(quantized), stream);
this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(quantized.Palette.Length).Clamp(1, 8);
this.WriteGraphicalControlExtension(frameMetaData, this.GetTransparentIndex(quantized), stream);
this.WriteImageDescriptor(frame, true, stream);
this.WriteColorTable(quantized, stream);
this.WriteImageData(quantized, stream);
quantized?.Dispose();
quantized = null; // So next frame can regenerate it
previousFrame = frame;
previousMeta = frameMetaData;
}
}
@ -210,21 +231,24 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// </summary>
/// <param name="stream">The stream to write to.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void WriteHeader(Stream stream)
{
stream.Write(GifConstants.MagicNumber, 0, GifConstants.MagicNumber.Length);
}
private void WriteHeader(Stream stream) => stream.Write(GifConstants.MagicNumber, 0, GifConstants.MagicNumber.Length);
/// <summary>
/// Writes the logical screen descriptor to the stream.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The image to encode.</param>
/// <param name="metaData">The image metadata.</param>
/// <param name="width">The image width.</param>
/// <param name="height">The image height.</param>
/// <param name="transparencyIndex">The transparency index to set the default background index to.</param>
/// <param name="useGlobalTable">Whether to use a global or local color table.</param>
/// <param name="stream">The stream to write to.</param>
private void WriteLogicalScreenDescriptor<TPixel>(Image<TPixel> image, int transparencyIndex, bool useGlobalTable, Stream stream)
where TPixel : struct, IPixel<TPixel>
private void WriteLogicalScreenDescriptor(
ImageMetaData metaData,
int width,
int height,
int transparencyIndex,
bool useGlobalTable,
Stream stream)
{
byte packedValue = GifLogicalScreenDescriptor.GetPackedValue(useGlobalTable, this.bitDepth - 1, false, this.bitDepth - 1);
@ -237,13 +261,12 @@ namespace SixLabors.ImageSharp.Formats.Gif
// 1..255 - Value used in the computation.
//
// Aspect Ratio = (Pixel Aspect Ratio + 15) / 64
ImageMetaData meta = image.MetaData;
byte ratio = 0;
if (meta.ResolutionUnits == PixelResolutionUnit.AspectRatio)
if (metaData.ResolutionUnits == PixelResolutionUnit.AspectRatio)
{
double hr = meta.HorizontalResolution;
double vr = meta.VerticalResolution;
double hr = metaData.HorizontalResolution;
double vr = metaData.VerticalResolution;
if (hr != vr)
{
if (hr > vr)
@ -258,8 +281,8 @@ namespace SixLabors.ImageSharp.Formats.Gif
}
var descriptor = new GifLogicalScreenDescriptor(
width: (ushort)image.Width,
height: (ushort)image.Height,
width: (ushort)width,
height: (ushort)height,
packed: packedValue,
backgroundColorIndex: unchecked((byte)transparencyIndex),
ratio);
@ -279,25 +302,8 @@ namespace SixLabors.ImageSharp.Formats.Gif
// Application Extension Header
if (repeatCount != 1)
{
this.buffer[0] = GifConstants.ExtensionIntroducer;
this.buffer[1] = GifConstants.ApplicationExtensionLabel;
this.buffer[2] = GifConstants.ApplicationBlockSize;
// Write NETSCAPE2.0
GifConstants.ApplicationIdentificationBytes.AsSpan().CopyTo(this.buffer.AsSpan(3, 11));
// Application Data ----
this.buffer[14] = 3; // Application block length
this.buffer[15] = 1; // Data sub-block index (always 1)
// 0 means loop indefinitely. Count is set as play n + 1 times.
repeatCount = (ushort)Math.Max(0, repeatCount - 1);
BinaryPrimitives.WriteUInt16LittleEndian(this.buffer.AsSpan(16, 2), repeatCount); // Repeat count for images.
this.buffer[18] = GifConstants.Terminator; // Terminator
stream.Write(this.buffer, 0, 19);
var loopingExtension = new GifNetscapeLoopingApplicationExtension(repeatCount);
this.WriteExtension(loopingExtension, stream);
}
}
@ -308,11 +314,6 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <param name="stream">The stream to write to.</param>
private void WriteComments(ImageMetaData metadata, Stream stream)
{
if (this.ignoreMetadata)
{
return;
}
if (!metadata.TryGetProperty(GifConstants.Comments, out ImageProperty property) || string.IsNullOrEmpty(property.Value))
{
return;
@ -337,7 +338,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <param name="metaData">The metadata of the image or frame.</param>
/// <param name="transparencyIndex">The index of the color in the color palette to make transparent.</param>
/// <param name="stream">The stream to write to.</param>
private void WriteGraphicalControlExtension(ImageFrameMetaData metaData, int transparencyIndex, Stream stream)
private void WriteGraphicalControlExtension(GifFrameMetaData metaData, int transparencyIndex, Stream stream)
{
byte packedValue = GifGraphicControlExtension.GetPackedValue(
disposalMethod: metaData.DisposalMethod,
@ -382,7 +383,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
localColorTableFlag: hasColorTable,
interfaceFlag: false,
sortFlag: false,
localColorTableSize: (byte)this.bitDepth);
localColorTableSize: this.bitDepth - 1);
var descriptor = new GifImageDescriptor(
left: 0,
@ -407,7 +408,8 @@ namespace SixLabors.ImageSharp.Formats.Gif
{
int pixelCount = image.Palette.Length;
int colorTableLength = (int)Math.Pow(2, this.bitDepth) * 3; // The maximium number of colors for the bit depth
// The maximium number of colors for the bit depth
int colorTableLength = ImageMaths.GetColorCountForBitDepth(this.bitDepth) * 3;
Rgb24 rgb = default;
using (IManagedByteBuffer colorTable = this.memoryAllocator.AllocateManagedByteBuffer(colorTableLength))

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

@ -8,8 +8,17 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <summary>
/// Registers the image encoders, decoders and mime type detectors for the gif format.
/// </summary>
internal sealed class GifFormat : IImageFormat
public sealed class GifFormat : IImageFormat<GifMetaData, GifFrameMetaData>
{
private GifFormat()
{
}
/// <summary>
/// Gets the current instance.
/// </summary>
public static GifFormat Instance { get; } = new GifFormat();
/// <inheritdoc/>
public string Name => "GIF";
@ -21,5 +30,11 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <inheritdoc/>
public IEnumerable<string> FileExtensions => GifConstants.FileExtensions;
/// <inheritdoc/>
public GifMetaData CreateDefaultFormatMetaData() => new GifMetaData();
/// <inheritdoc/>
public GifFrameMetaData CreateDefaultFormatFrameMetaData() => new GifFrameMetaData();
}
}

54
src/ImageSharp/Formats/Gif/GifFrameMetaData.cs

@ -0,0 +1,54 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Gif
{
/// <summary>
/// Provides Gif specific metadata information for the image frame.
/// </summary>
public class GifFrameMetaData : IDeepCloneable
{
/// <summary>
/// Initializes a new instance of the <see cref="GifFrameMetaData"/> class.
/// </summary>
public GifFrameMetaData()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="GifFrameMetaData"/> class.
/// </summary>
/// <param name="other">The metadata to create an instance from.</param>
private GifFrameMetaData(GifFrameMetaData other)
{
this.ColorTableLength = other.ColorTableLength;
this.FrameDelay = other.FrameDelay;
this.DisposalMethod = other.DisposalMethod;
}
/// <summary>
/// Gets or sets the length of the color table for paletted images.
/// If not 0, then this field indicates the maximum number of colors to use when quantizing the
/// image frame.
/// </summary>
public int ColorTableLength { get; set; }
/// <summary>
/// Gets or sets the frame delay for animated images.
/// If not 0, when utilized in Gif animation, this field specifies the number of hundredths (1/100) of a second to
/// wait before continuing with the processing of the Data Stream.
/// The clock starts ticking immediately after the graphic is rendered.
/// </summary>
public int FrameDelay { get; set; }
/// <summary>
/// Gets or sets the disposal method for animated images.
/// Primarily used in Gif animation, this field indicates the way in which the graphic is to
/// be treated after being displayed.
/// </summary>
public GifDisposalMethod DisposalMethod { get; set; }
/// <inheritdoc/>
public IDeepCloneable DeepClone() => new GifFrameMetaData(this);
}
}

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

@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <inheritdoc/>
public IImageFormat DetectFormat(ReadOnlySpan<byte> header)
{
return this.IsSupportedFileFormat(header) ? ImageFormats.Gif : null;
return this.IsSupportedFileFormat(header) ? GifFormat.Instance : null;
}
private bool IsSupportedFileFormat(ReadOnlySpan<byte> header)

50
src/ImageSharp/Formats/Gif/GifMetaData.cs

@ -0,0 +1,50 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Gif
{
/// <summary>
/// Provides Gif specific metadata information for the image.
/// </summary>
public class GifMetaData : IDeepCloneable
{
/// <summary>
/// Initializes a new instance of the <see cref="GifMetaData"/> class.
/// </summary>
public GifMetaData()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="GifMetaData"/> class.
/// </summary>
/// <param name="other">The metadata to create an instance from.</param>
private GifMetaData(GifMetaData other)
{
this.RepeatCount = other.RepeatCount;
this.ColorTableMode = other.ColorTableMode;
this.GlobalColorTableLength = other.GlobalColorTableLength;
}
/// <summary>
/// Gets or sets the number of times any animation is repeated.
/// <remarks>
/// 0 means to repeat indefinitely, count is set as play n + 1 times
/// </remarks>
/// </summary>
public ushort RepeatCount { get; set; }
/// <summary>
/// Gets or sets the color table mode.
/// </summary>
public GifColorTableMode ColorTableMode { get; set; }
/// <summary>
/// Gets or sets the length of the global color table if present.
/// </summary>
public int GlobalColorTableLength { get; set; }
/// <inheritdoc/>
public IDeepCloneable DeepClone() => new GifMetaData(this);
}
}

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

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System.Text;
using SixLabors.ImageSharp.MetaData;
namespace SixLabors.ImageSharp.Formats.Gif
{

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

@ -11,11 +11,6 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// </summary>
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 text encoding used to write comments.
/// </summary>
@ -29,6 +24,6 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <summary>
/// Gets the color table mode: Global or local.
/// </summary>
GifColorTableMode ColorTableMode { get; }
GifColorTableMode? ColorTableMode { get; }
}
}

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

@ -34,6 +34,6 @@ namespace SixLabors.ImageSharp
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
public static void SaveAsGif<TPixel>(this Image<TPixel> source, Stream stream, GifEncoder encoder)
where TPixel : struct, IPixel<TPixel>
=> source.Save(stream, encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(ImageFormats.Gif));
=> source.Save(stream, encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(GifFormat.Instance));
}
}

4
src/ImageSharp/Formats/Gif/Sections/GifGraphicControlExtension.cs

@ -53,7 +53,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// Gets the disposal method which indicates the way in which the
/// graphic is to be treated after being displayed.
/// </summary>
public DisposalMethod DisposalMethod => (DisposalMethod)((this.Packed & 0x1C) >> 2);
public GifDisposalMethod DisposalMethod => (GifDisposalMethod)((this.Packed & 0x1C) >> 2);
/// <summary>
/// Gets a value indicating whether transparency flag is to be set.
@ -77,7 +77,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
return MemoryMarshal.Cast<byte, GifGraphicControlExtension>(buffer)[0];
}
public static byte GetPackedValue(DisposalMethod disposalMethod, bool userInputFlag = false, bool transparencyFlag = false)
public static byte GetPackedValue(GifDisposalMethod disposalMethod, bool userInputFlag = false, bool transparencyFlag = false)
{
/*
Reserved | 3 Bits

4
src/ImageSharp/Formats/Gif/Sections/GifImageDescriptor.cs

@ -81,7 +81,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
return MemoryMarshal.Cast<byte, GifImageDescriptor>(buffer)[0];
}
public static byte GetPackedValue(bool localColorTableFlag, bool interfaceFlag, bool sortFlag, byte localColorTableSize)
public static byte GetPackedValue(bool localColorTableFlag, bool interfaceFlag, bool sortFlag, int localColorTableSize)
{
/*
Local Color Table Flag | 1 Bit
@ -108,7 +108,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
value |= 1 << 5;
}
value |= (byte)(localColorTableSize - 1);
value |= (byte)localColorTableSize;
return value;
}

44
src/ImageSharp/Formats/Gif/Sections/GifNetscapeLoopingApplicationExtension.cs

@ -0,0 +1,44 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers.Binary;
namespace SixLabors.ImageSharp.Formats.Gif
{
internal readonly struct GifNetscapeLoopingApplicationExtension : IGifExtension
{
public GifNetscapeLoopingApplicationExtension(ushort repeatCount) => this.RepeatCount = repeatCount;
public byte Label => GifConstants.ApplicationExtensionLabel;
/// <summary>
/// Gets the repeat count.
/// 0 means loop indefinitely. Count is set as play n + 1 times.
/// </summary>
public ushort RepeatCount { get; }
public static GifNetscapeLoopingApplicationExtension Parse(ReadOnlySpan<byte> buffer)
{
ushort repeatCount = BinaryPrimitives.ReadUInt16LittleEndian(buffer.Slice(0, 2));
return new GifNetscapeLoopingApplicationExtension(repeatCount);
}
public int WriteTo(Span<byte> buffer)
{
buffer[0] = GifConstants.ApplicationBlockSize;
// Write NETSCAPE2.0
GifConstants.NetscapeApplicationIdentificationBytes.AsSpan().CopyTo(buffer.Slice(1, 11));
// Application Data ----
buffer[12] = 3; // Application block length (always 3)
buffer[13] = 1; // Data sub-block indentity (always 1)
// 0 means loop indefinitely. Count is set as play n + 1 times.
BinaryPrimitives.WriteUInt16LittleEndian(buffer.Slice(14, 2), this.RepeatCount);
return 16; // Length - Introducer + Label + Terminator.
}
}
}

32
src/ImageSharp/Formats/IImageFormat.cs

@ -6,7 +6,7 @@ using System.Collections.Generic;
namespace SixLabors.ImageSharp.Formats
{
/// <summary>
/// Describes an image format.
/// Defines the contract for an image format.
/// </summary>
public interface IImageFormat
{
@ -30,4 +30,34 @@ namespace SixLabors.ImageSharp.Formats
/// </summary>
IEnumerable<string> FileExtensions { get; }
}
/// <summary>
/// Defines the contract for an image format containing metadata.
/// </summary>
/// <typeparam name="TFormatMetaData">The type of format metadata.</typeparam>
public interface IImageFormat<out TFormatMetaData> : IImageFormat
where TFormatMetaData : class
{
/// <summary>
/// Creates a default instance of the format metadata.
/// </summary>
/// <returns>The <typeparamref name="TFormatMetaData"/>.</returns>
TFormatMetaData CreateDefaultFormatMetaData();
}
/// <summary>
/// Defines the contract for an image format containing metadata with multiple frames.
/// </summary>
/// <typeparam name="TFormatMetaData">The type of format metadata.</typeparam>
/// <typeparam name="TFormatFrameMetaData">The type of format frame metadata.</typeparam>
public interface IImageFormat<out TFormatMetaData, out TFormatFrameMetaData> : IImageFormat<TFormatMetaData>
where TFormatMetaData : class
where TFormatFrameMetaData : class
{
/// <summary>
/// Creates a default instance of the format frame metadata.
/// </summary>
/// <returns>The <typeparamref name="TFormatFrameMetaData"/>.</returns>
TFormatFrameMetaData CreateDefaultFormatFrameMetaData();
}
}

7
src/ImageSharp/Formats/Jpeg/Components/Block8x8F.CopyTo.cs

@ -5,7 +5,6 @@ using System.Numerics;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Memory;
using SixLabors.Memory;
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Formats.Jpeg.Components
@ -15,7 +14,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
/// <summary>
/// Copy block data into the destination color buffer pixel area with the provided horizontal and vertical.
/// </summary>
public void CopyTo(BufferArea<float> area, int horizontalScale, int verticalScale)
public void CopyTo(in BufferArea<float> area, int horizontalScale, int verticalScale)
{
if (horizontalScale == 1 && verticalScale == 1)
{
@ -57,7 +56,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
}
// [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void CopyTo(BufferArea<float> area)
public void CopyTo(in BufferArea<float> area)
{
ref byte selfBase = ref Unsafe.As<Block8x8F, byte>(ref this);
ref byte destBase = ref Unsafe.As<float, byte>(ref area.GetReferenceToOrigin());
@ -81,7 +80,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
Unsafe.CopyBlock(ref d, ref s, 8 * sizeof(float));
}
private void CopyTo2x2(BufferArea<float> area)
private void CopyTo2x2(in BufferArea<float> area)
{
ref float destBase = ref area.GetReferenceToOrigin();
int destStride = area.Stride;

2
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmyk.cs

@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
}
public override void ConvertToRgba(ComponentValues values, Span<Vector4> result)
public override void ConvertToRgba(in ComponentValues values, Span<Vector4> result)
{
// TODO: We can optimize a lot here with Vector<float> and SRCS.Unsafe()!
ReadOnlySpan<float> cVals = values.Component0;

2
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScale.cs

@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
}
public override void ConvertToRgba(ComponentValues values, Span<Vector4> result)
public override void ConvertToRgba(in ComponentValues values, Span<Vector4> result)
{
// TODO: We can optimize a lot here with Vector<float> and SRCS.Unsafe()!
ReadOnlySpan<float> yVals = values.Component0;

2
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgb.cs

@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
}
public override void ConvertToRgba(ComponentValues values, Span<Vector4> result)
public override void ConvertToRgba(in ComponentValues values, Span<Vector4> result)
{
// TODO: We can optimize a lot here with Vector<float> and SRCS.Unsafe()!
ReadOnlySpan<float> rVals = values.Component0;

4
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrBasic.cs

@ -15,12 +15,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
}
public override void ConvertToRgba(ComponentValues values, Span<Vector4> result)
public override void ConvertToRgba(in ComponentValues values, Span<Vector4> result)
{
ConvertCore(values, result);
}
internal static void ConvertCore(ComponentValues values, Span<Vector4> result)
internal static void ConvertCore(in ComponentValues values, Span<Vector4> result)
{
// TODO: We can optimize a lot here with Vector<float> and SRCS.Unsafe()!
ReadOnlySpan<float> yVals = values.Component0;

4
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimd.cs

@ -19,7 +19,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
}
public override void ConvertToRgba(ComponentValues values, Span<Vector4> result)
public override void ConvertToRgba(in ComponentValues values, Span<Vector4> result)
{
int remainder = result.Length % 8;
int simdCount = result.Length - remainder;
@ -34,7 +34,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
/// <summary>
/// SIMD convert using buffers of sizes divisable by 8.
/// </summary>
internal static void ConvertCore(ComponentValues values, Span<Vector4> result)
internal static void ConvertCore(in ComponentValues values, Span<Vector4> result)
{
DebugGuard.IsTrue(result.Length % 8 == 0, nameof(result), "result.Length should be divisable by 8!");

4
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimdAvx2.cs

@ -22,7 +22,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
public static bool IsAvailable => Vector.IsHardwareAccelerated && SimdUtils.IsAvx2CompatibleArchitecture;
public override void ConvertToRgba(ComponentValues values, Span<Vector4> result)
public override void ConvertToRgba(in ComponentValues values, Span<Vector4> result)
{
int remainder = result.Length % 8;
int simdCount = result.Length - remainder;
@ -37,7 +37,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
/// <summary>
/// SIMD convert using buffers of sizes divisable by 8.
/// </summary>
internal static void ConvertCore(ComponentValues values, Span<Vector4> result)
internal static void ConvertCore(in ComponentValues values, Span<Vector4> result)
{
// This implementation is actually AVX specific.
// An AVX register is capable of storing 8 float-s.

2
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccK.cs

@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
}
public override void ConvertToRgba(ComponentValues values, Span<Vector4> result)
public override void ConvertToRgba(in ComponentValues values, Span<Vector4> result)
{
// TODO: We can optimize a lot here with Vector<float> and SRCS.Unsafe()!
ReadOnlySpan<float> yVals = values.Component0;

2
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs

@ -58,7 +58,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
/// </summary>
/// <param name="values">The input as a stack-only <see cref="ComponentValues"/> struct</param>
/// <param name="result">The destination buffer of <see cref="Vector4"/> values</param>
public abstract void ConvertToRgba(ComponentValues values, Span<Vector4> result);
public abstract void ConvertToRgba(in ComponentValues values, Span<Vector4> result);
/// <summary>
/// Returns the <see cref="JpegColorConverter"/> for the YCbCr colorspace that matches the current CPU architecture.

8
src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBlockPostProcessor.cs

@ -2,9 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Memory;
using SixLabors.Memory;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
@ -43,6 +41,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// <summary>
/// Initializes a new instance of the <see cref="JpegBlockPostProcessor"/> struct.
/// </summary>
/// <param name="decoder">The raw jpeg data.</param>
/// <param name="component">The raw component.</param>
public JpegBlockPostProcessor(IRawJpegData decoder, IJpegComponent component)
{
int qtIndex = component.QuantizationTableIndex;
@ -61,9 +61,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// - Level shift by +128, clip to [0, 255]
/// - Copy the resultin color values into 'destArea' scaling up the block by amount defined in <see cref="subSamplingDivisors"/>
/// </summary>
/// <param name="sourceBlock">The source block.</param>
/// <param name="destArea">The destination buffer area.</param>
public void ProcessBlockColorsInto(
ref Block8x8 sourceBlock,
BufferArea<float> destArea)
in BufferArea<float> destArea)
{
ref Block8x8F b = ref this.SourceBlock;
b.LoadFrom(ref sourceBlock);

140
src/ImageSharp/Formats/Jpeg/Components/Decoder/QualityEvaluator.cs

@ -0,0 +1,140 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
/// <summary>
/// Provides methods to evaluate the quality of an image.
/// Ported from <see href="https://github.com/ImageMagick/ImageMagick/blob/f362c02083d27211b913c6e44794f0ac6edaf2bd/coders/jpeg.c#L855"/>
/// </summary>
internal static class QualityEvaluator
{
private static readonly int[] Hash = new int[101]
{
1020, 1015, 932, 848, 780, 735, 702, 679, 660, 645,
632, 623, 613, 607, 600, 594, 589, 585, 581, 571,
555, 542, 529, 514, 494, 474, 457, 439, 424, 410,
397, 386, 373, 364, 351, 341, 334, 324, 317, 309,
299, 294, 287, 279, 274, 267, 262, 257, 251, 247,
243, 237, 232, 227, 222, 217, 213, 207, 202, 198,
192, 188, 183, 177, 173, 168, 163, 157, 153, 148,
143, 139, 132, 128, 125, 119, 115, 108, 104, 99,
94, 90, 84, 79, 74, 70, 64, 59, 55, 49,
45, 40, 34, 30, 25, 20, 15, 11, 6, 4,
0
};
private static readonly int[] Sums = new int[101]
{
32640, 32635, 32266, 31495, 30665, 29804, 29146, 28599, 28104,
27670, 27225, 26725, 26210, 25716, 25240, 24789, 24373, 23946,
23572, 22846, 21801, 20842, 19949, 19121, 18386, 17651, 16998,
16349, 15800, 15247, 14783, 14321, 13859, 13535, 13081, 12702,
12423, 12056, 11779, 11513, 11135, 10955, 10676, 10392, 10208,
9928, 9747, 9564, 9369, 9193, 9017, 8822, 8639, 8458,
8270, 8084, 7896, 7710, 7527, 7347, 7156, 6977, 6788,
6607, 6422, 6236, 6054, 5867, 5684, 5495, 5305, 5128,
4945, 4751, 4638, 4442, 4248, 4065, 3888, 3698, 3509,
3326, 3139, 2957, 2775, 2586, 2405, 2216, 2037, 1846,
1666, 1483, 1297, 1109, 927, 735, 554, 375, 201,
128, 0
};
private static readonly int[] Hash1 = new int[101]
{
510, 505, 422, 380, 355, 338, 326, 318, 311, 305,
300, 297, 293, 291, 288, 286, 284, 283, 281, 280,
279, 278, 277, 273, 262, 251, 243, 233, 225, 218,
211, 205, 198, 193, 186, 181, 177, 172, 168, 164,
158, 156, 152, 148, 145, 142, 139, 136, 133, 131,
129, 126, 123, 120, 118, 115, 113, 110, 107, 105,
102, 100, 97, 94, 92, 89, 87, 83, 81, 79,
76, 74, 70, 68, 66, 63, 61, 57, 55, 52,
50, 48, 44, 42, 39, 37, 34, 31, 29, 26,
24, 21, 18, 16, 13, 11, 8, 6, 3, 2,
0
};
private static readonly int[] Sums1 = new int[101]
{
16320, 16315, 15946, 15277, 14655, 14073, 13623, 13230, 12859,
12560, 12240, 11861, 11456, 11081, 10714, 10360, 10027, 9679,
9368, 9056, 8680, 8331, 7995, 7668, 7376, 7084, 6823,
6562, 6345, 6125, 5939, 5756, 5571, 5421, 5240, 5086,
4976, 4829, 4719, 4616, 4463, 4393, 4280, 4166, 4092,
3980, 3909, 3835, 3755, 3688, 3621, 3541, 3467, 3396,
3323, 3247, 3170, 3096, 3021, 2952, 2874, 2804, 2727,
2657, 2583, 2509, 2437, 2362, 2290, 2211, 2136, 2068,
1996, 1915, 1858, 1773, 1692, 1620, 1552, 1477, 1398,
1326, 1251, 1179, 1109, 1031, 961, 884, 814, 736,
667, 592, 518, 441, 369, 292, 221, 151, 86,
64, 0
};
/// <summary>
/// Returns an estimated quality of the image based on the quantization tables.
/// </summary>
/// <param name="quantizationTables">The quantization tables.</param>
/// <returns>The <see cref="int"/>.</returns>
public static int EstimateQuality(Block8x8F[] quantizationTables)
{
int quality = 75;
float sum = 0;
for (int i = 0; i < quantizationTables.Length; i++)
{
ref Block8x8F qTable = ref quantizationTables[i];
for (int j = 0; j < Block8x8F.Size; j++)
{
sum += qTable[j];
}
}
ref Block8x8F qTable0 = ref quantizationTables[0];
ref Block8x8F qTable1 = ref quantizationTables[1];
if (!qTable0.Equals(default))
{
if (!qTable1.Equals(default))
{
quality = (int)(qTable0[2]
+ qTable0[53]
+ qTable1[0]
+ qTable1[Block8x8F.Size - 1]);
for (int i = 0; i < 100; i++)
{
if (quality < Hash[i] && sum < Sums[i])
{
continue;
}
if (((quality <= Hash[i]) && (sum <= Sums[i])) || (i >= 50))
{
return i + 1;
}
}
}
else
{
quality = (int)(qTable0[2] + qTable0[53]);
for (int i = 0; i < 100; i++)
{
if (quality < Hash1[i] && sum < Sums1[i])
{
continue;
}
if (((quality <= Hash1[i]) && (sum <= Sums1[i])) || (i >= 50))
{
return i + 1;
}
}
}
}
return quality;
}
}
}

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

@ -8,17 +8,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// </summary>
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).
/// </summary>
/// <value>The quality of the jpg image from 0 to 100.</value>
int Quality { get; }
int? Quality { get; }
/// <summary>
/// Gets the subsample ration, that will be used to encode the image.

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

@ -34,6 +34,6 @@ namespace SixLabors.ImageSharp
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
public static void SaveAsJpeg<TPixel>(this Image<TPixel> source, Stream stream, JpegEncoder encoder)
where TPixel : struct, IPixel<TPixel>
=> source.Save(stream, encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(ImageFormats.Jpeg));
=> source.Save(stream, encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(JpegFormat.Instance));
}
}

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

@ -9,12 +9,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
public sealed class JpegConfigurationModule : IConfigurationModule
{
/// <inheritdoc/>
public void Configure(Configuration config)
public void Configure(Configuration configuration)
{
config.ImageFormatsManager.SetEncoder(ImageFormats.Jpeg, new JpegEncoder());
config.ImageFormatsManager.SetDecoder(ImageFormats.Jpeg, new JpegDecoder());
config.ImageFormatsManager.AddImageFormatDetector(new JpegImageFormatDetector());
configuration.ImageFormatsManager.SetEncoder(JpegFormat.Instance, new JpegEncoder());
configuration.ImageFormatsManager.SetDecoder(JpegFormat.Instance, new JpegDecoder());
configuration.ImageFormatsManager.AddImageFormatDetector(new JpegImageFormatDetector());
}
}
}

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

@ -234,6 +234,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
this.InitExifProfile();
this.InitIccProfile();
this.InitDerivedMetaDataProperties();
return new ImageInfo(new PixelTypeInfo(this.BitsPerPixel), this.ImageWidth, this.ImageHeight, this.MetaData);
}
@ -258,17 +259,20 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
this.InputStream.Read(this.markerBuffer, 0, 2);
byte marker = this.markerBuffer[1];
fileMarker = new JpegFileMarker(marker, (int)this.InputStream.Position - 2);
this.QuantizationTables = new Block8x8F[4];
// Only assign what we need
if (!metadataOnly)
{
this.QuantizationTables = new Block8x8F[4];
this.dcHuffmanTables = new HuffmanTables();
this.acHuffmanTables = new HuffmanTables();
this.fastACTables = new FastACTables(this.configuration.MemoryAllocator);
}
while (fileMarker.Marker != JpegConstants.Markers.EOI)
// Break only when we discover a valid EOI marker.
// https://github.com/SixLabors/ImageSharp/issues/695
while (fileMarker.Marker != JpegConstants.Markers.EOI
|| (fileMarker.Marker == JpegConstants.Markers.EOI && fileMarker.Invalid))
{
if (!fileMarker.Invalid)
{
@ -310,15 +314,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
break;
case JpegConstants.Markers.DQT:
if (metadataOnly)
{
this.InputStream.Skip(remaining);
}
else
{
this.ProcessDefineQuantizationTablesMarker(remaining);
}
this.ProcessDefineQuantizationTablesMarker(remaining);
break;
case JpegConstants.Markers.DRI:
@ -463,13 +459,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
}
else if (this.isExif)
{
double horizontalValue = this.MetaData.ExifProfile.TryGetValue(ExifTag.XResolution, out ExifValue horizontalTag)
? ((Rational)horizontalTag.Value).ToDouble()
: 0;
double verticalValue = this.MetaData.ExifProfile.TryGetValue(ExifTag.YResolution, out ExifValue verticalTag)
? ((Rational)verticalTag.Value).ToDouble()
: 0;
double horizontalValue = this.GetExifResolutionValue(ExifTag.XResolution);
double verticalValue = this.GetExifResolutionValue(ExifTag.YResolution);
if (horizontalValue > 0 && verticalValue > 0)
{
@ -480,6 +471,26 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
}
}
private double GetExifResolutionValue(ExifTag tag)
{
if (!this.MetaData.ExifProfile.TryGetValue(tag, out ExifValue exifValue))
{
return 0;
}
switch (exifValue.DataType)
{
case ExifDataType.Rational:
return ((Rational)exifValue.Value).ToDouble();
case ExifDataType.Long:
return (uint)exifValue.Value;
case ExifDataType.DoubleFloat:
return (double)exifValue.Value;
default:
return 0;
}
}
/// <summary>
/// Extends the profile with additional data.
/// </summary>
@ -689,6 +700,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
{
throw new ImageFormatException("DQT has wrong length");
}
this.MetaData.GetFormatMetaData(JpegFormat.Instance).Quality = QualityEvaluator.EstimateQuality(this.QuantizationTables);
}
/// <summary>
@ -899,9 +912,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <param name="values">The values</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void BuildHuffmanTable(HuffmanTables tables, int index, ReadOnlySpan<byte> codeLengths, ReadOnlySpan<byte> values)
{
tables[index] = new HuffmanTable(this.configuration.MemoryAllocator, codeLengths, values);
}
=> tables[index] = new HuffmanTable(this.configuration.MemoryAllocator, codeLengths, values);
/// <summary>
/// Reads a <see cref="ushort"/> from the stream advancing it by two bytes

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

@ -11,17 +11,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// </summary>
public sealed class JpegEncoder : IImageEncoder, IJpegEncoderOptions
{
/// <summary>
/// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded.
/// </summary>
public bool IgnoreMetadata { get; set; }
/// <summary>
/// Gets or sets the quality, that will be used to encode the image. Quality
/// index must be between 0 and 100 (compression from max to min).
/// Defaults to <value>75</value>.
/// </summary>
public int Quality { get; set; } = 75;
public int? Quality { get; set; }
/// <summary>
/// Gets or sets the subsample ration, that will be used to encode the image.

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

@ -123,19 +123,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
private readonly byte[] huffmanBuffer = new byte[179];
/// <summary>
/// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded.
/// Gets or sets the subsampling method to use.
/// </summary>
private readonly bool ignoreMetadata;
private JpegSubsample? subsample;
/// <summary>
/// The quality, that will be used to encode the image.
/// </summary>
private readonly int quality;
/// <summary>
/// Gets or sets the subsampling method to use.
/// </summary>
private readonly JpegSubsample? subsample;
private readonly int? quality;
/// <summary>
/// The accumulated bits to write to the stream.
@ -168,11 +163,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <param name="options">The options</param>
public JpegEncoderCore(IJpegEncoderOptions options)
{
// System.Drawing produces identical output for jpegs with a quality parameter of 0 and 1.
this.quality = options.Quality.Clamp(1, 100);
this.subsample = options.Subsample ?? (this.quality >= 91 ? JpegSubsample.Ratio444 : JpegSubsample.Ratio420);
this.ignoreMetadata = options.IgnoreMetadata;
this.quality = options.Quality;
this.subsample = options.Subsample;
}
/// <summary>
@ -194,16 +186,21 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
}
this.outputStream = stream;
ImageMetaData metaData = image.MetaData;
// System.Drawing produces identical output for jpegs with a quality parameter of 0 and 1.
int qlty = (this.quality ?? metaData.GetFormatMetaData(JpegFormat.Instance).Quality).Clamp(1, 100);
this.subsample = this.subsample ?? (qlty >= 91 ? JpegSubsample.Ratio444 : JpegSubsample.Ratio420);
// Convert from a quality rating to a scaling factor.
int scale;
if (this.quality < 50)
if (qlty < 50)
{
scale = 5000 / this.quality;
scale = 5000 / qlty;
}
else
{
scale = 200 - (this.quality * 2);
scale = 200 - (qlty * 2);
}
// Initialize the quantization tables.
@ -214,10 +211,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
int componentCount = 3;
// Write the Start Of Image marker.
this.WriteApplicationHeader(image.MetaData);
this.WriteApplicationHeader(metaData);
// Write Exif and ICC profiles
this.WriteProfiles(image);
this.WriteProfiles(metaData);
// Write the quantization tables.
this.WriteDefineQuantizationTables();
@ -624,6 +621,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// </exception>
private void WriteExifProfile(ExifProfile exifProfile)
{
if (exifProfile is null)
{
return;
}
const int MaxBytesApp1 = 65533;
const int MaxBytesWithExifId = 65527;
@ -762,19 +764,17 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <summary>
/// Writes the metadata profiles to the image.
/// </summary>
/// <param name="image">The image.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
private void WriteProfiles<TPixel>(Image<TPixel> image)
where TPixel : struct, IPixel<TPixel>
/// <param name="metaData">The image meta data.</param>
private void WriteProfiles(ImageMetaData metaData)
{
if (this.ignoreMetadata)
if (metaData is null)
{
return;
}
image.MetaData.SyncProfiles();
this.WriteExifProfile(image.MetaData.ExifProfile);
this.WriteIccProfile(image.MetaData.IccProfile);
metaData.SyncProfiles();
this.WriteExifProfile(metaData.ExifProfile);
this.WriteIccProfile(metaData.IccProfile);
}
/// <summary>

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

@ -8,8 +8,17 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <summary>
/// Registers the image encoders, decoders and mime type detectors for the jpeg format.
/// </summary>
internal sealed class JpegFormat : IImageFormat
public sealed class JpegFormat : IImageFormat<JpegMetaData>
{
private JpegFormat()
{
}
/// <summary>
/// Gets the current instance.
/// </summary>
public static JpegFormat Instance { get; } = new JpegFormat();
/// <inheritdoc/>
public string Name => "JPEG";
@ -21,5 +30,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <inheritdoc/>
public IEnumerable<string> FileExtensions => JpegConstants.FileExtensions;
/// <inheritdoc/>
public JpegMetaData CreateDefaultFormatMetaData() => new JpegMetaData();
}
}

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

@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <inheritdoc/>
public IImageFormat DetectFormat(ReadOnlySpan<byte> header)
{
return this.IsSupportedFileFormat(header) ? ImageFormats.Jpeg : null;
return this.IsSupportedFileFormat(header) ? JpegFormat.Instance : null;
}
private bool IsSupportedFileFormat(ReadOnlySpan<byte> header)

32
src/ImageSharp/Formats/Jpeg/JpegMetaData.cs

@ -0,0 +1,32 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Jpeg
{
/// <summary>
/// Provides Jpeg specific metadata information for the image.
/// </summary>
public class JpegMetaData : IDeepCloneable
{
/// <summary>
/// Initializes a new instance of the <see cref="JpegMetaData"/> class.
/// </summary>
public JpegMetaData()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="JpegMetaData"/> class.
/// </summary>
/// <param name="other">The metadata to create an instance from.</param>
private JpegMetaData(JpegMetaData other) => this.Quality = other.Quality;
/// <summary>
/// Gets or sets the encoded quality.
/// </summary>
public int Quality { get; set; } = 75;
/// <inheritdoc/>
public IDeepCloneable DeepClone() => new JpegMetaData(this);
}
}

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

@ -14,12 +14,12 @@ namespace SixLabors.ImageSharp.Formats.Png
/// Gets the number of bits per sample or per palette index (not per pixel).
/// Not all values are allowed for all <see cref="ColorType"/> values.
/// </summary>
PngBitDepth BitDepth { get; }
PngBitDepth? BitDepth { get; }
/// <summary>
/// Gets the color type
/// </summary>
PngColorType ColorType { get; }
PngColorType? ColorType { get; }
/// <summary>
/// Gets the filter method.
@ -33,15 +33,13 @@ namespace SixLabors.ImageSharp.Formats.Png
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. The default value is 2.2F.
/// Gets the gamma value, that will be written the the image.
/// </summary>
/// <value>The gamma value of the image.</value>
float Gamma { get; }
float? Gamma { get; }
/// <summary>
/// Gets quantizer for reducing the color count.
/// Gets the quantizer for reducing the color count.
/// </summary>
IQuantizer Quantizer { get; }
@ -49,11 +47,5 @@ namespace SixLabors.ImageSharp.Formats.Png
/// Gets the transparency threshold.
/// </summary>
byte Threshold { get; }
/// <summary>
/// Gets a value indicating whether this instance should write
/// gamma information to the stream. The default value is false.
/// </summary>
bool WriteGamma { get; }
}
}

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

@ -35,6 +35,6 @@ namespace SixLabors.ImageSharp
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
public static void SaveAsPng<TPixel>(this Image<TPixel> source, Stream stream, PngEncoder encoder)
where TPixel : struct, IPixel<TPixel>
=> source.Save(stream, encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(ImageFormats.Png));
=> source.Save(stream, encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(PngFormat.Instance));
}
}

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

@ -9,6 +9,21 @@ namespace SixLabors.ImageSharp.Formats.Png
/// </summary>
public enum PngBitDepth
{
/// <summary>
/// 1 bit per sample or per palette index (not per pixel).
/// </summary>
Bit1 = 1,
/// <summary>
/// 2 bits per sample or per palette index (not per pixel).
/// </summary>
Bit2 = 2,
/// <summary>
/// 4 bits per sample or per palette index (not per pixel).
/// </summary>
Bit4 = 4,
/// <summary>
/// 8 bits per sample or per palette index (not per pixel).
/// </summary>

52
src/ImageSharp/Formats/Png/PngChunkType.cs

@ -8,58 +8,58 @@ namespace SixLabors.ImageSharp.Formats.Png
/// </summary>
internal enum PngChunkType : uint
{
/// <summary>
/// The first chunk in a png file. Can only exists once. Contains
/// common information like the width and the height of the image or
/// the used compression method.
/// </summary>
Header = 0x49484452U, // IHDR
/// <summary>
/// The PLTE chunk contains from 1 to 256 palette entries, each a three byte
/// series in the RGB format.
/// </summary>
Palette = 0x504C5445U, // PLTE
/// <summary>
/// The IDAT chunk contains the actual image data. The image can contains more
/// than one chunk of this type. All chunks together are the whole image.
/// </summary>
Data = 0x49444154U, // IDAT
Data = 0x49444154U,
/// <summary>
/// This chunk must appear last. It marks the end of the PNG data stream.
/// The chunk's data field is empty.
/// </summary>
End = 0x49454E44U, // IEND
End = 0x49454E44U,
/// <summary>
/// This chunk specifies that the image uses simple transparency:
/// either alpha values associated with palette entries (for indexed-color images)
/// or a single transparent color (for grayscale and true color images).
/// The first chunk in a png file. Can only exists once. Contains
/// common information like the width and the height of the image or
/// the used compression method.
/// </summary>
PaletteAlpha = 0x74524E53U, // tRNS
Header = 0x49484452U,
/// <summary>
/// Textual information that the encoder wishes to record with the image can be stored in
/// tEXt chunks. Each tEXt chunk contains a keyword and a text string.
/// The PLTE chunk contains from 1 to 256 palette entries, each a three byte
/// series in the RGB format.
/// </summary>
Palette = 0x504C5445U,
/// <summary>
/// The eXIf data chunk which contains the Exif profile.
/// </summary>
Text = 0x74455874U, // tEXt
Exif = 0x65584966U,
/// <summary>
/// This chunk specifies the relationship between the image samples and the desired
/// display output intensity.
/// </summary>
Gamma = 0x67414D41U, // gAMA
Gamma = 0x67414D41U,
/// <summary>
/// The pHYs chunk specifies the intended pixel size or aspect ratio for display of the image.
/// </summary>
Physical = 0x70485973U, // pHYs
Physical = 0x70485973U,
/// <summary>
/// The data chunk which contains the Exif profile.
/// Textual information that the encoder wishes to record with the image can be stored in
/// tEXt chunks. Each tEXt chunk contains a keyword and a text string.
/// </summary>
Text = 0x74455874U,
/// <summary>
/// This chunk specifies that the image uses simple transparency:
/// either alpha values associated with palette entries (for indexed-color images)
/// or a single transparent color (for grayscale and true color images).
/// </summary>
Exif = 0x65584966U // eXIf
PaletteAlpha = 0x74524E53U
}
}

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

@ -11,8 +11,8 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <inheritdoc/>
public void Configure(Configuration configuration)
{
configuration.ImageFormatsManager.SetEncoder(ImageFormats.Png, new PngEncoder());
configuration.ImageFormatsManager.SetDecoder(ImageFormats.Png, new PngDecoder());
configuration.ImageFormatsManager.SetEncoder(PngFormat.Instance, new PngEncoder());
configuration.ImageFormatsManager.SetDecoder(PngFormat.Instance, new PngDecoder());
configuration.ImageFormatsManager.AddImageFormatDetector(new PngImageFormatDetector());
}
}

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

@ -10,7 +10,6 @@ using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Formats.Png.Filters;
using SixLabors.ImageSharp.Formats.Png.Zlib;
using SixLabors.ImageSharp.Memory;
@ -188,6 +187,11 @@ namespace SixLabors.ImageSharp.Formats.Png
/// </summary>
private bool hasTrans;
/// <summary>
/// The next chunk of data to return
/// </summary>
private PngChunk? nextChunk;
/// <summary>
/// Initializes a new instance of the <see cref="PngDecoderCore"/> class.
/// </summary>
@ -217,70 +221,74 @@ namespace SixLabors.ImageSharp.Formats.Png
public Image<TPixel> Decode<TPixel>(Stream stream)
where TPixel : struct, IPixel<TPixel>
{
var metadata = new ImageMetaData();
var metaData = new ImageMetaData();
PngMetaData pngMetaData = metaData.GetFormatMetaData(PngFormat.Instance);
this.currentStream = stream;
this.currentStream.Skip(8);
Image<TPixel> image = null;
try
{
using (var deframeStream = new ZlibInflateStream(this.currentStream))
while (!this.isEndChunkReached && this.TryReadChunk(out PngChunk chunk))
{
while (!this.isEndChunkReached && this.TryReadChunk(out PngChunk chunk))
try
{
try
switch (chunk.Type)
{
switch (chunk.Type)
{
case PngChunkType.Header:
this.ReadHeaderChunk(chunk.Data.Array);
this.ValidateHeader();
break;
case PngChunkType.Physical:
this.ReadPhysicalChunk(metadata, chunk.Data.GetSpan());
break;
case PngChunkType.Data:
if (image is null)
{
this.InitializeImage(metadata, out image);
}
case PngChunkType.Header:
this.ReadHeaderChunk(pngMetaData, chunk.Data.Array);
this.ValidateHeader();
break;
case PngChunkType.Physical:
this.ReadPhysicalChunk(metaData, chunk.Data.GetSpan());
break;
case PngChunkType.Gamma:
this.ReadGammaChunk(pngMetaData, chunk.Data.GetSpan());
break;
case PngChunkType.Data:
if (image is null)
{
this.InitializeImage(metaData, out image);
}
using (var deframeStream = new ZlibInflateStream(this.currentStream, this.ReadNextDataChunk))
{
deframeStream.AllocateNewBytes(chunk.Length);
this.ReadScanlines(deframeStream.CompressedStream, image.Frames.RootFrame);
this.currentStream.Read(this.crcBuffer, 0, 4);
break;
case PngChunkType.Palette:
byte[] pal = new byte[chunk.Length];
Buffer.BlockCopy(chunk.Data.Array, 0, pal, 0, chunk.Length);
this.palette = pal;
break;
case PngChunkType.PaletteAlpha:
byte[] alpha = new byte[chunk.Length];
Buffer.BlockCopy(chunk.Data.Array, 0, alpha, 0, chunk.Length);
this.paletteAlpha = alpha;
this.AssignTransparentMarkers(alpha);
break;
case PngChunkType.Text:
this.ReadTextChunk(metadata, chunk.Data.Array, chunk.Length);
break;
case PngChunkType.Exif:
if (!this.ignoreMetadata)
{
byte[] exifData = new byte[chunk.Length];
Buffer.BlockCopy(chunk.Data.Array, 0, exifData, 0, chunk.Length);
metadata.ExifProfile = new ExifProfile(exifData);
}
break;
case PngChunkType.End:
this.isEndChunkReached = true;
break;
}
}
finally
{
chunk.Data?.Dispose(); // Data is rented in ReadChunkData()
}
break;
case PngChunkType.Palette:
byte[] pal = new byte[chunk.Length];
Buffer.BlockCopy(chunk.Data.Array, 0, pal, 0, chunk.Length);
this.palette = pal;
break;
case PngChunkType.PaletteAlpha:
byte[] alpha = new byte[chunk.Length];
Buffer.BlockCopy(chunk.Data.Array, 0, alpha, 0, chunk.Length);
this.paletteAlpha = alpha;
this.AssignTransparentMarkers(alpha);
break;
case PngChunkType.Text:
this.ReadTextChunk(metaData, chunk.Data.Array, chunk.Length);
break;
case PngChunkType.Exif:
if (!this.ignoreMetadata)
{
byte[] exifData = new byte[chunk.Length];
Buffer.BlockCopy(chunk.Data.Array, 0, exifData, 0, chunk.Length);
metaData.ExifProfile = new ExifProfile(exifData);
}
break;
case PngChunkType.End:
this.isEndChunkReached = true;
break;
}
}
finally
{
chunk.Data?.Dispose(); // Data is rented in ReadChunkData()
}
}
if (image is null)
@ -303,7 +311,8 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
public IImageInfo Identify(Stream stream)
{
var metadata = new ImageMetaData();
var metaData = new ImageMetaData();
PngMetaData pngMetaData = metaData.GetFormatMetaData(PngFormat.Instance);
this.currentStream = stream;
this.currentStream.Skip(8);
try
@ -315,17 +324,20 @@ namespace SixLabors.ImageSharp.Formats.Png
switch (chunk.Type)
{
case PngChunkType.Header:
this.ReadHeaderChunk(chunk.Data.Array);
this.ReadHeaderChunk(pngMetaData, chunk.Data.Array);
this.ValidateHeader();
break;
case PngChunkType.Physical:
this.ReadPhysicalChunk(metadata, chunk.Data.GetSpan());
this.ReadPhysicalChunk(metaData, chunk.Data.GetSpan());
break;
case PngChunkType.Gamma:
this.ReadGammaChunk(pngMetaData, chunk.Data.GetSpan());
break;
case PngChunkType.Data:
this.SkipChunkDataAndCrc(chunk);
break;
case PngChunkType.Text:
this.ReadTextChunk(metadata, chunk.Data.Array, chunk.Length);
this.ReadTextChunk(metaData, chunk.Data.Array, chunk.Length);
break;
case PngChunkType.End:
this.isEndChunkReached = true;
@ -349,7 +361,7 @@ namespace SixLabors.ImageSharp.Formats.Png
throw new ImageFormatException("PNG Image does not contain a header chunk");
}
return new ImageInfo(new PixelTypeInfo(this.CalculateBitsPerPixel()), this.header.Width, this.header.Height, metadata);
return new ImageInfo(new PixelTypeInfo(this.CalculateBitsPerPixel()), this.header.Width, this.header.Height, metaData);
}
/// <summary>
@ -360,9 +372,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <returns>The <see cref="int"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static byte ReadByteLittleEndian(ReadOnlySpan<byte> buffer, int offset)
{
return (byte)(((buffer[offset] & 0xFF) << 16) | (buffer[offset + 1] & 0xFF));
}
=> (byte)(((buffer[offset] & 0xFF) << 16) | (buffer[offset + 1] & 0xFF));
/// <summary>
/// Attempts to convert a byte array to a new array where each value in the original array is represented by the
@ -430,6 +440,18 @@ namespace SixLabors.ImageSharp.Formats.Png
metadata.VerticalResolution = vResolution;
}
/// <summary>
/// Reads the data chunk containing gamma data.
/// </summary>
/// <param name="pngMetadata">The metadata to read to.</param>
/// <param name="data">The data containing physical data.</param>
private void ReadGammaChunk(PngMetaData pngMetadata, ReadOnlySpan<byte> data)
{
// The value is encoded as a 4-byte unsigned integer, representing gamma times 100000.
// For example, a gamma of 1/2.2 would be stored as 45455.
pngMetadata.Gamma = BinaryPrimitives.ReadUInt32BigEndian(data) / 100_000F;
}
/// <summary>
/// Initializes the image and various buffers needed for processing
/// </summary>
@ -711,7 +733,7 @@ namespace SixLabors.ImageSharp.Formats.Png
{
case PngColorType.Grayscale:
int factor = 255 / ((int)Math.Pow(2, this.header.BitDepth) - 1);
int factor = 255 / (ImageMaths.GetColorCountForBitDepth(this.header.BitDepth) - 1);
if (!this.hasTrans)
{
@ -933,7 +955,7 @@ namespace SixLabors.ImageSharp.Formats.Png
{
case PngColorType.Grayscale:
int factor = 255 / ((int)Math.Pow(2, this.header.BitDepth) - 1);
int factor = 255 / (ImageMaths.GetColorCountForBitDepth(this.header.BitDepth) - 1);
if (!this.hasTrans)
{
@ -1270,17 +1292,22 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <summary>
/// Reads a header chunk from the data.
/// </summary>
/// <param name="pngMetaData">The png metadata.</param>
/// <param name="data">The <see cref="T:ReadOnlySpan{byte}"/> containing data.</param>
private void ReadHeaderChunk(ReadOnlySpan<byte> data)
private void ReadHeaderChunk(PngMetaData pngMetaData, ReadOnlySpan<byte> data)
{
byte bitDepth = data[8];
this.header = new PngHeader(
width: BinaryPrimitives.ReadInt32BigEndian(data.Slice(0, 4)),
height: BinaryPrimitives.ReadInt32BigEndian(data.Slice(4, 4)),
bitDepth: data[8],
bitDepth: bitDepth,
colorType: (PngColorType)data[9],
compressionMethod: data[10],
filterMethod: data[11],
interlaceMethod: (PngInterlaceMode)data[12]);
pngMetaData.BitDepth = (PngBitDepth)bitDepth;
pngMetaData.ColorType = this.header.ColorType;
}
/// <summary>
@ -1344,6 +1371,32 @@ namespace SixLabors.ImageSharp.Formats.Png
metadata.Properties.Add(new ImageProperty(name, value));
}
/// <summary>
/// Reads the next data chunk.
/// </summary>
/// <returns>Count of bytes in the next data chunk, or 0 if there are no more data chunks left.</returns>
private int ReadNextDataChunk()
{
if (this.nextChunk != null)
{
return 0;
}
this.currentStream.Read(this.crcBuffer, 0, 4);
if (this.TryReadChunk(out PngChunk chunk))
{
if (chunk.Type == PngChunkType.Data)
{
return chunk.Length;
}
this.nextChunk = chunk;
}
return 0;
}
/// <summary>
/// Reads a chunk from the stream.
/// </summary>
@ -1353,6 +1406,15 @@ namespace SixLabors.ImageSharp.Formats.Png
/// </returns>
private bool TryReadChunk(out PngChunk chunk)
{
if (this.nextChunk != null)
{
chunk = this.nextChunk.Value;
this.nextChunk = null;
return true;
}
int length = this.ReadChunkLength();
if (length == -1)

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

@ -17,12 +17,12 @@ namespace SixLabors.ImageSharp.Formats.Png
/// Gets or sets the number of bits per sample or per palette index (not per pixel).
/// Not all values are allowed for all <see cref="ColorType"/> values.
/// </summary>
public PngBitDepth BitDepth { get; set; } = PngBitDepth.Bit8;
public PngBitDepth? BitDepth { get; set; }
/// <summary>
/// Gets or sets the color type.
/// </summary>
public PngColorType ColorType { get; set; } = PngColorType.RgbWithAlpha;
public PngColorType? ColorType { get; set; }
/// <summary>
/// Gets or sets the filter method.
@ -36,30 +36,21 @@ namespace SixLabors.ImageSharp.Formats.Png
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.
/// Gets or sets the gamma value, that will be written the the image.
/// </summary>
/// <value>The gamma value of the image.</value>
public float Gamma { get; set; } = 2.2F;
public float? Gamma { get; set; }
/// <summary>
/// Gets or sets quantizer for reducing the color count.
/// Defaults to the <see cref="WuQuantizer"/>
/// </summary>
public IQuantizer Quantizer { get; set; } = new WuQuantizer();
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}"/>.
/// </summary>

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

@ -4,7 +4,6 @@
using System;
using System.Buffers.Binary;
using System.IO;
using System.Linq;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Formats.Png.Filters;
@ -45,49 +44,49 @@ namespace SixLabors.ImageSharp.Formats.Png
private readonly Crc32 crc = new Crc32();
/// <summary>
/// The png bit depth
/// The png filter method.
/// </summary>
private readonly PngBitDepth pngBitDepth;
private readonly PngFilterMethod pngFilterMethod;
/// <summary>
/// Gets or sets a value indicating whether to use 16 bit encoding for supported color types.
/// Gets or sets the CompressionLevel value
/// </summary>
private readonly bool use16Bit;
private readonly int compressionLevel;
/// <summary>
/// The png color type.
/// Gets or sets the alpha threshold value
/// </summary>
private readonly PngColorType pngColorType;
private readonly byte threshold;
/// <summary>
/// The png filter method.
/// The quantizer for reducing the color count.
/// </summary>
private readonly PngFilterMethod pngFilterMethod;
private IQuantizer quantizer;
/// <summary>
/// The quantizer for reducing the color count.
/// Gets or sets a value indicating whether to write the gamma chunk
/// </summary>
private readonly IQuantizer quantizer;
private bool writeGamma;
/// <summary>
/// Gets or sets the CompressionLevel value
/// The png bit depth
/// </summary>
private readonly int compressionLevel;
private PngBitDepth? pngBitDepth;
/// <summary>
/// Gets or sets the Gamma value
/// Gets or sets a value indicating whether to use 16 bit encoding for supported color types.
/// </summary>
private readonly float gamma;
private bool use16Bit;
/// <summary>
/// Gets or sets the Threshold value
/// The png color type.
/// </summary>
private readonly byte threshold;
private PngColorType? pngColorType;
/// <summary>
/// Gets or sets a value indicating whether to Write Gamma
/// Gets or sets the Gamma value
/// </summary>
private readonly bool writeGamma;
private float? gamma;
/// <summary>
/// The image width.
@ -158,14 +157,12 @@ namespace SixLabors.ImageSharp.Formats.Png
{
this.memoryAllocator = memoryAllocator;
this.pngBitDepth = options.BitDepth;
this.use16Bit = this.pngBitDepth.Equals(PngBitDepth.Bit16);
this.pngColorType = options.ColorType;
this.pngFilterMethod = options.FilterMethod;
this.compressionLevel = options.CompressionLevel;
this.gamma = options.Gamma;
this.quantizer = options.Quantizer;
this.threshold = options.Threshold;
this.writeGamma = options.WriteGamma;
}
/// <summary>
@ -183,23 +180,42 @@ namespace SixLabors.ImageSharp.Formats.Png
this.width = image.Width;
this.height = image.Height;
// Always take the encoder options over the metadata values.
ImageMetaData metaData = image.MetaData;
PngMetaData pngMetaData = metaData.GetFormatMetaData(PngFormat.Instance);
this.gamma = this.gamma ?? pngMetaData.Gamma;
this.writeGamma = this.gamma > 0;
this.pngColorType = this.pngColorType ?? pngMetaData.ColorType;
this.pngBitDepth = this.pngBitDepth ?? pngMetaData.BitDepth;
this.use16Bit = this.pngBitDepth.Equals(PngBitDepth.Bit16);
stream.Write(PngConstants.HeaderBytes, 0, PngConstants.HeaderBytes.Length);
QuantizedFrame<TPixel> quantized = null;
ReadOnlySpan<byte> quantizedPixelsSpan = default;
if (this.pngColorType == PngColorType.Palette)
{
byte bits;
// Use the metadata to determine what quantization depth to use if no quantizer has been set.
if (this.quantizer == null)
{
bits = (byte)Math.Min(8u, (short)this.pngBitDepth);
int colorSize = ImageMaths.GetColorCountForBitDepth(bits);
this.quantizer = new WuQuantizer(colorSize);
}
// Create quantized frame returning the palette and set the bit depth.
quantized = this.quantizer.CreateFrameQuantizer<TPixel>().QuantizeFrame(image.Frames.RootFrame);
quantizedPixelsSpan = quantized.GetPixelSpan();
byte bits = (byte)ImageMaths.GetBitsNeededForColorDepth(quantized.Palette.Length).Clamp(1, 8);
bits = (byte)ImageMaths.GetBitsNeededForColorDepth(quantized.Palette.Length).Clamp(1, 8);
// Png only supports in four pixel depths: 1, 2, 4, and 8 bits when using the PLTE chunk
if (bits == 3)
{
bits = 4;
}
else if (bits >= 5 || bits <= 7)
else if (bits >= 5 && bits <= 7)
{
bits = 8;
}
@ -217,7 +233,7 @@ namespace SixLabors.ImageSharp.Formats.Png
width: image.Width,
height: image.Height,
bitDepth: this.bitDepth,
colorType: this.pngColorType,
colorType: this.pngColorType.Value,
compressionMethod: 0, // None
filterMethod: 0,
interlaceMethod: 0); // TODO: Can't write interlaced yet.
@ -227,12 +243,12 @@ namespace SixLabors.ImageSharp.Formats.Png
// Collect the indexed pixel data
if (quantized != null)
{
this.WritePaletteChunk(stream, header, quantized);
this.WritePaletteChunk(stream, quantized);
}
this.WritePhysicalChunk(stream, image);
this.WritePhysicalChunk(stream, metaData);
this.WriteGammaChunk(stream);
this.WriteExifChunk(stream, image);
this.WriteExifChunk(stream, metaData);
this.WriteDataChunks(image.Frames.RootFrame, quantizedPixelsSpan, stream);
this.WriteEndChunk(stream);
stream.Flush();
@ -539,30 +555,27 @@ namespace SixLabors.ImageSharp.Formats.Png
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <param name="header">The <see cref="PngHeader"/>.</param>
/// <param name="quantized">The quantized frame.</param>
private void WritePaletteChunk<TPixel>(Stream stream, in PngHeader header, QuantizedFrame<TPixel> quantized)
private void WritePaletteChunk<TPixel>(Stream stream, QuantizedFrame<TPixel> quantized)
where TPixel : struct, IPixel<TPixel>
{
// Grab the palette and write it to the stream.
TPixel[] palette = quantized.Palette;
byte pixelCount = palette.Length.ToByte();
// Get max colors for bit depth.
int colorTableLength = (int)Math.Pow(2, header.BitDepth) * 3;
int paletteLength = Math.Min(palette.Length, 256);
int colorTableLength = paletteLength * 3;
Rgba32 rgba = default;
bool anyAlpha = false;
using (IManagedByteBuffer colorTable = this.memoryAllocator.AllocateManagedByteBuffer(colorTableLength))
using (IManagedByteBuffer alphaTable = this.memoryAllocator.AllocateManagedByteBuffer(pixelCount))
using (IManagedByteBuffer alphaTable = this.memoryAllocator.AllocateManagedByteBuffer(paletteLength))
{
Span<byte> colorTableSpan = colorTable.GetSpan();
Span<byte> alphaTableSpan = alphaTable.GetSpan();
Span<byte> quantizedSpan = quantized.GetPixelSpan();
for (byte i = 0; i < pixelCount; i++)
for (int i = 0; i < paletteLength; i++)
{
if (quantizedSpan.IndexOf(i) > -1)
if (quantizedSpan.IndexOf((byte)i) > -1)
{
int offset = i * 3;
palette[i].ToRgba32(ref rgba);
@ -588,7 +601,7 @@ namespace SixLabors.ImageSharp.Formats.Png
// Write the transparency data
if (anyAlpha)
{
this.WriteChunk(stream, PngChunkType.PaletteAlpha, alphaTable.Array, 0, pixelCount);
this.WriteChunk(stream, PngChunkType.PaletteAlpha, alphaTable.Array, 0, paletteLength);
}
}
}
@ -596,11 +609,9 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <summary>
/// Writes the physical dimension information to the stream.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <param name="image">The image.</param>
private void WritePhysicalChunk<TPixel>(Stream stream, Image<TPixel> image)
where TPixel : struct, IPixel<TPixel>
/// <param name="meta">The image meta data.</param>
private void WritePhysicalChunk(Stream stream, ImageMetaData meta)
{
// The pHYs chunk specifies the intended pixel size or aspect ratio for display of the image. It contains:
// Pixels per unit, X axis: 4 bytes (unsigned integer)
@ -612,7 +623,6 @@ namespace SixLabors.ImageSharp.Formats.Png
// 1: unit is the meter
//
// When the unit specifier is 0, the pHYs chunk defines pixel aspect ratio only; the actual size of the pixels remains unspecified.
ImageMetaData meta = image.MetaData;
Span<byte> hResolution = this.chunkDataBuffer.AsSpan(0, 4);
Span<byte> vResolution = this.chunkDataBuffer.AsSpan(4, 4);
@ -653,16 +663,14 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <summary>
/// Writes the eXIf chunk to the stream, if any EXIF Profile values are present in the meta data.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <param name="image">The image.</param>
private void WriteExifChunk<TPixel>(Stream stream, Image<TPixel> image)
where TPixel : struct, IPixel<TPixel>
/// <param name="meta">The image meta data.</param>
private void WriteExifChunk(Stream stream, ImageMetaData meta)
{
if (image.MetaData.ExifProfile?.Values.Count > 0)
if (meta.ExifProfile?.Values.Count > 0)
{
image.MetaData.SyncProfiles();
this.WriteChunk(stream, PngChunkType.Exif, image.MetaData.ExifProfile.ToByteArray());
meta.SyncProfiles();
this.WriteChunk(stream, PngChunkType.Exif, meta.ExifProfile.ToByteArray());
}
}
@ -693,7 +701,7 @@ namespace SixLabors.ImageSharp.Formats.Png
private void WriteDataChunks<TPixel>(ImageFrame<TPixel> pixels, ReadOnlySpan<byte> quantizedPixelsSpan, Stream stream)
where TPixel : struct, IPixel<TPixel>
{
this.bytesPerScanline = this.width * this.bytesPerPixel;
this.bytesPerScanline = this.CalculateScanlineLength(this.width);
int resultLength = this.bytesPerScanline + 1;
this.previousScanline = this.memoryAllocator.AllocateManagedByteBuffer(this.bytesPerScanline, AllocationOptions.Clean);
@ -781,10 +789,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// Writes the chunk end to the stream.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
private void WriteEndChunk(Stream stream)
{
this.WriteChunk(stream, PngChunkType.End, null);
}
private void WriteEndChunk(Stream stream) => this.WriteChunk(stream, PngChunkType.End, null);
/// <summary>
/// Writes a chunk to the stream.
@ -792,10 +797,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="type">The type of chunk to write.</param>
/// <param name="data">The <see cref="T:byte[]"/> containing data.</param>
private void WriteChunk(Stream stream, PngChunkType type, byte[] data)
{
this.WriteChunk(stream, type, data, 0, data?.Length ?? 0);
}
private void WriteChunk(Stream stream, PngChunkType type, byte[] data) => this.WriteChunk(stream, type, data, 0, data?.Length ?? 0);
/// <summary>
/// Writes a chunk of a specified length to the stream at the given offset.
@ -827,5 +829,26 @@ namespace SixLabors.ImageSharp.Formats.Png
stream.Write(this.buffer, 0, 4); // write the crc
}
/// <summary>
/// Calculates the scanline length.
/// </summary>
/// <param name="width">The width of the row.</param>
/// <returns>
/// The <see cref="int"/> representing the length.
/// </returns>
private int CalculateScanlineLength(int width)
{
int mod = this.bitDepth == 16 ? 16 : 8;
int scanlineLength = width * this.bitDepth * this.bytesPerPixel;
int amount = scanlineLength % mod;
if (amount != 0)
{
scanlineLength += mod - amount;
}
return scanlineLength / mod;
}
}
}

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

@ -8,8 +8,17 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <summary>
/// Registers the image encoders, decoders and mime type detectors for the png format.
/// </summary>
internal sealed class PngFormat : IImageFormat
public sealed class PngFormat : IImageFormat<PngMetaData>
{
private PngFormat()
{
}
/// <summary>
/// Gets the current instance.
/// </summary>
public static PngFormat Instance { get; } = new PngFormat();
/// <inheritdoc/>
public string Name => "PNG";
@ -21,5 +30,8 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <inheritdoc/>
public IEnumerable<string> FileExtensions => PngConstants.FileExtensions;
/// <inheritdoc/>
public PngMetaData CreateDefaultFormatMetaData() => new PngMetaData();
}
}

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

@ -17,7 +17,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <inheritdoc/>
public IImageFormat DetectFormat(ReadOnlySpan<byte> header)
{
return this.IsSupportedFileFormat(header) ? ImageFormats.Png : null;
return this.IsSupportedFileFormat(header) ? PngFormat.Instance : null;
}
private bool IsSupportedFileFormat(ReadOnlySpan<byte> header)

48
src/ImageSharp/Formats/Png/PngMetaData.cs

@ -0,0 +1,48 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Png
{
/// <summary>
/// Provides Png specific metadata information for the image.
/// </summary>
public class PngMetaData : IDeepCloneable
{
/// <summary>
/// Initializes a new instance of the <see cref="PngMetaData"/> class.
/// </summary>
public PngMetaData()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="PngMetaData"/> class.
/// </summary>
/// <param name="other">The metadata to create an instance from.</param>
private PngMetaData(PngMetaData other)
{
this.BitDepth = other.BitDepth;
this.ColorType = other.ColorType;
this.Gamma = other.Gamma;
}
/// <summary>
/// Gets or sets the number of bits per sample or per palette index (not per pixel).
/// Not all values are allowed for all <see cref="ColorType"/> values.
/// </summary>
public PngBitDepth BitDepth { get; set; } = PngBitDepth.Bit8;
/// <summary>
/// Gets or sets the color type.
/// </summary>
public PngColorType ColorType { get; set; } = PngColorType.RgbWithAlpha;
/// <summary>
/// Gets or sets the gamma value for the image.
/// </summary>
public float Gamma { get; set; }
/// <inheritdoc/>
public IDeepCloneable DeepClone() => new PngMetaData(this);
}
}

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

@ -43,22 +43,24 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
private bool isDisposed;
/// <summary>
/// Whether the crc value has been read.
/// The current data remaining to be read
/// </summary>
private bool crcRead;
private int currentDataRemaining;
/// <summary>
/// The current data remaining to be read
/// Delegate to get more data once we've exhausted the current data remaining
/// </summary>
private int currentDataRemaining;
private Func<int> getData;
/// <summary>
/// Initializes a new instance of the <see cref="ZlibInflateStream"/> class.
/// </summary>
/// <param name="innerStream">The inner raw stream</param>
public ZlibInflateStream(Stream innerStream)
/// <param name="getData">A delegate to get more data from the inner stream</param>
public ZlibInflateStream(Stream innerStream, Func<int> getData)
{
this.innerStream = innerStream;
this.getData = getData;
}
/// <inheritdoc/>
@ -112,12 +114,36 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
{
if (this.currentDataRemaining == 0)
{
return 0;
// last buffer was read in its entirety, let's make sure we don't actually have more
this.currentDataRemaining = this.getData();
if (this.currentDataRemaining == 0)
{
return 0;
}
}
int bytesToRead = Math.Min(count, this.currentDataRemaining);
this.currentDataRemaining -= bytesToRead;
return this.innerStream.Read(buffer, offset, bytesToRead);
int bytesRead = this.innerStream.Read(buffer, offset, bytesToRead);
// keep reading data until we've reached the end of the stream or filled the buffer
while (this.currentDataRemaining == 0 && bytesRead < count)
{
this.currentDataRemaining = this.getData();
if (this.currentDataRemaining == 0)
{
return bytesRead;
}
offset += bytesRead;
bytesToRead = Math.Min(count - bytesRead, this.currentDataRemaining);
this.currentDataRemaining -= bytesToRead;
bytesRead += this.innerStream.Read(buffer, offset, bytesToRead);
}
return bytesRead;
}
/// <inheritdoc/>
@ -153,14 +179,6 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
{
this.compressedStream.Dispose();
this.compressedStream = null;
if (!this.crcRead)
{
// Consume the trailing 4 bytes
this.innerStream.Read(ChecksumBuffer, 0, 4);
this.currentDataRemaining -= 4;
this.crcRead = true;
}
}
}

31
src/ImageSharp/IDeepCloneable.cs

@ -0,0 +1,31 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp
{
/// <summary>
/// A generic interface for a deeply cloneable type.
/// </summary>
/// <typeparam name="T">The type of object to clone.</typeparam>
public interface IDeepCloneable<out T>
where T : class
{
/// <summary>
/// Creates a new <typeparamref name="T"/> that is a deep copy of the current instance.
/// </summary>
/// <returns>The <typeparamref name="T"/>.</returns>
T DeepClone();
}
/// <summary>
/// An interface for objects that can be cloned. This creates a deep copy of the object.
/// </summary>
public interface IDeepCloneable
{
/// <summary>
/// Creates a new object that is a deep copy of the current instance.
/// </summary>
/// <returns>The <see cref="IDeepCloneable"/>.</returns>
IDeepCloneable DeepClone();
}
}

37
src/ImageSharp/ImageFormats.cs

@ -1,37 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Bmp;
using SixLabors.ImageSharp.Formats.Gif;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Png;
namespace SixLabors.ImageSharp
{
/// <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 Bmp = new BmpFormat();
}
}

18
src/ImageSharp/ImageFrameCollection.cs

@ -94,7 +94,7 @@ namespace SixLabors.ImageSharp
public ImageFrame<TPixel> InsertFrame(int index, ImageFrame<TPixel> source)
{
this.ValidateFrame(source);
ImageFrame<TPixel> clonedFrame = source.Clone();
ImageFrame<TPixel> clonedFrame = source.Clone(this.parent.GetConfiguration());
this.frames.Insert(index, clonedFrame);
return clonedFrame;
}
@ -107,7 +107,7 @@ namespace SixLabors.ImageSharp
public ImageFrame<TPixel> AddFrame(ImageFrame<TPixel> source)
{
this.ValidateFrame(source);
ImageFrame<TPixel> clonedFrame = source.Clone();
ImageFrame<TPixel> clonedFrame = source.Clone(this.parent.GetConfiguration());
this.frames.Add(clonedFrame);
return clonedFrame;
}
@ -155,10 +155,7 @@ namespace SixLabors.ImageSharp
/// <returns>
/// <c>true</c> if the <seealso cref="ImageFrameCollection{TPixel}"/> contains the specified frame; otherwise, <c>false</c>.
/// </returns>
public bool Contains(ImageFrame<TPixel> frame)
{
return this.frames.Contains(frame);
}
public bool Contains(ImageFrame<TPixel> frame) => this.frames.Contains(frame);
/// <summary>
/// Moves an <seealso cref="ImageFrame{TPixel}"/> from <paramref name="sourceIndex"/> to <paramref name="destinationIndex"/>.
@ -195,7 +192,7 @@ namespace SixLabors.ImageSharp
this.frames.Remove(frame);
return new Image<TPixel>(this.parent.GetConfiguration(), this.parent.MetaData.Clone(), new[] { frame });
return new Image<TPixel>(this.parent.GetConfiguration(), this.parent.MetaData.DeepClone(), new[] { frame });
}
/// <summary>
@ -208,7 +205,7 @@ namespace SixLabors.ImageSharp
{
ImageFrame<TPixel> frame = this[index];
ImageFrame<TPixel> clonedFrame = frame.Clone();
return new Image<TPixel>(this.parent.GetConfiguration(), this.parent.MetaData.Clone(), new[] { clonedFrame });
return new Image<TPixel>(this.parent.GetConfiguration(), this.parent.MetaData.DeepClone(), new[] { clonedFrame });
}
/// <summary>
@ -217,10 +214,7 @@ namespace SixLabors.ImageSharp
/// <returns>
/// The new <see cref="ImageFrame{TPixel}" />.
/// </returns>
public ImageFrame<TPixel> CreateFrame()
{
return this.CreateFrame(default);
}
public ImageFrame<TPixel> CreateFrame() => this.CreateFrame(default);
/// <summary>
/// Creates a new <seealso cref="ImageFrame{TPixel}" /> and appends it to the end of the collection.

73
src/ImageSharp/ImageFrame{TPixel}.cs

@ -84,18 +84,21 @@ namespace SixLabors.ImageSharp
Guard.NotNull(configuration, nameof(configuration));
Guard.MustBeGreaterThan(width, 0, nameof(width));
Guard.MustBeGreaterThan(height, 0, nameof(height));
Guard.NotNull(metaData, nameof(metaData));
this.configuration = configuration;
this.MemoryAllocator = configuration.MemoryAllocator;
this.PixelBuffer = this.MemoryAllocator.Allocate2D<TPixel>(width, height);
this.MetaData = metaData;
this.MetaData = metaData ?? new ImageFrameMetaData();
this.Clear(configuration.GetParallelOptions(), backgroundColor);
}
/// <summary>
/// Initializes a new instance of the <see cref="ImageFrame{TPixel}" /> class wrapping an existing buffer.
/// </summary>
/// <param name="configuration">The configuration providing initialization code which allows extending the library.</param>
/// <param name="width">The width of the image in pixels.</param>
/// <param name="height">The height of the image in pixels.</param>
/// <param name="memorySource">The memory source.</param>
internal ImageFrame(Configuration configuration, int width, int height, MemorySource<TPixel> memorySource)
: this(configuration, width, height, memorySource, new ImageFrameMetaData())
{
@ -104,12 +107,12 @@ namespace SixLabors.ImageSharp
/// <summary>
/// Initializes a new instance of the <see cref="ImageFrame{TPixel}" /> class wrapping an existing buffer.
/// </summary>
internal ImageFrame(
Configuration configuration,
int width,
int height,
MemorySource<TPixel> memorySource,
ImageFrameMetaData metaData)
/// <param name="configuration">The configuration providing initialization code which allows extending the library.</param>
/// <param name="width">The width of the image in pixels.</param>
/// <param name="height">The height of the image in pixels.</param>
/// <param name="memorySource">The memory source.</param>
/// <param name="metaData">The meta data.</param>
internal ImageFrame(Configuration configuration, int width, int height, MemorySource<TPixel> memorySource, ImageFrameMetaData metaData)
{
Guard.NotNull(configuration, nameof(configuration));
Guard.MustBeGreaterThan(width, 0, nameof(width));
@ -136,7 +139,7 @@ namespace SixLabors.ImageSharp
this.MemoryAllocator = configuration.MemoryAllocator;
this.PixelBuffer = this.MemoryAllocator.Allocate2D<TPixel>(source.PixelBuffer.Width, source.PixelBuffer.Height);
source.PixelBuffer.GetSpan().CopyTo(this.PixelBuffer.GetSpan());
this.MetaData = source.MetaData.Clone();
this.MetaData = source.MetaData.DeepClone();
}
/// <summary>
@ -201,10 +204,7 @@ namespace SixLabors.ImageSharp
/// <param name="y">The y-coordinate of the pixel. Must be greater than or equal to zero and less than the height of the image.</param>
/// <returns>The <see typeparam="TPixel"/> at the specified position.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal ref TPixel GetPixelReference(int x, int y)
{
return ref this.PixelBuffer[x, y];
}
internal ref TPixel GetPixelReference(int x, int y) => ref this.PixelBuffer[x, y];
/// <summary>
/// Copies the pixels to a <see cref="Buffer2D{TPixel}"/> of the same size.
@ -249,10 +249,20 @@ namespace SixLabors.ImageSharp
}
/// <inheritdoc/>
public override string ToString()
{
return $"ImageFrame<{typeof(TPixel).Name}>: {this.Width}x{this.Height}";
}
public override string ToString() => $"ImageFrame<{typeof(TPixel).Name}>: {this.Width}x{this.Height}";
/// <summary>
/// Clones the current instance.
/// </summary>
/// <returns>The <see cref="ImageFrame{TPixel}"/></returns>
internal ImageFrame<TPixel> Clone() => this.Clone(this.configuration);
/// <summary>
/// Clones the current instance.
/// </summary>
/// <param name="configuration">The configuration providing initialization code which allows extending the library.</param>
/// <returns>The <see cref="ImageFrame{TPixel}"/></returns>
internal ImageFrame<TPixel> Clone(Configuration configuration) => new ImageFrame<TPixel>(configuration, this);
/// <summary>
/// Returns a copy of the image frame in the given pixel format.
@ -260,19 +270,28 @@ namespace SixLabors.ImageSharp
/// <typeparam name="TPixel2">The pixel format.</typeparam>
/// <returns>The <see cref="ImageFrame{TPixel2}"/></returns>
internal ImageFrame<TPixel2> CloneAs<TPixel2>()
where TPixel2 : struct, IPixel<TPixel2> => this.CloneAs<TPixel2>(this.configuration);
/// <summary>
/// Returns a copy of the image frame in the given pixel format.
/// </summary>
/// <typeparam name="TPixel2">The pixel format.</typeparam>
/// <param name="configuration">The configuration providing initialization code which allows extending the library.</param>
/// <returns>The <see cref="ImageFrame{TPixel2}"/></returns>
internal ImageFrame<TPixel2> CloneAs<TPixel2>(Configuration configuration)
where TPixel2 : struct, IPixel<TPixel2>
{
if (typeof(TPixel2) == typeof(TPixel))
{
return this.Clone() as ImageFrame<TPixel2>;
return this.Clone(configuration) as ImageFrame<TPixel2>;
}
var target = new ImageFrame<TPixel2>(this.configuration, this.Width, this.Height, this.MetaData.Clone());
var target = new ImageFrame<TPixel2>(configuration, this.Width, this.Height, this.MetaData.DeepClone());
ParallelFor.WithTemporaryBuffer(
0,
this.Height,
this.configuration,
configuration,
this.Width,
(int y, IMemoryOwner<Vector4> tempRowBuffer) =>
{
@ -305,19 +324,7 @@ namespace SixLabors.ImageSharp
});
}
/// <summary>
/// Clones the current instance.
/// </summary>
/// <returns>The <see cref="ImageFrame{TPixel}"/></returns>
internal ImageFrame<TPixel> Clone()
{
return new ImageFrame<TPixel>(this.configuration, this);
}
/// <inheritdoc/>
void IDisposable.Dispose()
{
this.Dispose();
}
void IDisposable.Dispose() => this.Dispose();
}
}

88
src/ImageSharp/Image{TPixel}.cs

@ -11,7 +11,6 @@ using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.MetaData;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Memory;
namespace SixLabors.ImageSharp
{
@ -23,15 +22,12 @@ namespace SixLabors.ImageSharp
where TPixel : struct, IPixel<TPixel>
{
private readonly Configuration configuration;
private readonly ImageFrameCollection<TPixel> frames;
/// <summary>
/// Initializes a new instance of the <see cref="Image{TPixel}"/> class
/// with the height and the width of the image.
/// </summary>
/// <param name="configuration">
/// The configuration providing initialization code which allows extending the library.
/// </param>
/// <param name="configuration">The configuration providing initialization code which allows extending the library.</param>
/// <param name="width">The width of the image in pixels.</param>
/// <param name="height">The height of the image in pixels.</param>
public Image(Configuration configuration, int width, int height)
@ -43,9 +39,7 @@ namespace SixLabors.ImageSharp
/// Initializes a new instance of the <see cref="Image{TPixel}"/> class
/// with the height and the width of the image.
/// </summary>
/// <param name="configuration">
/// The configuration providing initialization code which allows extending the library.
/// </param>
/// <param name="configuration">The configuration providing initialization code which allows extending the library.</param>
/// <param name="width">The width of the image in pixels.</param>
/// <param name="height">The height of the image in pixels.</param>
/// <param name="backgroundColor">The color to initialize the pixels with.</param>
@ -69,9 +63,7 @@ namespace SixLabors.ImageSharp
/// Initializes a new instance of the <see cref="Image{TPixel}"/> class
/// with the height and the width of the image.
/// </summary>
/// <param name="configuration">
/// The configuration providing initialization code which allows extending the library.
/// </param>
/// <param name="configuration">The configuration providing initialization code which allows extending the library.</param>
/// <param name="width">The width of the image in pixels.</param>
/// <param name="height">The height of the image in pixels.</param>
/// <param name="metadata">The images metadata.</param>
@ -80,37 +72,41 @@ namespace SixLabors.ImageSharp
this.configuration = configuration ?? Configuration.Default;
this.PixelType = new PixelTypeInfo(Unsafe.SizeOf<TPixel>() * 8);
this.MetaData = metadata ?? new ImageMetaData();
this.frames = new ImageFrameCollection<TPixel>(this, width, height, default(TPixel));
this.Frames = new ImageFrameCollection<TPixel>(this, width, height, default(TPixel));
}
/// <summary>
/// Initializes a new instance of the <see cref="Image{TPixel}"/> class
/// wrapping an external <see cref="MemorySource{T}"/>
/// </summary>
/// <param name="configuration">The configuration providing initialization code which allows extending the library.</param>
/// <param name="memorySource">The memory source.</param>
/// <param name="width">The width of the image in pixels.</param>
/// <param name="height">The height of the image in pixels.</param>
/// <param name="metadata">The images metadata.</param>
internal Image(Configuration configuration, MemorySource<TPixel> memorySource, int width, int height, ImageMetaData metadata)
{
this.configuration = configuration;
this.PixelType = new PixelTypeInfo(Unsafe.SizeOf<TPixel>() * 8);
this.MetaData = metadata;
this.frames = new ImageFrameCollection<TPixel>(this, width, height, memorySource);
this.Frames = new ImageFrameCollection<TPixel>(this, width, height, memorySource);
}
/// <summary>
/// Initializes a new instance of the <see cref="Image{TPixel}"/> class
/// with the height and the width of the image.
/// </summary>
/// <param name="configuration">
/// The configuration providing initialization code which allows extending the library.
/// </param>
/// <param name="configuration">The configuration providing initialization code which allows extending the library.</param>
/// <param name="width">The width of the image in pixels.</param>
/// <param name="height">The height of the image in pixels.</param>
/// <param name="backgroundColor">The color to initialize the pixels with.</param>
/// <param name="metadata">The images metadata.</param>
internal Image(Configuration configuration, int width, int height, TPixel backgroundColor, ImageMetaData metadata) {
internal Image(Configuration configuration, int width, int height, TPixel backgroundColor, ImageMetaData metadata)
{
this.configuration = configuration ?? Configuration.Default;
this.PixelType = new PixelTypeInfo(Unsafe.SizeOf<TPixel>() * 8);
this.MetaData = metadata ?? new ImageMetaData();
this.frames = new ImageFrameCollection<TPixel>(this, width, height, backgroundColor);
this.Frames = new ImageFrameCollection<TPixel>(this, width, height, backgroundColor);
}
/// <summary>
@ -126,7 +122,7 @@ namespace SixLabors.ImageSharp
this.PixelType = new PixelTypeInfo(Unsafe.SizeOf<TPixel>() * 8);
this.MetaData = metadata ?? new ImageMetaData();
this.frames = new ImageFrameCollection<TPixel>(this, frames);
this.Frames = new ImageFrameCollection<TPixel>(this, frames);
}
/// <summary>
@ -138,10 +134,10 @@ namespace SixLabors.ImageSharp
public PixelTypeInfo PixelType { get; }
/// <inheritdoc/>
public int Width => this.frames.RootFrame.Width;
public int Width => this.Frames.RootFrame.Width;
/// <inheritdoc/>
public int Height => this.frames.RootFrame.Height;
public int Height => this.Frames.RootFrame.Height;
/// <inheritdoc/>
public ImageMetaData MetaData { get; }
@ -149,12 +145,12 @@ namespace SixLabors.ImageSharp
/// <summary>
/// Gets the frames.
/// </summary>
public ImageFrameCollection<TPixel> Frames => this.frames;
public ImageFrameCollection<TPixel> Frames { get; }
/// <summary>
/// Gets the root frame.
/// </summary>
private IPixelSource<TPixel> PixelSource => this.frames?.RootFrame ?? throw new ObjectDisposedException(nameof(Image<TPixel>));
private IPixelSource<TPixel> PixelSource => this.Frames?.RootFrame ?? throw new ObjectDisposedException(nameof(Image<TPixel>));
/// <summary>
/// Gets or sets the pixel at the specified position.
@ -187,16 +183,17 @@ namespace SixLabors.ImageSharp
/// Clones the current image
/// </summary>
/// <returns>Returns a new image with all the same metadata as the original.</returns>
public Image<TPixel> Clone()
{
IEnumerable<ImageFrame<TPixel>> clonedFrames = this.frames.Select(x => x.Clone());
return new Image<TPixel>(this.configuration, this.MetaData.Clone(), clonedFrames);
}
public Image<TPixel> Clone() => this.Clone(this.configuration);
/// <inheritdoc/>
public override string ToString()
/// <summary>
/// Clones the current image with the given configueation.
/// </summary>
/// <param name="configuration">The configuration providing initialization code which allows extending the library.</param>
/// <returns>Returns a new <see cref="Image{TPixel}"/> with all the same pixel data as the original.</returns>
public Image<TPixel> Clone(Configuration configuration)
{
return $"Image<{typeof(TPixel).Name}>: {this.Width}x{this.Height}";
IEnumerable<ImageFrame<TPixel>> clonedFrames = this.Frames.Select(x => x.Clone(configuration));
return new Image<TPixel>(configuration, this.MetaData.DeepClone(), clonedFrames);
}
/// <summary>
@ -205,22 +202,27 @@ namespace SixLabors.ImageSharp
/// <typeparam name="TPixel2">The pixel format.</typeparam>
/// <returns>The <see cref="Image{TPixel2}"/></returns>
public Image<TPixel2> CloneAs<TPixel2>()
where TPixel2 : struct, IPixel<TPixel2>
{
IEnumerable<ImageFrame<TPixel2>> clonedFrames = this.frames.Select(x => x.CloneAs<TPixel2>());
var target = new Image<TPixel2>(this.configuration, this.MetaData.Clone(), clonedFrames);
return target;
}
where TPixel2 : struct, IPixel<TPixel2> => this.CloneAs<TPixel2>(this.configuration);
/// <summary>
/// Releases managed resources.
/// Returns a copy of the image in the given pixel format.
/// </summary>
public void Dispose()
/// <typeparam name="TPixel2">The pixel format.</typeparam>
/// <param name="configuration">The configuration providing initialization code which allows extending the library.</param>
/// <returns>The <see cref="Image{TPixel2}"/></returns>
public Image<TPixel2> CloneAs<TPixel2>(Configuration configuration)
where TPixel2 : struct, IPixel<TPixel2>
{
this.frames.Dispose();
IEnumerable<ImageFrame<TPixel2>> clonedFrames = this.Frames.Select(x => x.CloneAs<TPixel2>(configuration));
return new Image<TPixel2>(configuration, this.MetaData.DeepClone(), clonedFrames);
}
/// <inheritdoc/>
public void Dispose() => this.Frames.Dispose();
/// <inheritdoc/>
public override string ToString() => $"Image<{typeof(TPixel).Name}>: {this.Width}x{this.Height}";
/// <summary>
/// Switches the buffers used by the image and the pixelSource meaning that the Image will "own" the buffer from the pixelSource and the pixelSource will now own the Images buffer.
/// </summary>
@ -229,9 +231,9 @@ namespace SixLabors.ImageSharp
{
Guard.NotNull(pixelSource, nameof(pixelSource));
for (int i = 0; i < this.frames.Count; i++)
for (int i = 0; i < this.Frames.Count; i++)
{
this.frames[i].SwapOrCopyPixelsBufferFrom(pixelSource.frames[i]);
this.Frames[i].SwapOrCopyPixelsBufferFrom(pixelSource.Frames[i]);
}
}
}

2
src/ImageSharp/Formats/Gif/FrameDecodingMode.cs → src/ImageSharp/MetaData/FrameDecodingMode.cs

@ -1,7 +1,7 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Gif
namespace SixLabors.ImageSharp.MetaData
{
/// <summary>
/// Enumerated frame process modes to apply to multi-frame images.

51
src/ImageSharp/MetaData/ImageFrameMetaData.cs

@ -1,15 +1,18 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Formats.Gif;
using System.Collections.Generic;
using SixLabors.ImageSharp.Formats;
namespace SixLabors.ImageSharp.MetaData
{
/// <summary>
/// Encapsulates the metadata of an image frame.
/// </summary>
public sealed class ImageFrameMetaData
public sealed class ImageFrameMetaData : IDeepCloneable<ImageFrameMetaData>
{
private readonly Dictionary<IImageFormat, IDeepCloneable> formatMetaData = new Dictionary<IImageFormat, IDeepCloneable>();
/// <summary>
/// Initializes a new instance of the <see cref="ImageFrameMetaData"/> class.
/// </summary>
@ -28,32 +31,36 @@ namespace SixLabors.ImageSharp.MetaData
{
DebugGuard.NotNull(other, nameof(other));
this.FrameDelay = other.FrameDelay;
this.DisposalMethod = other.DisposalMethod;
foreach (KeyValuePair<IImageFormat, IDeepCloneable> meta in other.formatMetaData)
{
this.formatMetaData.Add(meta.Key, meta.Value.DeepClone());
}
}
/// <summary>
/// Gets or sets the frame delay for animated images.
/// If not 0, when utilized in Gif animation, this field specifies the number of hundredths (1/100) of a second to
/// wait before continuing with the processing of the Data Stream.
/// The clock starts ticking immediately after the graphic is rendered.
/// </summary>
public int FrameDelay { get; set; }
/// <inheritdoc/>
public ImageFrameMetaData DeepClone() => new ImageFrameMetaData(this);
/// <summary>
/// Gets or sets the disposal method for animated images.
/// Primarily used in Gif animation, this field indicates the way in which the graphic is to
/// be treated after being displayed.
/// Gets the metadata value associated with the specified key.
/// </summary>
public DisposalMethod DisposalMethod { get; set; }
/// <summary>
/// Clones this ImageFrameMetaData.
/// </summary>
/// <returns>The cloned instance.</returns>
public ImageFrameMetaData Clone()
/// <typeparam name="TFormatMetaData">The type of format metadata.</typeparam>
/// <typeparam name="TFormatFrameMetaData">The type of format frame metadata.</typeparam>
/// <param name="key">The key of the value to get.</param>
/// <returns>
/// The <typeparamref name="TFormatFrameMetaData"/>.
/// </returns>
public TFormatFrameMetaData GetFormatMetaData<TFormatMetaData, TFormatFrameMetaData>(IImageFormat<TFormatMetaData, TFormatFrameMetaData> key)
where TFormatMetaData : class
where TFormatFrameMetaData : class, IDeepCloneable
{
return new ImageFrameMetaData(this);
if (this.formatMetaData.TryGetValue(key, out IDeepCloneable meta))
{
return (TFormatFrameMetaData)meta;
}
TFormatFrameMetaData newMeta = key.CreateDefaultFormatFrameMetaData();
this.formatMetaData[key] = newMeta;
return newMeta;
}
}
}

66
src/ImageSharp/MetaData/ImageMetaData.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System.Collections.Generic;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.MetaData.Profiles.Exif;
using SixLabors.ImageSharp.MetaData.Profiles.Icc;
@ -10,7 +11,7 @@ namespace SixLabors.ImageSharp.MetaData
/// <summary>
/// Encapsulates the metadata of an image.
/// </summary>
public sealed class ImageMetaData
public sealed class ImageMetaData : IDeepCloneable<ImageMetaData>
{
/// <summary>
/// The default horizontal resolution value (dots per inch) in x direction.
@ -24,6 +25,13 @@ namespace SixLabors.ImageSharp.MetaData
/// </summary>
public const double DefaultVerticalResolution = 96;
/// <summary>
/// The default pixel resolution units.
/// <remarks>The default value is <see cref="PixelResolutionUnit.PixelsPerInch"/>.</remarks>
/// </summary>
public const PixelResolutionUnit DefaultPixelResolutionUnits = PixelResolutionUnit.PixelsPerInch;
private readonly Dictionary<IImageFormat, IDeepCloneable> formatMetaData = new Dictionary<IImageFormat, IDeepCloneable>();
private double horizontalResolution;
private double verticalResolution;
@ -34,6 +42,7 @@ namespace SixLabors.ImageSharp.MetaData
{
this.horizontalResolution = DefaultHorizontalResolution;
this.verticalResolution = DefaultVerticalResolution;
this.ResolutionUnits = DefaultPixelResolutionUnits;
}
/// <summary>
@ -48,20 +57,19 @@ namespace SixLabors.ImageSharp.MetaData
this.HorizontalResolution = other.HorizontalResolution;
this.VerticalResolution = other.VerticalResolution;
this.ResolutionUnits = other.ResolutionUnits;
this.RepeatCount = other.RepeatCount;
foreach (KeyValuePair<IImageFormat, IDeepCloneable> meta in other.formatMetaData)
{
this.formatMetaData.Add(meta.Key, meta.Value.DeepClone());
}
foreach (ImageProperty property in other.Properties)
{
this.Properties.Add(property);
}
this.ExifProfile = other.ExifProfile != null
? new ExifProfile(other.ExifProfile)
: null;
this.IccProfile = other.IccProfile != null
? new IccProfile(other.IccProfile)
: null;
this.ExifProfile = other.ExifProfile?.DeepClone();
this.IccProfile = other.IccProfile?.DeepClone();
}
/// <summary>
@ -107,7 +115,7 @@ namespace SixLabors.ImageSharp.MetaData
/// 02 : Pixels per centimeter
/// 03 : Pixels per meter
/// </summary>
public PixelResolutionUnit ResolutionUnits { get; set; } = PixelResolutionUnit.PixelsPerInch;
public PixelResolutionUnit ResolutionUnits { get; set; }
/// <summary>
/// Gets or sets the Exif profile.
@ -125,10 +133,28 @@ namespace SixLabors.ImageSharp.MetaData
public IList<ImageProperty> Properties { get; } = new List<ImageProperty>();
/// <summary>
/// Gets or sets the number of times any animation is repeated.
/// <remarks>0 means to repeat indefinitely.</remarks>
/// Gets the metadata value associated with the specified key.
/// </summary>
public ushort RepeatCount { get; set; }
/// <typeparam name="TFormatMetaData">The type of metadata.</typeparam>
/// <param name="key">The key of the value to get.</param>
/// <returns>
/// The <typeparamref name="TFormatMetaData"/>.
/// </returns>
public TFormatMetaData GetFormatMetaData<TFormatMetaData>(IImageFormat<TFormatMetaData> key)
where TFormatMetaData : class, IDeepCloneable
{
if (this.formatMetaData.TryGetValue(key, out IDeepCloneable meta))
{
return (TFormatMetaData)meta;
}
TFormatMetaData newMeta = key.CreateDefaultFormatMetaData();
this.formatMetaData[key] = newMeta;
return newMeta;
}
/// <inheritdoc/>
public ImageMetaData DeepClone() => new ImageMetaData(this);
/// <summary>
/// Looks up a property with the provided name.
@ -153,21 +179,9 @@ namespace SixLabors.ImageSharp.MetaData
return false;
}
/// <summary>
/// Clones this into a new instance
/// </summary>
/// <returns>The cloned metadata instance</returns>
public ImageMetaData Clone()
{
return new ImageMetaData(this);
}
/// <summary>
/// Synchronizes the profiles with the current meta data.
/// </summary>
internal void SyncProfiles()
{
this.ExifProfile?.Sync(this);
}
internal void SyncProfiles() => this.ExifProfile?.Sync(this);
}
}

27
src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs

@ -12,23 +12,18 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
/// <summary>
/// Represents an EXIF profile providing access to the collection of values.
/// </summary>
public sealed class ExifProfile
public sealed class ExifProfile : IDeepCloneable<ExifProfile>
{
/// <summary>
/// The byte array to read the EXIF profile from.
/// </summary>
private byte[] data;
private readonly byte[] data;
/// <summary>
/// The collection of EXIF values
/// </summary>
private List<ExifValue> values;
/// <summary>
/// The list of invalid EXIF tags
/// </summary>
private IReadOnlyList<ExifTag> invalidTags;
/// <summary>
/// The thumbnail offset position in the byte stream
/// </summary>
@ -55,7 +50,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
{
this.Parts = ExifParts.All;
this.data = data;
this.invalidTags = new List<ExifTag>();
this.InvalidTags = new List<ExifTag>();
}
/// <summary>
@ -63,22 +58,19 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
/// by making a copy from another EXIF profile.
/// </summary>
/// <param name="other">The other EXIF profile, where the clone should be made from.</param>
/// <exception cref="System.ArgumentNullException"><paramref name="other"/> is null.</exception>
public ExifProfile(ExifProfile other)
private ExifProfile(ExifProfile other)
{
Guard.NotNull(other, nameof(other));
this.Parts = other.Parts;
this.thumbnailLength = other.thumbnailLength;
this.thumbnailOffset = other.thumbnailOffset;
this.invalidTags = new List<ExifTag>(other.invalidTags);
this.InvalidTags = new List<ExifTag>(other.InvalidTags);
if (other.values != null)
{
this.values = new List<ExifValue>(other.Values.Count);
foreach (ExifValue value in other.Values)
{
this.values.Add(new ExifValue(value));
this.values.Add(value.DeepClone());
}
}
@ -97,7 +89,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
/// <summary>
/// Gets the tags that where found but contained an invalid value.
/// </summary>
public IReadOnlyList<ExifTag> InvalidTags => this.invalidTags;
public IReadOnlyList<ExifTag> InvalidTags { get; private set; }
/// <summary>
/// Gets the values of this EXIF profile.
@ -249,6 +241,9 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
return writer.GetData();
}
/// <inheritdoc/>
public ExifProfile DeepClone() => new ExifProfile(this);
/// <summary>
/// Synchronizes the profiles with the specified meta data.
/// </summary>
@ -294,7 +289,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
this.values = reader.ReadValues();
this.invalidTags = new List<ExifTag>(reader.InvalidTags);
this.InvalidTags = new List<ExifTag>(reader.InvalidTags);
this.thumbnailOffset = (int)reader.ThumbnailOffset;
this.thumbnailLength = (int)reader.ThumbnailLength;
}

24
src/ImageSharp/MetaData/Profiles/Exif/ExifReader.cs

@ -88,19 +88,19 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
}
uint ifdOffset = this.ReadUInt32();
this.AddValues(values, (int)ifdOffset);
this.AddValues(values, ifdOffset);
uint thumbnailOffset = this.ReadUInt32();
this.GetThumbnail((int)thumbnailOffset);
this.GetThumbnail(thumbnailOffset);
if (this.exifOffset != 0)
{
this.AddValues(values, (int)this.exifOffset);
this.AddValues(values, this.exifOffset);
}
if (this.gpsOffset != 0)
{
this.AddValues(values, (int)this.gpsOffset);
this.AddValues(values, this.gpsOffset);
}
return values;
@ -153,9 +153,14 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
/// </summary>
/// <param name="values">The values.</param>
/// <param name="index">The index.</param>
private void AddValues(List<ExifValue> values, int index)
private void AddValues(List<ExifValue> values, uint index)
{
this.position = index;
if (index > (uint)this.exifData.Length)
{
return;
}
this.position = (int)index;
int count = this.ReadUInt16();
for (int i = 0; i < count; i++)
@ -431,7 +436,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
return null;
}
private void GetThumbnail(int offset)
private void GetThumbnail(uint offset)
{
var values = new List<ExifValue>();
this.AddValues(values, offset);
@ -515,10 +520,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
return new Rational(numerator, denominator, false);
}
private sbyte ConvertToSignedByte(ReadOnlySpan<byte> buffer)
{
return unchecked((sbyte)buffer[0]);
}
private sbyte ConvertToSignedByte(ReadOnlySpan<byte> buffer) => unchecked((sbyte)buffer[0]);
private int ConvertToInt32(ReadOnlySpan<byte> buffer) // SignedLong in Exif Specification
{

69
src/ImageSharp/MetaData/Profiles/Exif/ExifValue.cs

@ -11,15 +11,30 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
/// <summary>
/// Represent the value of the EXIF profile.
/// </summary>
public sealed class ExifValue : IEquatable<ExifValue>
public sealed class ExifValue : IEquatable<ExifValue>, IDeepCloneable<ExifValue>
{
/// <summary>
/// Initializes a new instance of the <see cref="ExifValue"/> class.
/// </summary>
/// <param name="tag">The tag.</param>
/// <param name="dataType">The data type.</param>
/// <param name="value">The value.</param>
/// <param name="isArray">Whether the value is an array.</param>
internal ExifValue(ExifTag tag, ExifDataType dataType, object value, bool isArray)
{
this.Tag = tag;
this.DataType = dataType;
this.IsArray = isArray && dataType != ExifDataType.Ascii;
this.Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="ExifValue"/> class
/// by making a copy from another exif value.
/// </summary>
/// <param name="other">The other exif value, where the clone should be made from.</param>
/// <exception cref="System.ArgumentNullException"><paramref name="other"/> is null.</exception>
public ExifValue(ExifValue other)
/// <exception cref="ArgumentNullException"><paramref name="other"/> is null.</exception>
private ExifValue(ExifValue other)
{
Guard.NotNull(other, nameof(other));
@ -29,30 +44,17 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
if (!other.IsArray)
{
// All types are value types except for string which is immutable so safe to simply assign.
this.Value = other.Value;
}
else
{
// All array types are value types so Clone() is sufficient here.
var array = (Array)other.Value;
this.Value = array.Clone();
}
}
/// <summary>
/// Initializes a new instance of the <see cref="ExifValue"/> class.
/// </summary>
/// <param name="tag">The tag.</param>
/// <param name="dataType">The data type.</param>
/// <param name="value">The value.</param>
/// <param name="isArray">Whether the value is an array.</param>
internal ExifValue(ExifTag tag, ExifDataType dataType, object value, bool isArray)
{
this.Tag = tag;
this.DataType = dataType;
this.IsArray = isArray && dataType != ExifDataType.Ascii;
this.Value = value;
}
/// <summary>
/// Gets the data type of the exif value.
/// </summary>
@ -145,10 +147,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
/// <returns>
/// True if the <paramref name="left"/> parameter is equal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
public static bool operator ==(ExifValue left, ExifValue right)
{
return ReferenceEquals(left, right) || left.Equals(right);
}
public static bool operator ==(ExifValue left, ExifValue right) => ReferenceEquals(left, right) || left.Equals(right);
/// <summary>
/// Compares two <see cref="ExifValue"/> objects for equality.
@ -162,16 +161,10 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
/// <returns>
/// True if the <paramref name="left"/> parameter is not equal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
public static bool operator !=(ExifValue left, ExifValue right)
{
return !(left == right);
}
public static bool operator !=(ExifValue left, ExifValue right) => !(left == right);
/// <inheritdoc />
public override bool Equals(object obj)
{
return obj is ExifValue other && this.Equals(other);
}
public override bool Equals(object obj) => obj is ExifValue other && this.Equals(other);
/// <inheritdoc />
public bool Equals(ExifValue other)
@ -187,9 +180,9 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
}
return
this.Tag == other.Tag &&
this.DataType == other.DataType &&
object.Equals(this.Value, other.Value);
this.Tag == other.Tag
&& this.DataType == other.DataType
&& object.Equals(this.Value, other.Value);
}
/// <summary>
@ -205,10 +198,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
}
/// <inheritdoc/>
public override int GetHashCode()
{
return this.GetHashCode(this);
}
public override int GetHashCode() => this.GetHashCode(this);
/// <inheritdoc/>
public override string ToString()
@ -238,6 +228,9 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
return sb.ToString();
}
/// <inheritdoc/>
public ExifValue DeepClone() => new ExifValue(this);
/// <summary>
/// Creates a new <see cref="ExifValue"/>
/// </summary>
@ -584,7 +577,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
private static ExifValue CreateNumber(ExifTag tag, object value, bool isArray)
{
Type type = value?.GetType();
if (type != null && type.IsArray)
if (type?.IsArray == true)
{
type = type.GetElementType();
}

2
src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Matrix.cs

@ -59,7 +59,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc
/// <param name="value">The matrix to write</param>
/// <param name="isSingle">True if the values are encoded as Single; false if encoded as Fix16</param>
/// <returns>The number of bytes written</returns>
public int WriteMatrix(DenseMatrix<float> value, bool isSingle)
public int WriteMatrix(in DenseMatrix<float> value, bool isSingle)
{
int count = 0;
for (int y = 0; y < value.Rows; y++)

36
src/ImageSharp/MetaData/Profiles/ICC/IccProfile.cs

@ -13,7 +13,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc
/// <summary>
/// Represents an ICC profile
/// </summary>
public sealed class IccProfile
public sealed class IccProfile : IDeepCloneable<IccProfile>
{
/// <summary>
/// The byte array to read the ICC profile from
@ -42,23 +42,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc
/// Initializes a new instance of the <see cref="IccProfile"/> class.
/// </summary>
/// <param name="data">The raw ICC profile data</param>
public IccProfile(byte[] data)
{
this.data = data;
}
/// <summary>
/// Initializes a new instance of the <see cref="IccProfile"/> class
/// by making a copy from another ICC profile.
/// </summary>
/// <param name="other">The other ICC profile, where the clone should be made from.</param>
/// <exception cref="ArgumentNullException"><paramref name="other"/> is null.</exception>>
public IccProfile(IccProfile other)
{
Guard.NotNull(other, nameof(other));
this.data = other.ToByteArray();
}
public IccProfile(byte[] data) => this.data = data;
/// <summary>
/// Initializes a new instance of the <see cref="IccProfile"/> class.
@ -74,6 +58,19 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc
this.entries = new List<IccTagDataEntry>(entries);
}
/// <summary>
/// Initializes a new instance of the <see cref="IccProfile"/> class
/// by making a copy from another ICC profile.
/// </summary>
/// <param name="other">The other ICC profile, where the clone should be made from.</param>
/// <exception cref="ArgumentNullException"><paramref name="other"/> is null.</exception>>
private IccProfile(IccProfile other)
{
Guard.NotNull(other, nameof(other));
this.data = other.ToByteArray();
}
/// <summary>
/// Gets or sets the profile header
/// </summary>
@ -100,6 +97,9 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc
}
}
/// <inheritdoc/>
public IccProfile DeepClone() => new IccProfile(this);
#if !NETSTANDARD1_1
/// <summary>

8166
src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs

File diff suppressed because it is too large

44
src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt

@ -12,9 +12,11 @@
// Licensed under the Apache License, Version 2.0.
// <auto-generated />
using System;
using System.Numerics;
using System.Buffers;
using System;
using System.Numerics;
using System.Buffers;
using SixLabors.ImageSharp.Memory;
using SixLabors.Memory;
namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
@ -78,32 +80,28 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
/// <inheritdoc />
public override TPixel Blend(TPixel background, TPixel source, float amount)
{
return PorterDuffFunctions.<#=blender_composer#>(background, source, amount);
TPixel dest = default;
dest.PackFromScaledVector4(PorterDuffFunctions.<#=blender_composer#>(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1)));
return dest;
}
/// <inheritdoc />
public override void Blend(MemoryAllocator memoryManager, Span<TPixel> destination, Span<TPixel> background, Span<TPixel> source, Span<float> amount)
protected override void BlendFunction(Span<Vector4> destination, ReadOnlySpan<Vector4> background, ReadOnlySpan<Vector4> source, float amount)
{
Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length));
Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length));
Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length));
using (IMemoryOwner<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3))
amount = amount.Clamp(0, 1);
for (int i = 0; i < destination.Length; i++)
{
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
Span<Vector4> sourceSpan = buffer.Slice(destination.Length * 2, destination.Length);
PixelOperations<TPixel>.Instance.ToVector4(background, backgroundSpan, destination.Length);
PixelOperations<TPixel>.Instance.ToVector4(source, sourceSpan, destination.Length);
for (int i = 0; i < destination.Length; i++)
{
destinationSpan[i] = PorterDuffFunctions.<#=blender_composer#>(backgroundSpan[i], sourceSpan[i], amount[i]);
}
destination[i] = PorterDuffFunctions.<#=blender_composer#>(background[i], source[i], amount);
}
}
PixelOperations<TPixel>.Instance.PackFromVector4(destinationSpan, destination, destination.Length);
}
/// <inheritdoc />
protected override void BlendFunction(Span<Vector4> destination, ReadOnlySpan<Vector4> background, ReadOnlySpan<Vector4> source, ReadOnlySpan<float> amount)
{
for (int i = 0; i < destination.Length; i++)
{
destination[i] = PorterDuffFunctions.<#=blender_composer#>(background[i], source[i], amount[i].Clamp(0, 1));
}
}
}

838
src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.cs

File diff suppressed because it is too large

67
src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.tt

@ -13,6 +13,11 @@
// <auto-generated />
<#
// Note use of MethodImplOptions.NoInlining. We have tests that are failing on certain architectures when
// AggresiveInlining is used. Confirmed on Intel i7-6600U in 64bit.
#>
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
@ -24,107 +29,96 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
<# void GeneratePixelBlenders(string blender) { #>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[MethodImpl(MethodImplOptions.NoInlining)]
public static Vector4 <#=blender#>Src(Vector4 backdrop, Vector4 source, float opacity)
{
opacity = opacity.Clamp(0, 1);
source.W *= opacity;
source.W *= opacity;
return source;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[MethodImpl(MethodImplOptions.NoInlining)]
public static Vector4 <#=blender#>SrcAtop(Vector4 backdrop, Vector4 source, float opacity)
{
opacity = opacity.Clamp(0, 1);
source.W *= opacity;
source.W *= opacity;
return Atop(backdrop, source, <#=blender#>(backdrop, source));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[MethodImpl(MethodImplOptions.NoInlining)]
public static Vector4 <#=blender#>SrcOver(Vector4 backdrop, Vector4 source, float opacity)
{
opacity = opacity.Clamp(0, 1);
source.W *= opacity;
source.W *= opacity;
return Over(backdrop, source, <#=blender#>(backdrop, source));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[MethodImpl(MethodImplOptions.NoInlining)]
public static Vector4 <#=blender#>SrcIn(Vector4 backdrop, Vector4 source, float opacity)
{
opacity = opacity.Clamp(0, 1);
source.W *= opacity;
source.W *= opacity;
return In(backdrop, source, <#=blender#>(backdrop, source));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[MethodImpl(MethodImplOptions.NoInlining)]
public static Vector4 <#=blender#>SrcOut(Vector4 backdrop, Vector4 source, float opacity)
{
opacity = opacity.Clamp(0, 1);
source.W *= opacity;
source.W *= opacity;
return Out(backdrop, source);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[MethodImpl(MethodImplOptions.NoInlining)]
public static Vector4 <#=blender#>Dest(Vector4 backdrop, Vector4 source, float opacity)
{
return backdrop;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[MethodImpl(MethodImplOptions.NoInlining)]
public static Vector4 <#=blender#>DestAtop(Vector4 backdrop, Vector4 source, float opacity)
{
opacity = opacity.Clamp(0, 1);
source.W *= opacity;
source.W *= opacity;
return Atop(source, backdrop, <#=blender#>(source, backdrop));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[MethodImpl(MethodImplOptions.NoInlining)]
public static Vector4 <#=blender#>DestOver(Vector4 backdrop, Vector4 source, float opacity)
{
opacity = opacity.Clamp(0, 1);
source.W *= opacity;
source.W *= opacity;
return Over(source, backdrop, <#=blender#>(source, backdrop));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[MethodImpl(MethodImplOptions.NoInlining)]
public static Vector4 <#=blender#>DestIn(Vector4 backdrop, Vector4 source, float opacity)
{
opacity = opacity.Clamp(0, 1);
source.W *= opacity;
source.W *= opacity;
return In(source, backdrop, <#=blender#>(source, backdrop));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[MethodImpl(MethodImplOptions.NoInlining)]
public static Vector4 <#=blender#>DestOut(Vector4 backdrop, Vector4 source, float opacity)
{
opacity = opacity.Clamp(0, 1);
source.W *= opacity;
source.W *= opacity;
return Out(source, backdrop);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[MethodImpl(MethodImplOptions.NoInlining)]
public static Vector4 <#=blender#>Xor(Vector4 backdrop, Vector4 source, float opacity)
{
opacity = opacity.Clamp(0, 1);
source.W *= opacity;
source.W *= opacity;
return Xor(backdrop, source);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[MethodImpl(MethodImplOptions.NoInlining)]
public static Vector4 <#=blender#>Clear(Vector4 backdrop, Vector4 source, float opacity)
{
opacity = opacity.Clamp(0, 1);
source.W *= opacity;
source.W *= opacity;
return Clear(backdrop, source);
}
@ -137,8 +131,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
public static TPixel <#=blender#><#=composer#><TPixel>(TPixel backdrop, TPixel source, float opacity)
where TPixel : struct, IPixel<TPixel>
{
opacity = opacity.Clamp(0, 1);
TPixel dest = default;
dest.PackFromVector4(<#=blender#><#=composer#>(backdrop.ToVector4(),source.ToVector4(),opacity));
dest.PackFromScaledVector4(<#=blender#><#=composer#>(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity));
return dest;
}
@ -175,7 +170,7 @@ string[] blenders = new []{
foreach(var blender in blenders)
{
GeneratePixelBlenders(blender);
GeneratePixelBlenders(blender);
foreach(var composer in composers)
{

31
src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs

@ -8,7 +8,7 @@ using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
{
/// <summary>
/// Collection of Porter Duff alpha blending functions applying an the 'Over' composition model.
/// Collection of Porter Duff Color Blending and Alpha Composition Functions.
/// </summary>
/// <remarks>
/// These functions are designed to be a general solution for all color cases,
@ -148,31 +148,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
return backdrop <= 0.5f ? (2 * backdrop * source) : 1 - ((2 * (1 - source)) * (1 - backdrop));
}
/// <summary>
/// General composition function for all modes, with a general solution for alpha channel
/// </summary>
/// <param name="backdrop">Original Backdrop color</param>
/// <param name="source">Original source color</param>
/// <param name="xform">Desired transformed color, without taking Alpha channel in account</param>
/// <returns>The final color</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector4 SrcOverReference(Vector4 backdrop, Vector4 source, Vector4 xform)
{
// calculate weights
float xw = backdrop.W * source.W;
float bw = backdrop.W - xw;
float sw = source.W - xw;
// calculate final alpha
float a = xw + bw + sw;
// calculate final value
xform = ((xform * xw) + (backdrop * bw) + (source * sw)) / MathF.Max(a, Constants.Epsilon);
xform.W = a;
return xform;
}
public static Vector4 Over(Vector4 dst, Vector4 src, Vector4 blend)
{
// calculate weights
@ -193,6 +169,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
return color;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 Atop(Vector4 dst, Vector4 src, Vector4 blend)
{
// calculate weights
@ -212,6 +189,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
return color;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 In(Vector4 dst, Vector4 src, Vector4 blend)
{
float alpha = dst.W * src.W;
@ -223,6 +201,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
return color;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 Out(Vector4 dst, Vector4 src)
{
float alpha = (1 - dst.W) * src.W;
@ -234,6 +213,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
return color;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 Xor(Vector4 dst, Vector4 src)
{
float srcW = 1 - dst.W;
@ -249,6 +229,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
return color;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector4 Clear(Vector4 backdrop, Vector4 source)
{
return Vector4.Zero;

120
src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs

@ -2,6 +2,9 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using System.Numerics;
using SixLabors.ImageSharp.Memory;
using SixLabors.Memory;
namespace SixLabors.ImageSharp.PixelFormats
@ -23,19 +26,114 @@ namespace SixLabors.ImageSharp.PixelFormats
/// At amount = 0, "from" is returned, at amount = 1, "to" is returned.
/// </param>
/// <returns>The final pixel value after composition</returns>
public abstract TPixel Blend(TPixel background, TPixel source, float amount);
/// <summary>
/// Blend 2 pixels together.
/// </summary>
/// <param name="memoryAllocator">The <see cref="MemoryAllocator"/></param>
/// <param name="destination">The destination span.</param>
/// <param name="background">The background span.</param>
/// <param name="source">The source span.</param>
public abstract TPixel Blend(TPixel background, TPixel source, float amount);
/// <summary>
/// Blend 2 rows together.
/// </summary>
/// <param name="destination">destination span</param>
/// <param name="background">the background span</param>
/// <param name="source">the source span</param>
/// <param name="amount">
/// A value between 0 and 1 indicating the weight of the second source vector.
/// At amount = 0, "from" is returned, at amount = 1, "to" is returned.
/// </param>
public abstract void Blend(MemoryAllocator memoryAllocator, Span<TPixel> destination, Span<TPixel> background, Span<TPixel> source, Span<float> amount);
/// </param>
protected abstract void BlendFunction(Span<Vector4> destination, ReadOnlySpan<Vector4> background, ReadOnlySpan<Vector4> source, float amount);
/// <summary>
/// Blend 2 rows together.
/// </summary>
/// <param name="destination">destination span</param>
/// <param name="background">the background span</param>
/// <param name="source">the source span</param>
/// <param name="amount">
/// A span with values between 0 and 1 indicating the weight of the second source vector.
/// At amount = 0, "from" is returned, at amount = 1, "to" is returned.
/// </param>
protected abstract void BlendFunction(Span<Vector4> destination, ReadOnlySpan<Vector4> background, ReadOnlySpan<Vector4> source, ReadOnlySpan<float> amount);
/// <summary>
/// Blends 2 rows together
/// </summary>
/// <param name="memoryManager">memory manager to use internally</param>
/// <param name="destination">the destination span</param>
/// <param name="background">the background span</param>
/// <param name="source">the source span</param>
/// <param name="amount">
/// A span with values between 0 and 1 indicating the weight of the second source vector.
/// At amount = 0, "from" is returned, at amount = 1, "to" is returned.
/// </param>
public void Blend(MemoryAllocator memoryManager, Span<TPixel> destination, ReadOnlySpan<TPixel> background, ReadOnlySpan<TPixel> source, ReadOnlySpan<float> amount)
{
this.Blend<TPixel>(memoryManager, destination, background, source, amount);
}
/// <summary>
/// Blends 2 rows together
/// </summary>
/// <typeparam name="TPixelSrc">the pixel format of the source span</typeparam>
/// <param name="memoryManager">memory manager to use internally</param>
/// <param name="destination">the destination span</param>
/// <param name="background">the background span</param>
/// <param name="source">the source span</param>
/// <param name="amount">
/// A span with values between 0 and 1 indicating the weight of the second source vector.
/// At amount = 0, "from" is returned, at amount = 1, "to" is returned.
/// </param>
public void Blend<TPixelSrc>(MemoryAllocator memoryManager, Span<TPixel> destination, ReadOnlySpan<TPixel> background, ReadOnlySpan<TPixelSrc> source, ReadOnlySpan<float> amount)
where TPixelSrc : struct, IPixel<TPixelSrc>
{
Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length));
Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length));
Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length));
using (IMemoryOwner<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3))
{
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
Span<Vector4> sourceSpan = buffer.Slice(destination.Length * 2, destination.Length);
PixelOperations<TPixel>.Instance.ToScaledVector4(background, backgroundSpan, destination.Length);
PixelOperations<TPixelSrc>.Instance.ToScaledVector4(source, sourceSpan, destination.Length);
this.BlendFunction(destinationSpan, backgroundSpan, sourceSpan, amount);
PixelOperations<TPixel>.Instance.PackFromScaledVector4(destinationSpan, destination, destination.Length);
}
}
/// <summary>
/// Blends 2 rows together
/// </summary>
/// <typeparam name="TPixelSrc">the pixel format of the source span</typeparam>
/// <param name="memoryManager">memory manager to use internally</param>
/// <param name="destination">the destination span</param>
/// <param name="background">the background span</param>
/// <param name="source">the source span</param>
/// <param name="amount">
/// A value between 0 and 1 indicating the weight of the second source vector.
/// At amount = 0, "from" is returned, at amount = 1, "to" is returned.
/// </param>
public void Blend<TPixelSrc>(MemoryAllocator memoryManager, Span<TPixel> destination, ReadOnlySpan<TPixel> background, ReadOnlySpan<TPixelSrc> source, float amount)
where TPixelSrc : struct, IPixel<TPixelSrc>
{
Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length));
Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length));
Guard.MustBeBetweenOrEqualTo(amount, 0, 1, nameof(amount));
using (IMemoryOwner<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3))
{
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
Span<Vector4> sourceSpan = buffer.Slice(destination.Length * 2, destination.Length);
PixelOperations<TPixel>.Instance.ToScaledVector4(background, backgroundSpan, destination.Length);
PixelOperations<TPixelSrc>.Instance.ToScaledVector4(source, sourceSpan, destination.Length);
this.BlendFunction(destinationSpan, backgroundSpan, sourceSpan, amount);
PixelOperations<TPixel>.Instance.PackFromScaledVector4(destinationSpan, destination, destination.Length);
}
}
}
}

2
src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs

@ -67,7 +67,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
Buffer2D<TPixel> targetPixels,
Buffer2D<TPixel> sourcePixels,
Rectangle sourceRectangle,
DenseMatrix<float> kernel,
DenseMatrix<float> kernel, // TODO: Can't use 'in' as pass by ref to lambda expression.
Configuration configuration)
{
int kernelHeight = kernel.Rows;

9
src/ImageSharp/Processing/Processors/Quantization/IQuantizer.cs

@ -23,5 +23,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <returns>The <see cref="IFrameQuantizer{TPixel}"/></returns>
IFrameQuantizer<TPixel> CreateFrameQuantizer<TPixel>()
where TPixel : struct, IPixel<TPixel>;
/// <summary>
/// Creates the generic frame quantizer
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="maxColors">The maximum number of colors to hold in the color palette.</param>
/// <returns>The <see cref="IFrameQuantizer{TPixel}"/></returns>
IFrameQuantizer<TPixel> CreateFrameQuantizer<TPixel>(int maxColors)
where TPixel : struct, IPixel<TPixel>;
}
}

32
src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs

@ -22,7 +22,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <summary>
/// Maximum allowed color depth
/// </summary>
private readonly byte colors;
private readonly int colors;
/// <summary>
/// Stores the tree
@ -43,9 +43,23 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// the second pass quantizes a color based on the nodes in the tree
/// </remarks>
public OctreeFrameQuantizer(OctreeQuantizer quantizer)
: this(quantizer, quantizer.MaxColors)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="OctreeFrameQuantizer{TPixel}"/> class.
/// </summary>
/// <param name="quantizer">The octree quantizer.</param>
/// <param name="maxColors">The maximum number of colors to hold in the color palette.</param>
/// <remarks>
/// The Octree quantizer is a two pass algorithm. The initial pass sets up the Octree,
/// the second pass quantizes a color based on the nodes in the tree
/// </remarks>
public OctreeFrameQuantizer(OctreeQuantizer quantizer, int maxColors)
: base(quantizer, false)
{
this.colors = (byte)quantizer.MaxColors;
this.colors = maxColors;
this.octree = new Octree(ImageMaths.GetBitsNeededForColorDepth(this.colors).Clamp(1, 8));
}
@ -261,13 +275,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public TPixel[] Palletize(int colorCount)
{
while (this.Leaves > colorCount)
while (this.Leaves > colorCount - 1)
{
this.Reduce();
}
// Now palletize the nodes
var palette = new TPixel[colorCount + 1];
var palette = new TPixel[colorCount];
int paletteIndex = 0;
this.root.ConstructPalette(palette, ref paletteIndex);
@ -285,10 +299,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// The <see cref="int"/>.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int GetPaletteIndex(ref TPixel pixel, ref Rgba32 rgba)
{
return this.root.GetPaletteIndex(ref pixel, 0, ref rgba);
}
public int GetPaletteIndex(ref TPixel pixel, ref Rgba32 rgba) => this.root.GetPaletteIndex(ref pixel, 0, ref rgba);
/// <summary>
/// Keep track of the previous node that was quantized
@ -297,10 +308,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// The node last quantized
/// </param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected void TrackPrevious(OctreeNode node)
{
this.previousNode = node;
}
protected void TrackPrevious(OctreeNode node) => this.previousNode = node;
/// <summary>
/// Reduce the depth of the tree

23
src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer.cs

@ -15,6 +15,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// </summary>
public class OctreeQuantizer : IQuantizer
{
/// <summary>
/// The default maximum number of colors to use when quantizing the image.
/// </summary>
public const int DefaultMaxColors = 256;
/// <summary>
/// Initializes a new instance of the <see cref="OctreeQuantizer"/> class.
/// </summary>
@ -26,7 +31,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <summary>
/// Initializes a new instance of the <see cref="OctreeQuantizer"/> class.
/// </summary>
/// <param name="maxColors">The maximum number of colors to hold in the color palette</param>
/// <param name="maxColors">The maximum number of colors to hold in the color palette.</param>
public OctreeQuantizer(int maxColors)
: this(GetDiffuser(true), maxColors)
{
@ -37,7 +42,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// </summary>
/// <param name="dither">Whether to apply dithering to the output image</param>
public OctreeQuantizer(bool dither)
: this(GetDiffuser(dither), 255)
: this(GetDiffuser(dither), DefaultMaxColors)
{
}
@ -46,7 +51,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// </summary>
/// <param name="diffuser">The error diffusion algorithm, if any, to apply to the output image</param>
public OctreeQuantizer(IErrorDiffuser diffuser)
: this(diffuser, 255)
: this(diffuser, DefaultMaxColors)
{
}
@ -57,10 +62,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <param name="maxColors">The maximum number of colors to hold in the color palette</param>
public OctreeQuantizer(IErrorDiffuser diffuser, int maxColors)
{
Guard.MustBeBetweenOrEqualTo(maxColors, 1, 255, nameof(maxColors));
this.Diffuser = diffuser;
this.MaxColors = maxColors;
this.MaxColors = maxColors.Clamp(1, DefaultMaxColors);
}
/// <inheritdoc />
@ -76,6 +79,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
where TPixel : struct, IPixel<TPixel>
=> new OctreeFrameQuantizer<TPixel>(this);
/// <inheritdoc/>
public IFrameQuantizer<TPixel> CreateFrameQuantizer<TPixel>(int maxColors)
where TPixel : struct, IPixel<TPixel>
{
maxColors = maxColors.Clamp(1, DefaultMaxColors);
return new OctreeFrameQuantizer<TPixel>(this, maxColors);
}
private static IErrorDiffuser GetDiffuser(bool dither) => dither ? KnownDiffusers.FloydSteinberg : null;
}
}

2
src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs

@ -36,6 +36,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
public PaletteFrameQuantizer(PaletteQuantizer quantizer, TPixel[] colors)
: base(quantizer, true)
{
// TODO: Why is this value constrained? Gif has limitations but theoretically
// we might want to reduce the palette of an image to greater than that limitation.
Guard.MustBeBetweenOrEqualTo(colors.Length, 1, 256, nameof(colors));
this.palette = colors;
this.paletteVector = new Vector4[this.palette.Length];

20
src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs

@ -37,10 +37,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// Initializes a new instance of the <see cref="PaletteQuantizer"/> class.
/// </summary>
/// <param name="diffuser">The error diffusion algorithm, if any, to apply to the output image</param>
public PaletteQuantizer(IErrorDiffuser diffuser)
{
this.Diffuser = diffuser;
}
public PaletteQuantizer(IErrorDiffuser diffuser) => this.Diffuser = diffuser;
/// <inheritdoc />
public IErrorDiffuser Diffuser { get; }
@ -50,6 +47,21 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
where TPixel : struct, IPixel<TPixel>
=> this.CreateFrameQuantizer(() => NamedColors<TPixel>.WebSafePalette);
/// <inheritdoc/>
public IFrameQuantizer<TPixel> CreateFrameQuantizer<TPixel>(int maxColors)
where TPixel : struct, IPixel<TPixel>
{
TPixel[] websafe = NamedColors<TPixel>.WebSafePalette;
int max = Math.Min(maxColors, websafe.Length);
if (max != websafe.Length)
{
return this.CreateFrameQuantizer(() => NamedColors<TPixel>.WebSafePalette.AsSpan(0, max).ToArray());
}
return this.CreateFrameQuantizer(() => websafe);
}
/// <summary>
/// Gets the palette to use to quantize the image.
/// </summary>

165
src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs

@ -3,7 +3,6 @@
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@ -38,7 +37,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
where TPixel : struct, IPixel<TPixel>
{
// TODO: The WuFrameQuantizer<TPixel> code is rising several questions:
// - Do we really need to ALWAYS allocate the whole table of size TableLength? (~ 2471625 * sizeof(long) * 5 bytes )
// - Do we really need to ALWAYS allocate the whole table of size TableLength? (~ 2471625 * sizeof(long) * 5 bytes ) JS. I'm afraid so.
// - Isn't an AOS ("array of structures") layout more efficient & more readable than SOA ("structure of arrays") for this particular use case?
// (T, R, G, B, A, M2) could be grouped together!
// - It's a frequently used class, we need tests! (So we can optimize safely.) There are tests in the original!!! We should just adopt them!
@ -47,12 +46,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <summary>
/// The index bits.
/// </summary>
private const int IndexBits = 6;
private const int IndexBits = 5;
/// <summary>
/// The index alpha bits.
/// The index alpha bits. Keep separate for now to allow easy adjustment.
/// </summary>
private const int IndexAlphaBits = 3;
private const int IndexAlphaBits = 5;
/// <summary>
/// The index count.
@ -65,7 +64,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
private const int IndexAlphaCount = (1 << IndexAlphaBits) + 1;
/// <summary>
/// The table length.
/// The table length. Now 1185921.
/// </summary>
private const int TableLength = IndexCount * IndexCount * IndexCount * IndexAlphaCount;
@ -128,11 +127,22 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// the second pass quantizes a color based on the position in the histogram.
/// </remarks>
public WuFrameQuantizer(WuQuantizer quantizer)
: base(quantizer, false)
: this(quantizer, quantizer.MaxColors)
{
this.colors = quantizer.MaxColors;
}
/// <summary>
/// Initializes a new instance of the <see cref="WuFrameQuantizer{TPixel}"/> class.
/// </summary>
/// <param name="quantizer">The wu quantizer.</param>
/// <param name="maxColors">The maximum number of colors to hold in the color palette.</param>
/// <remarks>
/// The Wu quantizer is a two pass algorithm. The initial pass sets up the 3-D color histogram,
/// the second pass quantizes a color based on the position in the histogram.
/// </remarks>
public WuFrameQuantizer(WuQuantizer quantizer, int maxColors)
: base(quantizer, false) => this.colors = maxColors;
/// <inheritdoc/>
public override QuantizedFrame<TPixel> QuantizeFrame(ImageFrame<TPixel> image)
{
@ -169,18 +179,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
if (this.palette is null)
{
this.palette = new TPixel[this.colors];
Span<long> vwtSpan = this.vwt.GetSpan();
Span<long> vmrSpan = this.vmr.GetSpan();
Span<long> vmgSpan = this.vmg.GetSpan();
Span<long> vmbSpan = this.vmb.GetSpan();
Span<long> vmaSpan = this.vma.GetSpan();
for (int k = 0; k < this.colors; k++)
{
this.Mark(ref this.colorCube[k], (byte)k);
float weight = Volume(ref this.colorCube[k], this.vwt.GetSpan());
float weight = Volume(ref this.colorCube[k], vwtSpan);
if (MathF.Abs(weight) > Constants.Epsilon)
{
float r = Volume(ref this.colorCube[k], this.vmr.GetSpan());
float g = Volume(ref this.colorCube[k], this.vmg.GetSpan());
float b = Volume(ref this.colorCube[k], this.vmb.GetSpan());
float a = Volume(ref this.colorCube[k], this.vma.GetSpan());
float r = Volume(ref this.colorCube[k], vmrSpan);
float g = Volume(ref this.colorCube[k], vmgSpan);
float b = Volume(ref this.colorCube[k], vmbSpan);
float a = Volume(ref this.colorCube[k], vmaSpan);
ref TPixel color = ref this.palette[k];
color.PackFromScaledVector4(new Vector4(r, g, b, a) / weight / 255F);
@ -191,57 +207,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
return this.palette;
}
/// <summary>
/// Quantizes the pixel
/// </summary>
/// <param name="rgba">The rgba used to quantize the pixel input</param>
private void QuantizePixel(ref Rgba32 rgba)
{
// Add the color to a 3-D color histogram.
int r = rgba.R >> (8 - IndexBits);
int g = rgba.G >> (8 - IndexBits);
int b = rgba.B >> (8 - IndexBits);
int a = rgba.A >> (8 - IndexAlphaBits);
int index = GetPaletteIndex(r + 1, g + 1, b + 1, a + 1);
Span<long> vwtSpan = this.vwt.GetSpan();
Span<long> vmrSpan = this.vmr.GetSpan();
Span<long> vmgSpan = this.vmg.GetSpan();
Span<long> vmbSpan = this.vmb.GetSpan();
Span<long> vmaSpan = this.vma.GetSpan();
Span<float> m2Span = this.m2.GetSpan();
vwtSpan[index]++;
vmrSpan[index] += rgba.R;
vmgSpan[index] += rgba.G;
vmbSpan[index] += rgba.B;
vmaSpan[index] += rgba.A;
var vector = new Vector4(rgba.R, rgba.G, rgba.B, rgba.A);
m2Span[index] += Vector4.Dot(vector, vector);
}
/// <inheritdoc/>
protected override void FirstPass(ImageFrame<TPixel> source, int width, int height)
{
// Build up the 3-D color histogram
// Loop through each row
for (int y = 0; y < height; y++)
{
Span<TPixel> row = source.GetPixelRowSpan(y);
ref TPixel scanBaseRef = ref MemoryMarshal.GetReference(row);
// And loop through each column
Rgba32 rgba = default;
for (int x = 0; x < width; x++)
{
ref TPixel pixel = ref Unsafe.Add(ref scanBaseRef, x);
pixel.ToRgba32(ref rgba);
this.QuantizePixel(ref rgba);
}
}
this.Build3DHistogram(source, width, height);
this.Get3DMoments(source.MemoryAllocator);
this.BuildCube();
}
@ -456,6 +425,54 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
}
}
/// <summary>
/// Builds a 3-D color histogram of <c>counts, r/g/b, c^2</c>.
/// </summary>
/// <param name="source">The source data.</param>
/// <param name="width">The width in pixels of the image.</param>
/// <param name="height">The height in pixels of the image.</param>
private void Build3DHistogram(ImageFrame<TPixel> source, int width, int height)
{
Span<long> vwtSpan = this.vwt.GetSpan();
Span<long> vmrSpan = this.vmr.GetSpan();
Span<long> vmgSpan = this.vmg.GetSpan();
Span<long> vmbSpan = this.vmb.GetSpan();
Span<long> vmaSpan = this.vma.GetSpan();
Span<float> m2Span = this.m2.GetSpan();
// Build up the 3-D color histogram
// Loop through each row
for (int y = 0; y < height; y++)
{
Span<TPixel> row = source.GetPixelRowSpan(y);
ref TPixel scanBaseRef = ref MemoryMarshal.GetReference(row);
// And loop through each column
Rgba32 rgba = default;
for (int x = 0; x < width; x++)
{
ref TPixel pixel = ref Unsafe.Add(ref scanBaseRef, x);
pixel.ToRgba32(ref rgba);
int r = rgba.R >> (8 - IndexBits);
int g = rgba.G >> (8 - IndexBits);
int b = rgba.B >> (8 - IndexBits);
int a = rgba.A >> (8 - IndexAlphaBits);
int index = GetPaletteIndex(r + 1, g + 1, b + 1, a + 1);
vwtSpan[index]++;
vmrSpan[index] += rgba.R;
vmgSpan[index] += rgba.G;
vmbSpan[index] += rgba.B;
vmaSpan[index] += rgba.A;
var vector = new Vector4(rgba.R, rgba.G, rgba.B, rgba.A);
m2Span[index] += Vector4.Dot(vector, vector);
}
}
}
/// <summary>
/// Converts the histogram into moments so that we can rapidly calculate the sums of the above quantities over any desired box.
/// </summary>
@ -621,22 +638,28 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <returns>The <see cref="float"/>.</returns>
private float Maximize(ref Box cube, int direction, int first, int last, out int cut, float wholeR, float wholeG, float wholeB, float wholeA, float wholeW)
{
long baseR = Bottom(ref cube, direction, this.vmr.GetSpan());
long baseG = Bottom(ref cube, direction, this.vmg.GetSpan());
long baseB = Bottom(ref cube, direction, this.vmb.GetSpan());
long baseA = Bottom(ref cube, direction, this.vma.GetSpan());
long baseW = Bottom(ref cube, direction, this.vwt.GetSpan());
Span<long> vwtSpan = this.vwt.GetSpan();
Span<long> vmrSpan = this.vmr.GetSpan();
Span<long> vmgSpan = this.vmg.GetSpan();
Span<long> vmbSpan = this.vmb.GetSpan();
Span<long> vmaSpan = this.vma.GetSpan();
long baseR = Bottom(ref cube, direction, vmrSpan);
long baseG = Bottom(ref cube, direction, vmgSpan);
long baseB = Bottom(ref cube, direction, vmbSpan);
long baseA = Bottom(ref cube, direction, vmaSpan);
long baseW = Bottom(ref cube, direction, vwtSpan);
float max = 0F;
cut = -1;
for (int i = first; i < last; i++)
{
float halfR = baseR + Top(ref cube, direction, i, this.vmr.GetSpan());
float halfG = baseG + Top(ref cube, direction, i, this.vmg.GetSpan());
float halfB = baseB + Top(ref cube, direction, i, this.vmb.GetSpan());
float halfA = baseA + Top(ref cube, direction, i, this.vma.GetSpan());
float halfW = baseW + Top(ref cube, direction, i, this.vwt.GetSpan());
float halfR = baseR + Top(ref cube, direction, i, vmrSpan);
float halfG = baseG + Top(ref cube, direction, i, vmgSpan);
float halfB = baseB + Top(ref cube, direction, i, vmbSpan);
float halfA = baseA + Top(ref cube, direction, i, vmaSpan);
float halfW = baseW + Top(ref cube, direction, i, vwtSpan);
if (MathF.Abs(halfW) < Constants.Epsilon)
{

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

Loading…
Cancel
Save