diff --git a/src/ImageSharp/ImageSharp.csproj b/src/ImageSharp/ImageSharp.csproj
index ca6ca16898..832d551fd7 100644
--- a/src/ImageSharp/ImageSharp.csproj
+++ b/src/ImageSharp/ImageSharp.csproj
@@ -31,8 +31,8 @@
-
-
+
+
diff --git a/src/ImageSharp/Processing/KnownDitherings.cs b/src/ImageSharp/Processing/KnownDitherings.cs
index 1c086b7408..9537acda18 100644
--- a/src/ImageSharp/Processing/KnownDitherings.cs
+++ b/src/ImageSharp/Processing/KnownDitherings.cs
@@ -30,6 +30,11 @@ namespace SixLabors.ImageSharp.Processing
///
public static IDither Bayer8x8 { get; } = OrderedDither.Bayer8x8;
+ ///
+ /// Gets the order ditherer using the 16x16 Bayer dithering matrix
+ ///
+ public static IDither Bayer16x16 { get; } = OrderedDither.Bayer16x16;
+
///
/// Gets the error Dither that implements the Atkinson algorithm.
///
diff --git a/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs b/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs
index 30ac5f135b..b853fcb669 100644
--- a/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs
+++ b/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs
@@ -25,7 +25,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
///
/// The diffusion matrix.
/// The starting offset within the matrix.
- [MethodImpl(InliningOptions.ShortMethod)]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public ErrorDither(in DenseMatrix matrix, int offset)
{
this.matrix = matrix;
@@ -87,7 +87,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
=> !(left == right);
///
- [MethodImpl(InliningOptions.ShortMethod)]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ApplyQuantizationDither(
ref TFrameQuantizer quantizer,
ImageFrame source,
@@ -96,26 +96,25 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
where TFrameQuantizer : struct, IQuantizer
where TPixel : unmanaged, IPixel
{
- int offsetY = bounds.Top;
- int offsetX = bounds.Left;
float scale = quantizer.Options.DitherScale;
for (int y = bounds.Top; y < bounds.Bottom; y++)
{
- ref TPixel sourceRowRef = ref MemoryMarshal.GetReference(source.GetPixelRowSpan(y));
- ref byte destinationRowRef = ref MemoryMarshal.GetReference(destination.GetWritablePixelRowSpanUnsafe(y - offsetY));
+ ReadOnlySpan sourceRow = source.GetPixelRowSpan(y).Slice(bounds.X, bounds.Width);
+ Span destRow =
+ destination.GetWritablePixelRowSpanUnsafe(y - bounds.Y).Slice(0, sourceRow.Length);
- for (int x = bounds.Left; x < bounds.Right; x++)
+ for (int x = 0; x < sourceRow.Length; x++)
{
- TPixel sourcePixel = Unsafe.Add(ref sourceRowRef, x);
- Unsafe.Add(ref destinationRowRef, x - offsetX) = quantizer.GetQuantizedColor(sourcePixel, out TPixel transformed);
+ TPixel sourcePixel = sourceRow[x];
+ destRow[x] = quantizer.GetQuantizedColor(sourcePixel, out TPixel transformed);
this.Dither(source, bounds, sourcePixel, transformed, x, y, scale);
}
}
}
///
- [MethodImpl(InliningOptions.ShortMethod)]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ApplyPaletteDither(
in TPaletteDitherImageProcessor processor,
ImageFrame source,
@@ -124,13 +123,15 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
where TPixel : unmanaged, IPixel
{
float scale = processor.DitherScale;
+
for (int y = bounds.Top; y < bounds.Bottom; y++)
{
- ref TPixel sourceRowRef = ref MemoryMarshal.GetReference(source.GetPixelRowSpan(y));
- for (int x = bounds.Left; x < bounds.Right; x++)
+ Span row = source.GetPixelRowSpan(y).Slice(bounds.X, bounds.Width);
+
+ for (int x = 0; x < row.Length; x++)
{
- ref TPixel sourcePixel = ref Unsafe.Add(ref sourceRowRef, x);
- TPixel transformed = Unsafe.AsRef(processor).GetPaletteColor(sourcePixel);
+ ref TPixel sourcePixel = ref row[x];
+ TPixel transformed = processor.GetPaletteColor(sourcePixel);
this.Dither(source, bounds, sourcePixel, transformed, x, y, scale);
sourcePixel = transformed;
}
@@ -138,7 +139,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
}
// Internal for AOT
- [MethodImpl(InliningOptions.ShortMethod)]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
internal TPixel Dither(
ImageFrame image,
Rectangle bounds,
diff --git a/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.KnownTypes.cs b/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.KnownTypes.cs
index 71ad6db973..1934604522 100644
--- a/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.KnownTypes.cs
+++ b/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.KnownTypes.cs
@@ -23,6 +23,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
///
public static OrderedDither Bayer8x8 = new OrderedDither(8);
+ ///
+ /// Applies order dithering using the 16x16 Bayer dithering matrix.
+ ///
+ public static OrderedDither Bayer16x16 = new OrderedDither(16);
+
///
/// Applies order dithering using the 3x3 ordered dithering matrix.
///
diff --git a/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs b/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs
index 9b99a5257d..a89d23cc00 100644
--- a/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs
+++ b/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs
@@ -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 struct.
///
/// The length of the matrix sides
- [MethodImpl(InliningOptions.ShortMethod)]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public OrderedDither(uint length)
{
DenseMatrix ditherMatrix = OrderedDitherFactory.CreateDitherMatrix(length);
@@ -102,7 +101,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
=> !(left == right);
///
- [MethodImpl(InliningOptions.ShortMethod)]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ApplyQuantizationDither(
ref TFrameQuantizer quantizer,
ImageFrame source,
@@ -125,7 +124,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
}
///
- [MethodImpl(InliningOptions.ShortMethod)]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ApplyPaletteDither(
in TPaletteDitherImageProcessor processor,
ImageFrame 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 source,
int x,
int y,
- int bitDepth,
+ int spread,
float scale)
where TPixel : unmanaged, IPixel
{
- 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);
///
- [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);
///
- [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 source;
private readonly IndexedImageFrame 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 sourceRow = this.source.GetPixelRowSpan(y).Slice(this.bounds.X, this.bounds.Width);
+ Span 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 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 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);
}
}
}
diff --git a/tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs
index adc3c381a0..5c1b5da7f1 100644
--- a/tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs
+++ b/tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs
@@ -37,6 +37,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization
{ KnownDitherings.Bayer2x2, nameof(KnownDitherings.Bayer2x2) },
{ KnownDitherings.Bayer4x4, nameof(KnownDitherings.Bayer4x4) },
{ KnownDitherings.Bayer8x8, nameof(KnownDitherings.Bayer8x8) },
+ { KnownDitherings.Bayer16x16, nameof(KnownDitherings.Bayer16x16) },
{ KnownDitherings.Ordered3x3, nameof(KnownDitherings.Ordered3x3) }
};
diff --git a/tests/ImageSharp.Tests/Processing/Processors/Quantization/QuantizerTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Quantization/QuantizerTests.cs
index a25eca5b02..3f4656d411 100644
--- a/tests/ImageSharp.Tests/Processing/Processors/Quantization/QuantizerTests.cs
+++ b/tests/ImageSharp.Tests/Processing/Processors/Quantization/QuantizerTests.cs
@@ -169,8 +169,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization
provider.RunRectangleConstrainedValidatingProcessorTest(
(x, rect) => x.Quantize(quantizer, rect),
- comparer: ValidatorComparer,
testOutputDetails: testOutputDetails,
+ comparer: ValidatorComparer,
appendPixelTypeToFileName: false);
}
diff --git a/tests/ImageSharp.Tests/TestUtilities/TestUtils.cs b/tests/ImageSharp.Tests/TestUtilities/TestUtils.cs
index 39ebf7f159..5f41021a06 100644
--- a/tests/ImageSharp.Tests/TestUtilities/TestUtils.cs
+++ b/tests/ImageSharp.Tests/TestUtilities/TestUtils.cs
@@ -240,7 +240,7 @@ namespace SixLabors.ImageSharp.Tests
using (Image image = provider.GetImage())
{
FormattableString testOutputDetails = $"";
- image.Mutate(ctx => { testOutputDetails = processAndGetTestOutputDetails(ctx); });
+ image.Mutate(ctx => testOutputDetails = processAndGetTestOutputDetails(ctx));
image.DebugSave(
provider,