|
|
|
@ -3,7 +3,6 @@ |
|
|
|
|
|
|
|
using System; |
|
|
|
using System.Runtime.CompilerServices; |
|
|
|
using System.Runtime.InteropServices; |
|
|
|
using SixLabors.ImageSharp.Advanced; |
|
|
|
using SixLabors.ImageSharp.PixelFormats; |
|
|
|
using SixLabors.ImageSharp.Processing.Processors.Quantization; |
|
|
|
@ -23,7 +22,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering |
|
|
|
/// Initializes a new instance of the <see cref="OrderedDither"/> struct.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="length">The length of the matrix sides</param>
|
|
|
|
[MethodImpl(InliningOptions.ShortMethod)] |
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
|
|
public OrderedDither(uint length) |
|
|
|
{ |
|
|
|
DenseMatrix<uint> ditherMatrix = OrderedDitherFactory.CreateDitherMatrix(length); |
|
|
|
@ -102,7 +101,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering |
|
|
|
=> !(left == right); |
|
|
|
|
|
|
|
/// <inheritdoc/>
|
|
|
|
[MethodImpl(InliningOptions.ShortMethod)] |
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
|
|
public void ApplyQuantizationDither<TFrameQuantizer, TPixel>( |
|
|
|
ref TFrameQuantizer quantizer, |
|
|
|
ImageFrame<TPixel> source, |
|
|
|
@ -125,7 +124,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering |
|
|
|
} |
|
|
|
|
|
|
|
/// <inheritdoc/>
|
|
|
|
[MethodImpl(InliningOptions.ShortMethod)] |
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
|
|
public void ApplyPaletteDither<TPaletteDitherImageProcessor, TPixel>( |
|
|
|
in TPaletteDitherImageProcessor processor, |
|
|
|
ImageFrame<TPixel> source, |
|
|
|
@ -145,24 +144,25 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering |
|
|
|
in ditherOperation); |
|
|
|
} |
|
|
|
|
|
|
|
[MethodImpl(InliningOptions.ShortMethod)] |
|
|
|
// Spread assumes an even colorspace distribution and precision.
|
|
|
|
// Cubed root used because we always compare to Rgb.
|
|
|
|
// https://bisqwit.iki.fi/story/howto/dither/jy/
|
|
|
|
// https://en.wikipedia.org/wiki/Ordered_dithering#Algorithm
|
|
|
|
internal static int CalculatePaletteSpread(int colors) => (int)(255 / (Math.Pow(colors, 1.0 / 3) - 1)); |
|
|
|
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
|
|
internal TPixel Dither<TPixel>( |
|
|
|
TPixel source, |
|
|
|
int x, |
|
|
|
int y, |
|
|
|
int bitDepth, |
|
|
|
int spread, |
|
|
|
float scale) |
|
|
|
where TPixel : unmanaged, IPixel<TPixel> |
|
|
|
{ |
|
|
|
Rgba32 rgba = default; |
|
|
|
Unsafe.SkipInit(out Rgba32 rgba); |
|
|
|
source.ToRgba32(ref rgba); |
|
|
|
Rgba32 attempt; |
|
|
|
Unsafe.SkipInit(out Rgba32 attempt); |
|
|
|
|
|
|
|
// Spread assumes an even colorspace distribution and precision.
|
|
|
|
// Calculated as 0-255/component count. 256 / bitDepth
|
|
|
|
// https://bisqwit.iki.fi/story/howto/dither/jy/
|
|
|
|
// https://en.wikipedia.org/wiki/Ordered_dithering#Algorithm
|
|
|
|
int spread = 256 / bitDepth; |
|
|
|
float factor = spread * this.thresholdMatrix[y % this.modulusY, x % this.modulusX] * scale; |
|
|
|
|
|
|
|
attempt.R = (byte)Numerics.Clamp(rgba.R + factor, byte.MinValue, byte.MaxValue); |
|
|
|
@ -181,7 +181,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering |
|
|
|
=> obj is OrderedDither dither && this.Equals(dither); |
|
|
|
|
|
|
|
/// <inheritdoc/>
|
|
|
|
[MethodImpl(InliningOptions.ShortMethod)] |
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
|
|
public bool Equals(OrderedDither other) |
|
|
|
=> this.thresholdMatrix.Equals(other.thresholdMatrix) && this.modulusX == other.modulusX && this.modulusY == other.modulusY; |
|
|
|
|
|
|
|
@ -190,7 +190,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering |
|
|
|
=> this.Equals((object)other); |
|
|
|
|
|
|
|
/// <inheritdoc/>
|
|
|
|
[MethodImpl(InliningOptions.ShortMethod)] |
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
|
|
public override int GetHashCode() |
|
|
|
=> HashCode.Combine(this.thresholdMatrix, this.modulusX, this.modulusY); |
|
|
|
|
|
|
|
@ -203,9 +203,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering |
|
|
|
private readonly ImageFrame<TPixel> source; |
|
|
|
private readonly IndexedImageFrame<TPixel> destination; |
|
|
|
private readonly Rectangle bounds; |
|
|
|
private readonly int bitDepth; |
|
|
|
private readonly int spread; |
|
|
|
|
|
|
|
[MethodImpl(InliningOptions.ShortMethod)] |
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
|
|
public QuantizeDitherRowOperation( |
|
|
|
ref TFrameQuantizer quantizer, |
|
|
|
in OrderedDither dither, |
|
|
|
@ -218,23 +218,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering |
|
|
|
this.source = source; |
|
|
|
this.destination = destination; |
|
|
|
this.bounds = bounds; |
|
|
|
this.bitDepth = ColorNumerics.GetBitsNeededForColorDepth(destination.Palette.Length); |
|
|
|
this.spread = CalculatePaletteSpread(destination.Palette.Length); |
|
|
|
} |
|
|
|
|
|
|
|
[MethodImpl(InliningOptions.ShortMethod)] |
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
|
|
public void Invoke(int y) |
|
|
|
{ |
|
|
|
int offsetY = this.bounds.Top; |
|
|
|
int offsetX = this.bounds.Left; |
|
|
|
ref TFrameQuantizer quantizer = ref Unsafe.AsRef(this.quantizer); |
|
|
|
int spread = this.spread; |
|
|
|
float scale = this.quantizer.Options.DitherScale; |
|
|
|
|
|
|
|
ref TPixel sourceRowRef = ref MemoryMarshal.GetReference(this.source.GetPixelRowSpan(y)); |
|
|
|
ref byte destinationRowRef = ref MemoryMarshal.GetReference(this.destination.GetWritablePixelRowSpanUnsafe(y - offsetY)); |
|
|
|
ReadOnlySpan<TPixel> sourceRow = this.source.GetPixelRowSpan(y).Slice(this.bounds.X, this.bounds.Width); |
|
|
|
Span<byte> destRow = |
|
|
|
this.destination.GetWritablePixelRowSpanUnsafe(y - this.bounds.Y).Slice(0, sourceRow.Length); |
|
|
|
|
|
|
|
for (int x = this.bounds.Left; x < this.bounds.Right; x++) |
|
|
|
for (int x = 0; x < sourceRow.Length; x++) |
|
|
|
{ |
|
|
|
TPixel dithered = this.dither.Dither(Unsafe.Add(ref sourceRowRef, x), x, y, this.bitDepth, scale); |
|
|
|
Unsafe.Add(ref destinationRowRef, x - offsetX) = Unsafe.AsRef(this.quantizer).GetQuantizedColor(dithered, out TPixel _); |
|
|
|
TPixel dithered = this.dither.Dither(sourceRow[x], x, y, spread, scale); |
|
|
|
destRow[x] = quantizer.GetQuantizedColor(dithered, out TPixel _); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
@ -248,9 +249,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering |
|
|
|
private readonly ImageFrame<TPixel> source; |
|
|
|
private readonly Rectangle bounds; |
|
|
|
private readonly float scale; |
|
|
|
private readonly int bitDepth; |
|
|
|
private readonly int spread; |
|
|
|
|
|
|
|
[MethodImpl(InliningOptions.ShortMethod)] |
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
|
|
public PaletteDitherRowOperation( |
|
|
|
in TPaletteDitherImageProcessor processor, |
|
|
|
in OrderedDither dither, |
|
|
|
@ -262,19 +263,23 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering |
|
|
|
this.source = source; |
|
|
|
this.bounds = bounds; |
|
|
|
this.scale = processor.DitherScale; |
|
|
|
this.bitDepth = ColorNumerics.GetBitsNeededForColorDepth(processor.Palette.Length); |
|
|
|
this.spread = CalculatePaletteSpread(processor.Palette.Length); |
|
|
|
} |
|
|
|
|
|
|
|
[MethodImpl(InliningOptions.ShortMethod)] |
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
|
|
public void Invoke(int y) |
|
|
|
{ |
|
|
|
ref TPixel sourceRowRef = ref MemoryMarshal.GetReference(this.source.GetPixelRowSpan(y)); |
|
|
|
ref TPaletteDitherImageProcessor processor = ref Unsafe.AsRef(this.processor); |
|
|
|
int spread = this.spread; |
|
|
|
float scale = this.scale; |
|
|
|
|
|
|
|
Span<TPixel> row = this.source.GetPixelRowSpan(y).Slice(this.bounds.X, this.bounds.Width); |
|
|
|
|
|
|
|
for (int x = this.bounds.Left; x < this.bounds.Right; x++) |
|
|
|
for (int x = 0; x < row.Length; x++) |
|
|
|
{ |
|
|
|
ref TPixel sourcePixel = ref Unsafe.Add(ref sourceRowRef, x); |
|
|
|
TPixel dithered = this.dither.Dither(sourcePixel, x, y, this.bitDepth, this.scale); |
|
|
|
sourcePixel = Unsafe.AsRef(this.processor).GetPaletteColor(dithered); |
|
|
|
ref TPixel sourcePixel = ref row[x]; |
|
|
|
TPixel dithered = this.dither.Dither(sourcePixel, x, y, spread, scale); |
|
|
|
sourcePixel = processor.GetPaletteColor(dithered); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|