Browse Source

Fix output, cleanup and minor optimizations.

af/merge-core
James Jackson-South 6 years ago
parent
commit
7cb840a86a
  1. 20
      src/ImageSharp/Processing/Processors/Dithering/ErrorDiffuser.cs
  2. 6
      src/ImageSharp/Processing/Processors/Dithering/ErrorDiffusionPaletteProcessor{TPixel}.cs
  3. 6
      src/ImageSharp/Processing/Processors/Dithering/OrderedDitherPaletteProcessor{TPixel}.cs
  4. 69
      src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs
  5. 55
      src/ImageSharp/Processing/Processors/Quantization/FrameQuantizer{TPixel}.cs
  6. 29
      src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs
  7. 8
      tests/ImageSharp.Benchmarks/Samplers/Diffuse.cs
  8. 2
      tests/Images/External

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

@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
/// </summary>
public abstract class ErrorDiffuser : IErrorDiffuser
{
private readonly int startingOffset;
private readonly int offset;
private readonly DenseMatrix<float> matrix;
/// <summary>
@ -24,13 +24,15 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
/// <param name="matrix">The dithering matrix.</param>
internal ErrorDiffuser(in DenseMatrix<float> matrix)
{
this.startingOffset = 0;
// Calculate the offset position of the pixel relative to
// the diffusion matrix.
this.offset = 0;
for (int col = 0; col < matrix.Columns; col++)
{
if (matrix[0, col] != 0)
{
this.startingOffset = col - 1;
this.offset = col - 1;
break;
}
}
@ -45,7 +47,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
{
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;
@ -60,18 +62,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
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.startingOffset;
int offset = this.offset;
DenseMatrix<float> matrix = this.matrix;
// Loop through and distribute the error amongst neighboring pixels.
for (int row = 0, targetY = y + row; row < matrix.Rows && targetY < maxY; row++)
for (int row = 0, targetY = y; row < matrix.Rows && targetY < maxY; row++, targetY++)
{
Span<TPixel> rowSpan = image.GetPixelRowSpan(targetY);
for (int col = 0; col < matrix.Columns; col++)
{
int targetX = x + (col - offset);
if (targetX > minX && targetX < maxX)
if (targetX >= minX && targetX < maxX)
{
float coefficient = matrix[row, col];
if (coefficient == 0)
@ -80,9 +82,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
}
ref TPixel pixel = ref rowSpan[targetX];
var offsetColor = pixel.ToVector4();
var result = pixel.ToVector4();
Vector4 result = (error * coefficient) + offsetColor;
result += error * coefficient;
pixel.FromVector4(result);
}
}

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

@ -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,7 +70,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;

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

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

29
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();

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

@ -43,7 +43,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Samplers
//
// #### After ####
//
// | Method | Job | Runtime | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated |
// |---------- |----- |-------- |---------:|---------:|---------:|------:|------:|------:|----------:|
// | DoDiffuse | Clr | Clr | 12.94 ms | 22.48 ms | 1.232 ms | - | - | - | 4.25 KB |
// | DoDiffuse | Core | Core | 10.95 ms | 19.31 ms | 1.058 ms | - | - | - | 4.13 KB |
// | 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 54e075785697c9d6aa371282d492f16d9d916888
Subproject commit 563ec6f7774734ba39924174c8961705a1ea6fa2
Loading…
Cancel
Save