Browse Source

Merge branch 'master' into feature/tga

af/merge-core
James Jackson-South 7 years ago
committed by GitHub
parent
commit
4ea5d4a0ac
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      src/ImageSharp/Advanced/AotCompilerTools.cs
  2. 4
      src/ImageSharp/Processing/Extensions/DiffuseExtensions.cs
  3. 4
      src/ImageSharp/Processing/Processors/Binarization/BinaryErrorDiffusionProcessor{TPixel}.cs
  4. 12
      src/ImageSharp/Processing/Processors/Dithering/AtkinsonDiffuser.cs
  5. 12
      src/ImageSharp/Processing/Processors/Dithering/BurksDiffuser.cs
  6. 102
      src/ImageSharp/Processing/Processors/Dithering/ErrorDiffuser.cs
  7. 10
      src/ImageSharp/Processing/Processors/Dithering/ErrorDiffusionPaletteProcessor{TPixel}.cs
  8. 12
      src/ImageSharp/Processing/Processors/Dithering/FloydSteinbergDiffuser.cs
  9. 7
      src/ImageSharp/Processing/Processors/Dithering/IErrorDiffuser.cs
  10. 14
      src/ImageSharp/Processing/Processors/Dithering/JarvisJudiceNinkeDiffuser.cs
  11. 6
      src/ImageSharp/Processing/Processors/Dithering/OrderedDitherPaletteProcessor{TPixel}.cs
  12. 69
      src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs
  13. 12
      src/ImageSharp/Processing/Processors/Dithering/Sierra2Diffuser.cs
  14. 14
      src/ImageSharp/Processing/Processors/Dithering/Sierra3Diffuser.cs
  15. 12
      src/ImageSharp/Processing/Processors/Dithering/SierraLiteDiffuser.cs
  16. 16
      src/ImageSharp/Processing/Processors/Dithering/StevensonArceDiffuser.cs
  17. 14
      src/ImageSharp/Processing/Processors/Dithering/StuckiDiffuser.cs
  18. 3
      src/ImageSharp/Processing/Processors/Dithering/error_diffusion.txt
  19. 55
      src/ImageSharp/Processing/Processors/Quantization/FrameQuantizer{TPixel}.cs
  20. 6
      src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs
  21. 6
      src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs
  22. 31
      src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs
  23. 49
      tests/ImageSharp.Benchmarks/Samplers/Diffuse.cs
  24. 2
      tests/Images/External

2
src/ImageSharp/Advanced/AotCompilerTools.cs

@ -136,7 +136,7 @@ namespace SixLabors.ImageSharp.Advanced
TPixel pixel = default; TPixel pixel = default;
using (var image = new ImageFrame<TPixel>(Configuration.Default, 1, 1)) using (var image = new ImageFrame<TPixel>(Configuration.Default, 1, 1))
{ {
test.Dither(image, pixel, pixel, 0, 0, 0, 0, 0, 0); test.Dither(image, pixel, pixel, 0, 0, 0, 0, 0);
} }
} }

4
src/ImageSharp/Processing/Extensions/DiffuseExtensions.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
@ -95,4 +95,4 @@ namespace SixLabors.ImageSharp.Processing.Dithering
Rectangle rectangle) => Rectangle rectangle) =>
source.ApplyProcessor(new ErrorDiffusionPaletteProcessor(diffuser, threshold, palette), rectangle); source.ApplyProcessor(new ErrorDiffusionPaletteProcessor(diffuser, threshold, palette), rectangle);
} }
} }

4
src/ImageSharp/Processing/Processors/Binarization/BinaryErrorDiffusionProcessor{TPixel}.cs

@ -14,7 +14,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization
/// Performs binary threshold filtering against an image using error diffusion. /// Performs binary threshold filtering against an image using error diffusion.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
internal class BinaryErrorDiffusionProcessor<TPixel> : ImageProcessor<TPixel> internal sealed class BinaryErrorDiffusionProcessor<TPixel> : ImageProcessor<TPixel>
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
private readonly BinaryErrorDiffusionProcessor definition; private readonly BinaryErrorDiffusionProcessor definition;
@ -76,7 +76,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization
} }
TPixel transformedPixel = luminance >= threshold ? upperColor : lowerColor; TPixel transformedPixel = luminance >= threshold ? upperColor : lowerColor;
diffuser.Dither(source, sourcePixel, transformedPixel, x, y, startX, startY, endX, endY); diffuser.Dither(source, sourcePixel, transformedPixel, x, y, startX, endX, endY);
} }
} }
} }

12
src/ImageSharp/Processing/Processors/Dithering/AtkinsonDiffuser.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Primitives; using SixLabors.ImageSharp.Primitives;
@ -11,22 +11,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
/// </summary> /// </summary>
public sealed class AtkinsonDiffuser : ErrorDiffuser public sealed class AtkinsonDiffuser : ErrorDiffuser
{ {
private const float Divisor = 8F;
/// <summary> /// <summary>
/// The diffusion matrix /// The diffusion matrix
/// </summary> /// </summary>
private static readonly DenseMatrix<float> AtkinsonMatrix = private static readonly DenseMatrix<float> AtkinsonMatrix =
new float[,] new float[,]
{ {
{ 0, 0, 1, 1 }, { 0, 0, 1 / Divisor, 1 / Divisor },
{ 1, 1, 1, 0 }, { 1 / Divisor, 1 / Divisor, 1 / Divisor, 0 },
{ 0, 1, 0, 0 } { 0, 1 / Divisor, 0, 0 }
}; };
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="AtkinsonDiffuser"/> class. /// Initializes a new instance of the <see cref="AtkinsonDiffuser"/> class.
/// </summary> /// </summary>
public AtkinsonDiffuser() public AtkinsonDiffuser()
: base(AtkinsonMatrix, 8) : base(AtkinsonMatrix)
{ {
} }
} }

12
src/ImageSharp/Processing/Processors/Dithering/BurksDiffuser.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Primitives; using SixLabors.ImageSharp.Primitives;
@ -11,22 +11,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
/// </summary> /// </summary>
public sealed class BurksDiffuser : ErrorDiffuser public sealed class BurksDiffuser : ErrorDiffuser
{ {
private const float Divisor = 32F;
/// <summary> /// <summary>
/// The diffusion matrix /// The diffusion matrix
/// </summary> /// </summary>
private static readonly DenseMatrix<float> BurksMatrix = private static readonly DenseMatrix<float> BurksMatrix =
new float[,] new float[,]
{ {
{ 0, 0, 0, 8, 4 }, { 0, 0, 0, 8 / Divisor, 4 / Divisor },
{ 2, 4, 8, 4, 2 } { 2 / Divisor, 4 / Divisor, 8 / Divisor, 4 / Divisor, 2 / Divisor }
}; };
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="BurksDiffuser"/> class. /// Initializes a new instance of the <see cref="BurksDiffuser"/> class.
/// </summary> /// </summary>
public BurksDiffuser() public BurksDiffuser()
: base(BurksMatrix, 32) : base(BurksMatrix)
{ {
} }
} }
} }

102
src/ImageSharp/Processing/Processors/Dithering/ErrorDiffuser.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
@ -15,66 +15,39 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
/// </summary> /// </summary>
public abstract class ErrorDiffuser : IErrorDiffuser public abstract class ErrorDiffuser : IErrorDiffuser
{ {
/// <summary> private readonly int offset;
/// The vector to perform division.
/// </summary>
private readonly Vector4 divisorVector;
/// <summary>
/// The matrix width.
/// </summary>
private readonly int matrixHeight;
/// <summary>
/// The matrix height.
/// </summary>
private readonly int matrixWidth;
/// <summary>
/// The offset at which to start the dithering operation.
/// </summary>
private readonly int startingOffset;
/// <summary>
/// The diffusion matrix.
/// </summary>
private readonly DenseMatrix<float> matrix; private readonly DenseMatrix<float> matrix;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ErrorDiffuser"/> class. /// Initializes a new instance of the <see cref="ErrorDiffuser"/> class.
/// </summary> /// </summary>
/// <param name="matrix">The dithering matrix.</param> /// <param name="matrix">The dithering matrix.</param>
/// <param name="divisor">The divisor.</param> internal ErrorDiffuser(in DenseMatrix<float> matrix)
internal ErrorDiffuser(in DenseMatrix<float> matrix, byte divisor)
{ {
Guard.MustBeGreaterThan(divisor, 0, nameof(divisor)); // Calculate the offset position of the pixel relative to
// the diffusion matrix.
this.matrix = matrix; this.offset = 0;
this.matrixWidth = this.matrix.Columns;
this.matrixHeight = this.matrix.Rows;
this.divisorVector = new Vector4(divisor);
this.startingOffset = 0; for (int col = 0; col < matrix.Columns; col++)
for (int i = 0; i < this.matrixWidth; i++)
{ {
// Good to disable here as we are not comparing mathematical output. if (matrix[0, col] != 0)
// ReSharper disable once CompareOfFloatsByEqualityOperator
if (matrix[0, i] != 0)
{ {
this.startingOffset = (byte)(i - 1); this.offset = col - 1;
break; break;
} }
} }
this.matrix = matrix;
} }
/// <inheritdoc /> /// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(InliningOptions.ShortMethod)]
public void Dither<TPixel>(ImageFrame<TPixel> image, TPixel source, TPixel transformed, int x, int y, int minX, int minY, int maxX, int maxY) public void Dither<TPixel>(ImageFrame<TPixel> image, TPixel source, TPixel transformed, int x, int y, int minX, int maxX, int maxY)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
image[x, y] = transformed; image[x, y] = transformed;
// Equal? Break out as there's nothing to pass. // Equal? Break out as there's no error to pass.
if (source.Equals(transformed)) if (source.Equals(transformed))
{ {
return; return;
@ -82,45 +55,40 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
// Calculate the error // Calculate the error
Vector4 error = source.ToVector4() - transformed.ToVector4(); Vector4 error = source.ToVector4() - transformed.ToVector4();
this.DoDither(image, x, y, minX, minY, maxX, maxY, error); this.DoDither(image, x, y, minX, maxX, maxY, error);
} }
[MethodImpl(MethodImplOptions.NoInlining)] [MethodImpl(InliningOptions.ShortMethod)]
private void DoDither<TPixel>(ImageFrame<TPixel> image, int x, int y, int minX, int minY, int maxX, int maxY, Vector4 error) private void DoDither<TPixel>(ImageFrame<TPixel> image, int x, int y, int minX, int maxX, int maxY, Vector4 error)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
int offset = this.offset;
DenseMatrix<float> matrix = this.matrix;
// Loop through and distribute the error amongst neighboring pixels. // Loop through and distribute the error amongst neighboring pixels.
for (int row = 0; row < this.matrixHeight; row++) for (int row = 0, targetY = y; row < matrix.Rows && targetY < maxY; row++, targetY++)
{ {
int matrixY = y + row; Span<TPixel> rowSpan = image.GetPixelRowSpan(targetY);
if (matrixY > minY && matrixY < maxY)
{
Span<TPixel> rowSpan = image.GetPixelRowSpan(matrixY);
for (int col = 0; col < this.matrixWidth; col++) for (int col = 0; col < matrix.Columns; col++)
{
int targetX = x + (col - offset);
if (targetX >= minX && targetX < maxX)
{ {
int matrixX = x + (col - this.startingOffset); float coefficient = matrix[row, col];
if (coefficient == 0)
if (matrixX > minX && matrixX < maxX)
{ {
float coefficient = this.matrix[row, col]; continue;
}
// Good to disable here as we are not comparing mathematical output.
// ReSharper disable once CompareOfFloatsByEqualityOperator
if (coefficient == 0)
{
continue;
}
ref TPixel pixel = ref rowSpan[matrixX]; ref TPixel pixel = ref rowSpan[targetX];
var offsetColor = pixel.ToVector4(); var result = pixel.ToVector4();
Vector4 result = ((error * coefficient) / this.divisorVector) + offsetColor; result += error * coefficient;
pixel.FromVector4(result); pixel.FromVector4(result);
}
} }
} }
} }
} }
} }
} }

10
src/ImageSharp/Processing/Processors/Dithering/ErrorDiffusionPaletteProcessor{TPixel}.cs

@ -13,7 +13,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
/// An <see cref="IImageProcessor{TPixel}"/> that dithers an image using error diffusion. /// An <see cref="IImageProcessor{TPixel}"/> that dithers an image using error diffusion.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
internal class ErrorDiffusionPaletteProcessor<TPixel> : PaletteDitherProcessor<TPixel> internal sealed class ErrorDiffusionPaletteProcessor<TPixel> : PaletteDitherProcessor<TPixel>
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
/// <summary> /// <summary>
@ -33,8 +33,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
protected override void OnFrameApply(ImageFrame<TPixel> source) protected override void OnFrameApply(ImageFrame<TPixel> source)
{ {
byte threshold = (byte)MathF.Round(this.Definition.Threshold * 255F); byte threshold = (byte)MathF.Round(this.Definition.Threshold * 255F);
bool isAlphaOnly = typeof(TPixel) == typeof(Alpha8);
var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds());
int startY = interest.Y; int startY = interest.Y;
int endY = interest.Bottom; int endY = interest.Bottom;
@ -49,7 +47,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
sourcePixel.ToRgba32(ref rgba); sourcePixel.ToRgba32(ref rgba);
// Convert to grayscale using ITU-R Recommendation BT.709 if required // Convert to grayscale using ITU-R Recommendation BT.709 if required
byte luminance = isAlphaOnly ? rgba.A : ImageMaths.Get8BitBT709Luminance(rgba.R, rgba.G, rgba.B); byte luminance = ImageMaths.Get8BitBT709Luminance(rgba.R, rgba.G, rgba.B);
for (int y = startY; y < endY; y++) for (int y = startY; y < endY; y++)
{ {
@ -72,14 +70,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
} }
sourcePixel.ToRgba32(ref rgba); sourcePixel.ToRgba32(ref rgba);
luminance = isAlphaOnly ? rgba.A : ImageMaths.Get8BitBT709Luminance(rgba.R, rgba.G, rgba.B); luminance = ImageMaths.Get8BitBT709Luminance(rgba.R, rgba.G, rgba.B);
// Setup the previous pointer // Setup the previous pointer
previousPixel = sourcePixel; previousPixel = sourcePixel;
} }
TPixel transformedPixel = luminance >= threshold ? pair.Second : pair.First; TPixel transformedPixel = luminance >= threshold ? pair.Second : pair.First;
this.Definition.Diffuser.Dither(source, sourcePixel, transformedPixel, x, y, startX, startY, endX, endY); this.Definition.Diffuser.Dither(source, sourcePixel, transformedPixel, x, y, startX, endX, endY);
} }
} }
} }

12
src/ImageSharp/Processing/Processors/Dithering/FloydSteinbergDiffuser.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Primitives; using SixLabors.ImageSharp.Primitives;
@ -11,22 +11,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
/// </summary> /// </summary>
public sealed class FloydSteinbergDiffuser : ErrorDiffuser public sealed class FloydSteinbergDiffuser : ErrorDiffuser
{ {
private const float Divisor = 16F;
/// <summary> /// <summary>
/// The diffusion matrix /// The diffusion matrix
/// </summary> /// </summary>
private static readonly DenseMatrix<float> FloydSteinbergMatrix = private static readonly DenseMatrix<float> FloydSteinbergMatrix =
new float[,] new float[,]
{ {
{ 0, 0, 7 }, { 0, 0, 7 / Divisor },
{ 3, 5, 1 } { 3 / Divisor, 5 / Divisor, 1 / Divisor }
}; };
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="FloydSteinbergDiffuser"/> class. /// Initializes a new instance of the <see cref="FloydSteinbergDiffuser"/> class.
/// </summary> /// </summary>
public FloydSteinbergDiffuser() public FloydSteinbergDiffuser()
: base(FloydSteinbergMatrix, 16) : base(FloydSteinbergMatrix)
{ {
} }
} }
} }

7
src/ImageSharp/Processing/Processors/Dithering/IErrorDiffuser.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -19,11 +19,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
/// <param name="x">The column index.</param> /// <param name="x">The column index.</param>
/// <param name="y">The row index.</param> /// <param name="y">The row index.</param>
/// <param name="minX">The minimum column value.</param> /// <param name="minX">The minimum column value.</param>
/// <param name="minY">The minimum row value.</param>
/// <param name="maxX">The maximum column value.</param> /// <param name="maxX">The maximum column value.</param>
/// <param name="maxY">The maximum row value.</param> /// <param name="maxY">The maximum row value.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
void Dither<TPixel>(ImageFrame<TPixel> image, TPixel source, TPixel transformed, int x, int y, int minX, int minY, int maxX, int maxY) void Dither<TPixel>(ImageFrame<TPixel> image, TPixel source, TPixel transformed, int x, int y, int minX, int maxX, int maxY)
where TPixel : struct, IPixel<TPixel>; where TPixel : struct, IPixel<TPixel>;
} }
} }

14
src/ImageSharp/Processing/Processors/Dithering/JarvisJudiceNinkeDiffuser.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Primitives; using SixLabors.ImageSharp.Primitives;
@ -11,23 +11,25 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
/// </summary> /// </summary>
public sealed class JarvisJudiceNinkeDiffuser : ErrorDiffuser public sealed class JarvisJudiceNinkeDiffuser : ErrorDiffuser
{ {
private const float Divisor = 48F;
/// <summary> /// <summary>
/// The diffusion matrix /// The diffusion matrix
/// </summary> /// </summary>
private static readonly DenseMatrix<float> JarvisJudiceNinkeMatrix = private static readonly DenseMatrix<float> JarvisJudiceNinkeMatrix =
new float[,] new float[,]
{ {
{ 0, 0, 0, 7, 5 }, { 0, 0, 0, 7 / Divisor, 5 / Divisor },
{ 3, 5, 7, 5, 3 }, { 3 / Divisor, 5 / Divisor, 7 / Divisor, 5 / Divisor, 3 / Divisor },
{ 1, 3, 5, 3, 1 } { 1 / Divisor, 3 / Divisor, 5 / Divisor, 3 / Divisor, 1 / Divisor }
}; };
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="JarvisJudiceNinkeDiffuser"/> class. /// Initializes a new instance of the <see cref="JarvisJudiceNinkeDiffuser"/> class.
/// </summary> /// </summary>
public JarvisJudiceNinkeDiffuser() public JarvisJudiceNinkeDiffuser()
: base(JarvisJudiceNinkeMatrix, 48) : base(JarvisJudiceNinkeMatrix)
{ {
} }
} }
} }

6
src/ImageSharp/Processing/Processors/Dithering/OrderedDitherPaletteProcessor{TPixel}.cs

@ -32,8 +32,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source) protected override void OnFrameApply(ImageFrame<TPixel> source)
{ {
bool isAlphaOnly = typeof(TPixel) == typeof(Alpha8);
var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds());
int startY = interest.Y; int startY = interest.Y;
int endY = interest.Bottom; int endY = interest.Bottom;
@ -48,7 +46,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
sourcePixel.ToRgba32(ref rgba); sourcePixel.ToRgba32(ref rgba);
// Convert to grayscale using ITU-R Recommendation BT.709 if required // Convert to grayscale using ITU-R Recommendation BT.709 if required
byte luminance = isAlphaOnly ? rgba.A : ImageMaths.Get8BitBT709Luminance(rgba.R, rgba.G, rgba.B); byte luminance = ImageMaths.Get8BitBT709Luminance(rgba.R, rgba.G, rgba.B);
for (int y = startY; y < endY; y++) for (int y = startY; y < endY; y++)
{ {
@ -71,7 +69,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
} }
sourcePixel.ToRgba32(ref rgba); sourcePixel.ToRgba32(ref rgba);
luminance = isAlphaOnly ? rgba.A : ImageMaths.Get8BitBT709Luminance(rgba.R, rgba.G, rgba.B); luminance = ImageMaths.Get8BitBT709Luminance(rgba.R, rgba.G, rgba.B);
// Setup the previous pointer // Setup the previous pointer
previousPixel = sourcePixel; previousPixel = sourcePixel;

69
src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs

@ -2,11 +2,13 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Buffers;
using System.Collections.Generic; using System.Collections.Generic;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Memory;
using SixLabors.Primitives; using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Dithering namespace SixLabors.ImageSharp.Processing.Processors.Dithering
@ -19,13 +21,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
private readonly Dictionary<TPixel, PixelPair<TPixel>> cache = new Dictionary<TPixel, PixelPair<TPixel>>(); private readonly Dictionary<TPixel, PixelPair<TPixel>> cache = new Dictionary<TPixel, PixelPair<TPixel>>();
private IMemoryOwner<TPixel> palette;
private TPixel[] palette; private IMemoryOwner<Vector4> paletteVector;
private bool palleteVectorMapped;
/// <summary> private bool isDisposed;
/// The vector representation of the image palette.
/// </summary>
private Vector4[] paletteVector;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="PaletteDitherProcessor{TPixel}"/> class. /// Initializes a new instance of the <see cref="PaletteDitherProcessor{TPixel}"/> class.
@ -37,6 +36,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
: base(source, sourceRectangle) : base(source, sourceRectangle)
{ {
this.Definition = definition; this.Definition = definition;
this.palette = this.Configuration.MemoryAllocator.Allocate<TPixel>(definition.Palette.Length);
this.paletteVector = this.Configuration.MemoryAllocator.Allocate<Vector4>(definition.Palette.Length);
} }
protected PaletteDitherProcessor Definition { get; } protected PaletteDitherProcessor Definition { get; }
@ -44,28 +45,45 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
/// <inheritdoc/> /// <inheritdoc/>
protected override void BeforeFrameApply(ImageFrame<TPixel> source) protected override void BeforeFrameApply(ImageFrame<TPixel> source)
{ {
// Lazy init palette: // Lazy init palettes:
if (this.palette is null) if (!this.palleteVectorMapped)
{ {
ReadOnlySpan<Color> sourcePalette = this.Definition.Palette.Span; ReadOnlySpan<Color> sourcePalette = this.Definition.Palette.Span;
this.palette = new TPixel[sourcePalette.Length]; Color.ToPixel(this.Configuration, sourcePalette, this.palette.Memory.Span);
Color.ToPixel<TPixel>(this.Configuration, sourcePalette, this.palette);
}
// Lazy init paletteVector:
if (this.paletteVector is null)
{
this.paletteVector = new Vector4[this.palette.Length];
PixelOperations<TPixel>.Instance.ToVector4( PixelOperations<TPixel>.Instance.ToVector4(
this.Configuration, this.Configuration,
(ReadOnlySpan<TPixel>)this.palette, this.palette.Memory.Span,
(Span<Vector4>)this.paletteVector, this.paletteVector.Memory.Span,
PixelConversionModifiers.Scale); PixelConversionModifiers.Scale);
} }
this.palleteVectorMapped = true;
base.BeforeFrameApply(source); base.BeforeFrameApply(source);
} }
/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
if (this.isDisposed)
{
return;
}
if (disposing)
{
this.palette?.Dispose();
this.paletteVector?.Dispose();
}
this.palette = null;
this.paletteVector = null;
this.isDisposed = true;
base.Dispose(disposing);
}
/// <summary> /// <summary>
/// Returns the two closest colors from the palette calculated via Euclidean distance in the Rgba space. /// Returns the two closest colors from the palette calculated via Euclidean distance in the Rgba space.
/// </summary> /// </summary>
@ -93,21 +111,26 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
TPixel closest = default; TPixel closest = default;
TPixel secondClosest = default; TPixel secondClosest = default;
for (int index = 0; index < this.paletteVector.Length; index++) Span<TPixel> paletteSpan = this.palette.Memory.Span;
ref TPixel paletteSpanBase = ref MemoryMarshal.GetReference(paletteSpan);
Span<Vector4> paletteVectorSpan = this.paletteVector.Memory.Span;
ref Vector4 paletteVectorSpanBase = ref MemoryMarshal.GetReference(paletteVectorSpan);
for (int index = 0; index < paletteVectorSpan.Length; index++)
{ {
ref Vector4 candidate = ref this.paletteVector[index]; ref Vector4 candidate = ref Unsafe.Add(ref paletteVectorSpanBase, index);
float distance = Vector4.DistanceSquared(vector, candidate); float distance = Vector4.DistanceSquared(vector, candidate);
if (distance < leastDistance) if (distance < leastDistance)
{ {
leastDistance = distance; leastDistance = distance;
secondClosest = closest; secondClosest = closest;
closest = this.palette[index]; closest = Unsafe.Add(ref paletteSpanBase, index);
} }
else if (distance < secondLeastDistance) else if (distance < secondLeastDistance)
{ {
secondLeastDistance = distance; secondLeastDistance = distance;
secondClosest = this.palette[index]; secondClosest = Unsafe.Add(ref paletteSpanBase, index);
} }
} }

12
src/ImageSharp/Processing/Processors/Dithering/Sierra2Diffuser.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Primitives; using SixLabors.ImageSharp.Primitives;
@ -11,22 +11,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
/// </summary> /// </summary>
public sealed class Sierra2Diffuser : ErrorDiffuser public sealed class Sierra2Diffuser : ErrorDiffuser
{ {
private const float Divisor = 16F;
/// <summary> /// <summary>
/// The diffusion matrix /// The diffusion matrix
/// </summary> /// </summary>
private static readonly DenseMatrix<float> Sierra2Matrix = private static readonly DenseMatrix<float> Sierra2Matrix =
new float[,] new float[,]
{ {
{ 0, 0, 0, 4, 3 }, { 0, 0, 0, 4 / Divisor, 3 / Divisor },
{ 1, 2, 3, 2, 1 } { 1 / Divisor, 2 / Divisor, 3 / Divisor, 2 / Divisor, 1 / Divisor }
}; };
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Sierra2Diffuser"/> class. /// Initializes a new instance of the <see cref="Sierra2Diffuser"/> class.
/// </summary> /// </summary>
public Sierra2Diffuser() public Sierra2Diffuser()
: base(Sierra2Matrix, 16) : base(Sierra2Matrix)
{ {
} }
} }
} }

14
src/ImageSharp/Processing/Processors/Dithering/Sierra3Diffuser.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Primitives; using SixLabors.ImageSharp.Primitives;
@ -11,23 +11,25 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
/// </summary> /// </summary>
public sealed class Sierra3Diffuser : ErrorDiffuser public sealed class Sierra3Diffuser : ErrorDiffuser
{ {
private const float Divisor = 32F;
/// <summary> /// <summary>
/// The diffusion matrix /// The diffusion matrix
/// </summary> /// </summary>
private static readonly DenseMatrix<float> Sierra3Matrix = private static readonly DenseMatrix<float> Sierra3Matrix =
new float[,] new float[,]
{ {
{ 0, 0, 0, 5, 3 }, { 0, 0, 0, 5 / Divisor, 3 / Divisor },
{ 2, 4, 5, 4, 2 }, { 2 / Divisor, 4 / Divisor, 5 / Divisor, 4 / Divisor, 2 / Divisor },
{ 0, 2, 3, 2, 0 } { 0, 2 / Divisor, 3 / Divisor, 2 / Divisor, 0 }
}; };
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Sierra3Diffuser"/> class. /// Initializes a new instance of the <see cref="Sierra3Diffuser"/> class.
/// </summary> /// </summary>
public Sierra3Diffuser() public Sierra3Diffuser()
: base(Sierra3Matrix, 32) : base(Sierra3Matrix)
{ {
} }
} }
} }

12
src/ImageSharp/Processing/Processors/Dithering/SierraLiteDiffuser.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Primitives; using SixLabors.ImageSharp.Primitives;
@ -11,22 +11,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
/// </summary> /// </summary>
public sealed class SierraLiteDiffuser : ErrorDiffuser public sealed class SierraLiteDiffuser : ErrorDiffuser
{ {
private const float Divisor = 4F;
/// <summary> /// <summary>
/// The diffusion matrix /// The diffusion matrix
/// </summary> /// </summary>
private static readonly DenseMatrix<float> SierraLiteMatrix = private static readonly DenseMatrix<float> SierraLiteMatrix =
new float[,] new float[,]
{ {
{ 0, 0, 2 }, { 0, 0, 2 / Divisor },
{ 1, 1, 0 } { 1 / Divisor, 1 / Divisor, 0 }
}; };
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="SierraLiteDiffuser"/> class. /// Initializes a new instance of the <see cref="SierraLiteDiffuser"/> class.
/// </summary> /// </summary>
public SierraLiteDiffuser() public SierraLiteDiffuser()
: base(SierraLiteMatrix, 4) : base(SierraLiteMatrix)
{ {
} }
} }
} }

16
src/ImageSharp/Processing/Processors/Dithering/StevensonArceDiffuser.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Primitives; using SixLabors.ImageSharp.Primitives;
@ -10,24 +10,26 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
/// </summary> /// </summary>
public sealed class StevensonArceDiffuser : ErrorDiffuser public sealed class StevensonArceDiffuser : ErrorDiffuser
{ {
private const float Divisor = 200F;
/// <summary> /// <summary>
/// The diffusion matrix /// The diffusion matrix
/// </summary> /// </summary>
private static readonly DenseMatrix<float> StevensonArceMatrix = private static readonly DenseMatrix<float> StevensonArceMatrix =
new float[,] new float[,]
{ {
{ 0, 0, 0, 0, 0, 32, 0 }, { 0, 0, 0, 0, 0, 32 / Divisor, 0 },
{ 12, 0, 26, 0, 30, 0, 16 }, { 12 / Divisor, 0, 26 / Divisor, 0, 30 / Divisor, 0, 16 / Divisor },
{ 0, 12, 0, 26, 0, 12, 0 }, { 0, 12 / Divisor, 0, 26 / Divisor, 0, 12 / Divisor, 0 },
{ 5, 0, 12, 0, 12, 0, 5 } { 5 / Divisor, 0, 12 / Divisor, 0, 12 / Divisor, 0, 5 / Divisor }
}; };
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="StevensonArceDiffuser"/> class. /// Initializes a new instance of the <see cref="StevensonArceDiffuser"/> class.
/// </summary> /// </summary>
public StevensonArceDiffuser() public StevensonArceDiffuser()
: base(StevensonArceMatrix, 200) : base(StevensonArceMatrix)
{ {
} }
} }
} }

14
src/ImageSharp/Processing/Processors/Dithering/StuckiDiffuser.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Primitives; using SixLabors.ImageSharp.Primitives;
@ -11,23 +11,25 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
/// </summary> /// </summary>
public sealed class StuckiDiffuser : ErrorDiffuser public sealed class StuckiDiffuser : ErrorDiffuser
{ {
private const float Divisor = 42F;
/// <summary> /// <summary>
/// The diffusion matrix /// The diffusion matrix
/// </summary> /// </summary>
private static readonly DenseMatrix<float> StuckiMatrix = private static readonly DenseMatrix<float> StuckiMatrix =
new float[,] new float[,]
{ {
{ 0, 0, 0, 8, 4 }, { 0, 0, 0, 8 / Divisor, 4 / Divisor },
{ 2, 4, 8, 4, 2 }, { 2 / Divisor, 4 / Divisor, 8 / Divisor, 4 / Divisor, 2 / Divisor },
{ 1, 2, 4, 2, 1 } { 1 / Divisor, 2 / Divisor, 4 / Divisor, 2 / Divisor, 1 / Divisor }
}; };
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="StuckiDiffuser"/> class. /// Initializes a new instance of the <see cref="StuckiDiffuser"/> class.
/// </summary> /// </summary>
public StuckiDiffuser() public StuckiDiffuser()
: base(StuckiMatrix, 42) : base(StuckiMatrix)
{ {
} }
} }
} }

3
src/ImageSharp/Processing/Processors/Dithering/error_diffusion.txt

@ -1,3 +1,6 @@
Reference:
http://bisqwit.iki.fi/jutut/kuvat/ordered_dither/error_diffusion.txt
List of error diffusion schemes. List of error diffusion schemes.
Quantization error of *current* pixel is added to the pixels Quantization error of *current* pixel is added to the pixels

55
src/ImageSharp/Processing/Processors/Quantization/FrameQuantizer{TPixel}.cs

@ -1,11 +1,12 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Buffers;
using System.Collections.Generic; using System.Collections.Generic;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Dithering; using SixLabors.ImageSharp.Processing.Processors.Dithering;
@ -31,7 +32,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <summary> /// <summary>
/// The vector representation of the image palette. /// The vector representation of the image palette.
/// </summary> /// </summary>
private Vector4[] paletteVector; private IMemoryOwner<Vector4> paletteVector;
private bool isDisposed;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="FrameQuantizer{TPixel}"/> class. /// Initializes a new instance of the <see cref="FrameQuantizer{TPixel}"/> class.
@ -80,8 +83,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
public bool Dither { get; } public bool Dither { get; }
/// <inheritdoc/> /// <inheritdoc/>
public virtual void Dispose() public void Dispose()
{ {
this.Dispose(true);
GC.SuppressFinalize(this);
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -103,11 +108,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
// Collect the palette. Required before the second pass runs. // Collect the palette. Required before the second pass runs.
ReadOnlyMemory<TPixel> palette = this.GetPalette(); ReadOnlyMemory<TPixel> palette = this.GetPalette();
this.paletteVector = new Vector4[palette.Length]; this.paletteVector = image.Configuration.MemoryAllocator.Allocate<Vector4>(palette.Length);
PixelOperations<TPixel>.Instance.ToVector4( PixelOperations<TPixel>.Instance.ToVector4(
image.Configuration, image.Configuration,
palette.Span, palette.Span,
(Span<Vector4>)this.paletteVector, this.paletteVector.Memory.Span,
PixelConversionModifiers.Scale); PixelConversionModifiers.Scale);
var quantizedFrame = new QuantizedFrame<TPixel>(image.MemoryAllocator, width, height, palette); var quantizedFrame = new QuantizedFrame<TPixel>(image.MemoryAllocator, width, height, palette);
@ -129,6 +134,27 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
return quantizedFrame; return quantizedFrame;
} }
/// <summary>
/// Disposes the object and frees resources for the Garbage Collector.
/// </summary>
/// <param name="disposing">Whether to dispose managed and unmanaged objects.</param>
protected virtual void Dispose(bool disposing)
{
if (this.isDisposed)
{
return;
}
if (disposing)
{
this.paletteVector?.Dispose();
}
this.paletteVector = null;
this.isDisposed = true;
}
/// <summary> /// <summary>
/// Execute the first pass through the pixels in the image to create the palette. /// Execute the first pass through the pixels in the image to create the palette.
/// </summary> /// </summary>
@ -161,7 +187,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// Retrieve the palette for the quantized image. /// Retrieve the palette for the quantized image.
/// </summary> /// </summary>
/// <returns> /// <returns>
/// <see cref="T:TPixel[]"/> /// <see cref="ReadOnlyMemory{TPixel}"/>
/// </returns> /// </returns>
protected abstract ReadOnlyMemory<TPixel> GetPalette(); protected abstract ReadOnlyMemory<TPixel> GetPalette();
@ -173,12 +199,15 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
protected byte GetTransparentIndex() protected byte GetTransparentIndex()
{ {
// Transparent pixels are much more likely to be found at the end of a palette. // Transparent pixels are much more likely to be found at the end of a palette.
int paletteVectorLengthMinus1 = this.paletteVector.Length - 1; Span<Vector4> paletteVectorSpan = this.paletteVector.Memory.Span;
ref Vector4 paletteVectorSpanBase = ref MemoryMarshal.GetReference(paletteVectorSpan);
int paletteVectorLengthMinus1 = paletteVectorSpan.Length - 1;
int index = paletteVectorLengthMinus1; int index = paletteVectorLengthMinus1;
for (int i = paletteVectorLengthMinus1; i >= 0; i--) for (int i = paletteVectorLengthMinus1; i >= 0; i--)
{ {
ref Vector4 candidate = ref this.paletteVector[i]; ref Vector4 candidate = ref Unsafe.Add(ref paletteVectorSpanBase, i);
if (candidate.Equals(default)) if (candidate.Equals(default))
{ {
index = i; index = i;
@ -211,10 +240,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
float leastDistance = float.MaxValue; float leastDistance = float.MaxValue;
Vector4 vector = pixel.ToScaledVector4(); Vector4 vector = pixel.ToScaledVector4();
float epsilon = Constants.EpsilonSquared; float epsilon = Constants.EpsilonSquared;
Span<Vector4> paletteVectorSpan = this.paletteVector.Memory.Span;
ref Vector4 paletteVectorSpanBase = ref MemoryMarshal.GetReference(paletteVectorSpan);
for (int index = 0; index < this.paletteVector.Length; index++) for (int index = 0; index < paletteVectorSpan.Length; index++)
{ {
ref Vector4 candidate = ref this.paletteVector[index]; ref Vector4 candidate = ref Unsafe.Add(ref paletteVectorSpanBase, index);
float distance = Vector4.DistanceSquared(vector, candidate); float distance = Vector4.DistanceSquared(vector, candidate);
// Greater... Move on. // Greater... Move on.
@ -239,4 +270,4 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
return result; return result;
} }
} }
} }

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

@ -1,4 +1,4 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
@ -128,7 +128,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
if (this.Dither) if (this.Dither)
{ {
// Apply the dithering matrix. We have to reapply the value now as the original has changed. // Apply the dithering matrix. We have to reapply the value now as the original has changed.
this.Diffuser.Dither(source, sourcePixel, transformedPixel, x, y, 0, 0, width, height); this.Diffuser.Dither(source, sourcePixel, transformedPixel, x, y, 0, width, height);
} }
output[(y * source.Width) + x] = pixelValue; output[(y * source.Width) + x] = pixelValue;
@ -571,4 +571,4 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
} }
} }
} }
} }

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

@ -1,4 +1,4 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
@ -76,7 +76,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
if (this.Dither) if (this.Dither)
{ {
// Apply the dithering matrix. We have to reapply the value now as the original has changed. // Apply the dithering matrix. We have to reapply the value now as the original has changed.
this.Diffuser.Dither(source, sourcePixel, transformedPixel, x, y, 0, 0, width, height); this.Diffuser.Dither(source, sourcePixel, transformedPixel, x, y, 0, width, height);
} }
output[(y * source.Width) + x] = pixelValue; output[(y * source.Width) + x] = pixelValue;
@ -98,4 +98,4 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private byte QuantizePixel(ref TPixel pixel) => this.GetClosestPixel(ref pixel); private byte QuantizePixel(ref TPixel pixel) => this.GetClosestPixel(ref pixel);
} }
} }

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

@ -117,6 +117,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// </summary> /// </summary>
private Box[] colorCube; private Box[] colorCube;
private bool isDisposed;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="WuFrameQuantizer{TPixel}"/> class. /// Initializes a new instance of the <see cref="WuFrameQuantizer{TPixel}"/> class.
/// </summary> /// </summary>
@ -158,15 +160,23 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
} }
/// <inheritdoc/> /// <inheritdoc/>
public override void Dispose() protected override void Dispose(bool disposing)
{ {
this.vwt?.Dispose(); if (this.isDisposed)
this.vmr?.Dispose(); {
this.vmg?.Dispose(); return;
this.vmb?.Dispose(); }
this.vma?.Dispose();
this.m2?.Dispose(); if (disposing)
this.tag?.Dispose(); {
this.vwt?.Dispose();
this.vmr?.Dispose();
this.vmg?.Dispose();
this.vmb?.Dispose();
this.vma?.Dispose();
this.m2?.Dispose();
this.tag?.Dispose();
}
this.vwt = null; this.vwt = null;
this.vmr = null; this.vmr = null;
@ -175,6 +185,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
this.vma = null; this.vma = null;
this.m2 = null; this.m2 = null;
this.tag = null; this.tag = null;
this.isDisposed = true;
base.Dispose(true);
} }
internal ReadOnlyMemory<TPixel> AotGetPalette() => this.GetPalette(); internal ReadOnlyMemory<TPixel> AotGetPalette() => this.GetPalette();
@ -260,7 +273,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
if (this.Dither) if (this.Dither)
{ {
// Apply the dithering matrix. We have to reapply the value now as the original has changed. // Apply the dithering matrix. We have to reapply the value now as the original has changed.
this.Diffuser.Dither(source, sourcePixel, transformedPixel, x, y, 0, 0, width, height); this.Diffuser.Dither(source, sourcePixel, transformedPixel, x, y, 0, width, height);
} }
output[(y * source.Width) + x] = pixelValue; output[(y * source.Width) + x] = pixelValue;

49
tests/ImageSharp.Benchmarks/Samplers/Diffuse.cs

@ -0,0 +1,49 @@
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Dithering;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Benchmarks.Samplers
{
[Config(typeof(Config.ShortClr))]
public class Diffuse
{
[Benchmark]
public Size DoDiffuse()
{
using (var image = new Image<Rgba32>(Configuration.Default, 800, 800, Rgba32.BlanchedAlmond))
{
image.Mutate(x => x.Diffuse());
return image.Size();
}
}
}
}
// #### 25th October 2019 ####
//
// BenchmarkDotNet=v0.11.5, OS=Windows 10.0.18362
// Intel Core i7-8650U CPU 1.90GHz(Kaby Lake R), 1 CPU, 8 logical and 4 physical cores
// .NET Core SDK = 3.0.100
//
// [Host] : .NET Core 2.1.13 (CoreCLR 4.6.28008.01, CoreFX 4.6.28008.01), 64bit RyuJIT
// Clr : .NET Framework 4.7.2 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.8.4018.0
// Core : .NET Core 2.1.13 (CoreCLR 4.6.28008.01, CoreFX 4.6.28008.01), 64bit RyuJIT
//
// IterationCount=3 LaunchCount=1 WarmupCount=3
//
// #### Before ####
//
// | Method | Job | Runtime | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated |
// |---------- |----- |-------- |----------:|---------:|---------:|------:|------:|------:|----------:|
// | DoDiffuse | Clr | Clr | 129.58 ms | 24.60 ms | 1.349 ms | - | - | - | 6 KB |
// | DoDiffuse | Core | Core | 92.63 ms | 89.78 ms | 4.921 ms | - | - | - | 4.58 KB |
//
// #### After ####
//
// | Method | Job | Runtime | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated |
// |---------- |----- |-------- |----------:|----------:|----------:|------:|------:|------:|----------:|
// | DoDiffuse | Clr | Clr | 124.93 ms | 33.297 ms | 1.8251 ms | - | - | - | 2 KB |
// | DoDiffuse | Core | Core | 89.63 ms | 9.895 ms | 0.5424 ms | - | - | - | 1.91 KB |

2
tests/Images/External

@ -1 +1 @@
Subproject commit 1d3d4e3652dc95bd8bd420346bfe0f189addc587 Subproject commit 563ec6f7774734ba39924174c8961705a1ea6fa2
Loading…
Cancel
Save