Browse Source

Merge branch 'master' into feature/tga

af/merge-core
James Jackson-South 6 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;
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.
using System;
@ -95,4 +95,4 @@ namespace SixLabors.ImageSharp.Processing.Dithering
Rectangle 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.
/// </summary>
/// <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>
{
private readonly BinaryErrorDiffusionProcessor definition;
@ -76,7 +76,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization
}
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.
using SixLabors.ImageSharp.Primitives;
@ -11,22 +11,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
/// </summary>
public sealed class AtkinsonDiffuser : ErrorDiffuser
{
private const float Divisor = 8F;
/// <summary>
/// The diffusion matrix
/// </summary>
private static readonly DenseMatrix<float> AtkinsonMatrix =
new float[,]
{
{ 0, 0, 1, 1 },
{ 1, 1, 1, 0 },
{ 0, 1, 0, 0 }
{ 0, 0, 1 / Divisor, 1 / Divisor },
{ 1 / Divisor, 1 / Divisor, 1 / Divisor, 0 },
{ 0, 1 / Divisor, 0, 0 }
};
/// <summary>
/// Initializes a new instance of the <see cref="AtkinsonDiffuser"/> class.
/// </summary>
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.
using SixLabors.ImageSharp.Primitives;
@ -11,22 +11,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
/// </summary>
public sealed class BurksDiffuser : ErrorDiffuser
{
private const float Divisor = 32F;
/// <summary>
/// The diffusion matrix
/// </summary>
private static readonly DenseMatrix<float> BurksMatrix =
new float[,]
{
{ 0, 0, 0, 8, 4 },
{ 2, 4, 8, 4, 2 }
{ 0, 0, 0, 8 / Divisor, 4 / Divisor },
{ 2 / Divisor, 4 / Divisor, 8 / Divisor, 4 / Divisor, 2 / Divisor }
};
/// <summary>
/// Initializes a new instance of the <see cref="BurksDiffuser"/> class.
/// </summary>
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.
using System;
@ -15,66 +15,39 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
/// </summary>
public abstract class ErrorDiffuser : IErrorDiffuser
{
/// <summary>
/// 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 int offset;
private readonly DenseMatrix<float> matrix;
/// <summary>
/// Initializes a new instance of the <see cref="ErrorDiffuser"/> class.
/// </summary>
/// <param name="matrix">The dithering matrix.</param>
/// <param name="divisor">The divisor.</param>
internal ErrorDiffuser(in DenseMatrix<float> matrix, byte divisor)
internal ErrorDiffuser(in DenseMatrix<float> matrix)
{
Guard.MustBeGreaterThan(divisor, 0, nameof(divisor));
this.matrix = matrix;
this.matrixWidth = this.matrix.Columns;
this.matrixHeight = this.matrix.Rows;
this.divisorVector = new Vector4(divisor);
// Calculate the offset position of the pixel relative to
// the diffusion matrix.
this.offset = 0;
this.startingOffset = 0;
for (int i = 0; i < this.matrixWidth; i++)
for (int col = 0; col < matrix.Columns; col++)
{
// Good to disable here as we are not comparing mathematical output.
// ReSharper disable once CompareOfFloatsByEqualityOperator
if (matrix[0, i] != 0)
if (matrix[0, col] != 0)
{
this.startingOffset = (byte)(i - 1);
this.offset = col - 1;
break;
}
}
this.matrix = matrix;
}
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Dither<TPixel>(ImageFrame<TPixel> image, TPixel source, TPixel transformed, int x, int y, int minX, int minY, int maxX, int maxY)
[MethodImpl(InliningOptions.ShortMethod)]
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>
{
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))
{
return;
@ -82,45 +55,40 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
// Calculate the error
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)]
private void DoDither<TPixel>(ImageFrame<TPixel> image, int x, int y, int minX, int minY, int maxX, int maxY, Vector4 error)
[MethodImpl(InliningOptions.ShortMethod)]
private void DoDither<TPixel>(ImageFrame<TPixel> image, int x, int y, int minX, int maxX, int maxY, Vector4 error)
where TPixel : struct, IPixel<TPixel>
{
int offset = this.offset;
DenseMatrix<float> matrix = this.matrix;
// 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;
if (matrixY > minY && matrixY < maxY)
{
Span<TPixel> rowSpan = image.GetPixelRowSpan(matrixY);
Span<TPixel> rowSpan = image.GetPixelRowSpan(targetY);
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);
if (matrixX > minX && matrixX < maxX)
float coefficient = matrix[row, col];
if (coefficient == 0)
{
float coefficient = this.matrix[row, col];
// Good to disable here as we are not comparing mathematical output.
// ReSharper disable once CompareOfFloatsByEqualityOperator
if (coefficient == 0)
{
continue;
}
continue;
}
ref TPixel pixel = ref rowSpan[matrixX];
var offsetColor = pixel.ToVector4();
ref TPixel pixel = ref rowSpan[targetX];
var result = pixel.ToVector4();
Vector4 result = ((error * coefficient) / this.divisorVector) + offsetColor;
pixel.FromVector4(result);
}
result += error * coefficient;
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.
/// </summary>
/// <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>
{
/// <summary>
@ -33,8 +33,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
protected override void OnFrameApply(ImageFrame<TPixel> source)
{
byte threshold = (byte)MathF.Round(this.Definition.Threshold * 255F);
bool isAlphaOnly = typeof(TPixel) == typeof(Alpha8);
var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds());
int startY = interest.Y;
int endY = interest.Bottom;
@ -49,7 +47,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
sourcePixel.ToRgba32(ref rgba);
// 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++)
{
@ -72,14 +70,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
}
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
previousPixel = sourcePixel;
}
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.
using SixLabors.ImageSharp.Primitives;
@ -11,22 +11,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
/// </summary>
public sealed class FloydSteinbergDiffuser : ErrorDiffuser
{
private const float Divisor = 16F;
/// <summary>
/// The diffusion matrix
/// </summary>
private static readonly DenseMatrix<float> FloydSteinbergMatrix =
new float[,]
{
{ 0, 0, 7 },
{ 3, 5, 1 }
{ 0, 0, 7 / Divisor },
{ 3 / Divisor, 5 / Divisor, 1 / Divisor }
};
/// <summary>
/// Initializes a new instance of the <see cref="FloydSteinbergDiffuser"/> class.
/// </summary>
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.
using SixLabors.ImageSharp.PixelFormats;
@ -19,11 +19,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
/// <param name="x">The column index.</param>
/// <param name="y">The row index.</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="maxY">The maximum row value.</param>
/// <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>;
}
}
}

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.
using SixLabors.ImageSharp.Primitives;
@ -11,23 +11,25 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
/// </summary>
public sealed class JarvisJudiceNinkeDiffuser : ErrorDiffuser
{
private const float Divisor = 48F;
/// <summary>
/// The diffusion matrix
/// </summary>
private static readonly DenseMatrix<float> JarvisJudiceNinkeMatrix =
new float[,]
{
{ 0, 0, 0, 7, 5 },
{ 3, 5, 7, 5, 3 },
{ 1, 3, 5, 3, 1 }
{ 0, 0, 0, 7 / Divisor, 5 / Divisor },
{ 3 / Divisor, 5 / Divisor, 7 / Divisor, 5 / Divisor, 3 / Divisor },
{ 1 / Divisor, 3 / Divisor, 5 / Divisor, 3 / Divisor, 1 / Divisor }
};
/// <summary>
/// Initializes a new instance of the <see cref="JarvisJudiceNinkeDiffuser"/> class.
/// </summary>
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/>
protected override void OnFrameApply(ImageFrame<TPixel> source)
{
bool isAlphaOnly = typeof(TPixel) == typeof(Alpha8);
var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds());
int startY = interest.Y;
int endY = interest.Bottom;
@ -48,7 +46,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
sourcePixel.ToRgba32(ref rgba);
// 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++)
{
@ -71,7 +69,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
}
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
previousPixel = sourcePixel;

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

@ -2,11 +2,13 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Memory;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Dithering
@ -19,13 +21,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
where TPixel : struct, IPixel<TPixel>
{
private readonly Dictionary<TPixel, PixelPair<TPixel>> cache = new Dictionary<TPixel, PixelPair<TPixel>>();
private TPixel[] palette;
/// <summary>
/// The vector representation of the image palette.
/// </summary>
private Vector4[] paletteVector;
private IMemoryOwner<TPixel> palette;
private IMemoryOwner<Vector4> paletteVector;
private bool palleteVectorMapped;
private bool isDisposed;
/// <summary>
/// Initializes a new instance of the <see cref="PaletteDitherProcessor{TPixel}"/> class.
@ -37,6 +36,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
: base(source, sourceRectangle)
{
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; }
@ -44,28 +45,45 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
/// <inheritdoc/>
protected override void BeforeFrameApply(ImageFrame<TPixel> source)
{
// Lazy init palette:
if (this.palette is null)
// Lazy init palettes:
if (!this.palleteVectorMapped)
{
ReadOnlySpan<Color> sourcePalette = this.Definition.Palette.Span;
this.palette = new TPixel[sourcePalette.Length];
Color.ToPixel<TPixel>(this.Configuration, sourcePalette, this.palette);
}
Color.ToPixel(this.Configuration, sourcePalette, this.palette.Memory.Span);
// Lazy init paletteVector:
if (this.paletteVector is null)
{
this.paletteVector = new Vector4[this.palette.Length];
PixelOperations<TPixel>.Instance.ToVector4(
this.Configuration,
(ReadOnlySpan<TPixel>)this.palette,
(Span<Vector4>)this.paletteVector,
this.palette.Memory.Span,
this.paletteVector.Memory.Span,
PixelConversionModifiers.Scale);
}
this.palleteVectorMapped = true;
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>
/// Returns the two closest colors from the palette calculated via Euclidean distance in the Rgba space.
/// </summary>
@ -93,21 +111,26 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
TPixel closest = 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);
if (distance < leastDistance)
{
leastDistance = distance;
secondClosest = closest;
closest = this.palette[index];
closest = Unsafe.Add(ref paletteSpanBase, index);
}
else if (distance < secondLeastDistance)
{
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.
using SixLabors.ImageSharp.Primitives;
@ -11,22 +11,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
/// </summary>
public sealed class Sierra2Diffuser : ErrorDiffuser
{
private const float Divisor = 16F;
/// <summary>
/// The diffusion matrix
/// </summary>
private static readonly DenseMatrix<float> Sierra2Matrix =
new float[,]
{
{ 0, 0, 0, 4, 3 },
{ 1, 2, 3, 2, 1 }
{ 0, 0, 0, 4 / Divisor, 3 / Divisor },
{ 1 / Divisor, 2 / Divisor, 3 / Divisor, 2 / Divisor, 1 / Divisor }
};
/// <summary>
/// Initializes a new instance of the <see cref="Sierra2Diffuser"/> class.
/// </summary>
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.
using SixLabors.ImageSharp.Primitives;
@ -11,23 +11,25 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
/// </summary>
public sealed class Sierra3Diffuser : ErrorDiffuser
{
private const float Divisor = 32F;
/// <summary>
/// The diffusion matrix
/// </summary>
private static readonly DenseMatrix<float> Sierra3Matrix =
new float[,]
{
{ 0, 0, 0, 5, 3 },
{ 2, 4, 5, 4, 2 },
{ 0, 2, 3, 2, 0 }
{ 0, 0, 0, 5 / Divisor, 3 / Divisor },
{ 2 / Divisor, 4 / Divisor, 5 / Divisor, 4 / Divisor, 2 / Divisor },
{ 0, 2 / Divisor, 3 / Divisor, 2 / Divisor, 0 }
};
/// <summary>
/// Initializes a new instance of the <see cref="Sierra3Diffuser"/> class.
/// </summary>
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.
using SixLabors.ImageSharp.Primitives;
@ -11,22 +11,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
/// </summary>
public sealed class SierraLiteDiffuser : ErrorDiffuser
{
private const float Divisor = 4F;
/// <summary>
/// The diffusion matrix
/// </summary>
private static readonly DenseMatrix<float> SierraLiteMatrix =
new float[,]
{
{ 0, 0, 2 },
{ 1, 1, 0 }
{ 0, 0, 2 / Divisor },
{ 1 / Divisor, 1 / Divisor, 0 }
};
/// <summary>
/// Initializes a new instance of the <see cref="SierraLiteDiffuser"/> class.
/// </summary>
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.
using SixLabors.ImageSharp.Primitives;
@ -10,24 +10,26 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
/// </summary>
public sealed class StevensonArceDiffuser : ErrorDiffuser
{
private const float Divisor = 200F;
/// <summary>
/// The diffusion matrix
/// </summary>
private static readonly DenseMatrix<float> StevensonArceMatrix =
new float[,]
{
{ 0, 0, 0, 0, 0, 32, 0 },
{ 12, 0, 26, 0, 30, 0, 16 },
{ 0, 12, 0, 26, 0, 12, 0 },
{ 5, 0, 12, 0, 12, 0, 5 }
{ 0, 0, 0, 0, 0, 32 / Divisor, 0 },
{ 12 / Divisor, 0, 26 / Divisor, 0, 30 / Divisor, 0, 16 / Divisor },
{ 0, 12 / Divisor, 0, 26 / Divisor, 0, 12 / Divisor, 0 },
{ 5 / Divisor, 0, 12 / Divisor, 0, 12 / Divisor, 0, 5 / Divisor }
};
/// <summary>
/// Initializes a new instance of the <see cref="StevensonArceDiffuser"/> class.
/// </summary>
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.
using SixLabors.ImageSharp.Primitives;
@ -11,23 +11,25 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
/// </summary>
public sealed class StuckiDiffuser : ErrorDiffuser
{
private const float Divisor = 42F;
/// <summary>
/// The diffusion matrix
/// </summary>
private static readonly DenseMatrix<float> StuckiMatrix =
new float[,]
{
{ 0, 0, 0, 8, 4 },
{ 2, 4, 8, 4, 2 },
{ 1, 2, 4, 2, 1 }
{ 0, 0, 0, 8 / Divisor, 4 / Divisor },
{ 2 / Divisor, 4 / Divisor, 8 / Divisor, 4 / Divisor, 2 / Divisor },
{ 1 / Divisor, 2 / Divisor, 4 / Divisor, 2 / Divisor, 1 / Divisor }
};
/// <summary>
/// Initializes a new instance of the <see cref="StuckiDiffuser"/> class.
/// </summary>
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.
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.
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Dithering;
@ -31,7 +32,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <summary>
/// The vector representation of the image palette.
/// </summary>
private Vector4[] paletteVector;
private IMemoryOwner<Vector4> paletteVector;
private bool isDisposed;
/// <summary>
/// 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; }
/// <inheritdoc/>
public virtual void Dispose()
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
/// <inheritdoc/>
@ -103,11 +108,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
// Collect the palette. Required before the second pass runs.
ReadOnlyMemory<TPixel> palette = this.GetPalette();
this.paletteVector = new Vector4[palette.Length];
this.paletteVector = image.Configuration.MemoryAllocator.Allocate<Vector4>(palette.Length);
PixelOperations<TPixel>.Instance.ToVector4(
image.Configuration,
palette.Span,
(Span<Vector4>)this.paletteVector,
this.paletteVector.Memory.Span,
PixelConversionModifiers.Scale);
var quantizedFrame = new QuantizedFrame<TPixel>(image.MemoryAllocator, width, height, palette);
@ -129,6 +134,27 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
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>
/// Execute the first pass through the pixels in the image to create the palette.
/// </summary>
@ -161,7 +187,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// Retrieve the palette for the quantized image.
/// </summary>
/// <returns>
/// <see cref="T:TPixel[]"/>
/// <see cref="ReadOnlyMemory{TPixel}"/>
/// </returns>
protected abstract ReadOnlyMemory<TPixel> GetPalette();
@ -173,12 +199,15 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
protected byte GetTransparentIndex()
{
// 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;
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))
{
index = i;
@ -211,10 +240,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
float leastDistance = float.MaxValue;
Vector4 vector = pixel.ToScaledVector4();
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);
// Greater... Move on.
@ -239,4 +270,4 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
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.
using System;
@ -128,7 +128,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
if (this.Dither)
{
// 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;
@ -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.
using System;
@ -76,7 +76,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
if (this.Dither)
{
// 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;
@ -98,4 +98,4 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
[MethodImpl(MethodImplOptions.AggressiveInlining)]
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>
private Box[] colorCube;
private bool isDisposed;
/// <summary>
/// Initializes a new instance of the <see cref="WuFrameQuantizer{TPixel}"/> class.
/// </summary>
@ -158,15 +160,23 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
}
/// <inheritdoc/>
public override void Dispose()
protected override void Dispose(bool disposing)
{
this.vwt?.Dispose();
this.vmr?.Dispose();
this.vmg?.Dispose();
this.vmb?.Dispose();
this.vma?.Dispose();
this.m2?.Dispose();
this.tag?.Dispose();
if (this.isDisposed)
{
return;
}
if (disposing)
{
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.vmr = null;
@ -175,6 +185,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
this.vma = null;
this.m2 = null;
this.tag = null;
this.isDisposed = true;
base.Dispose(true);
}
internal ReadOnlyMemory<TPixel> AotGetPalette() => this.GetPalette();
@ -260,7 +273,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
if (this.Dither)
{
// 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;

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