Skip to content

Commit f076b30

Browse files
Merge branch 'master' into PorterDuffImprovement
2 parents c4bb9cb + d091027 commit f076b30

24 files changed

+287
-187
lines changed

src/ImageSharp/Advanced/AotCompilerTools.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ private static void AotCompileDithering<TPixel>()
136136
TPixel pixel = default;
137137
using (var image = new ImageFrame<TPixel>(Configuration.Default, 1, 1))
138138
{
139-
test.Dither(image, pixel, pixel, 0, 0, 0, 0, 0, 0);
139+
test.Dither(image, pixel, pixel, 0, 0, 0, 0, 0);
140140
}
141141
}
142142

src/ImageSharp/Processing/Extensions/DiffuseExtensions.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) Six Labors and contributors.
1+
// Copyright (c) Six Labors and contributors.
22
// Licensed under the Apache License, Version 2.0.
33

44
using System;
@@ -95,4 +95,4 @@ public static IImageProcessingContext Diffuse(
9595
Rectangle rectangle) =>
9696
source.ApplyProcessor(new ErrorDiffusionPaletteProcessor(diffuser, threshold, palette), rectangle);
9797
}
98-
}
98+
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization
1414
/// Performs binary threshold filtering against an image using error diffusion.
1515
/// </summary>
1616
/// <typeparam name="TPixel">The pixel format.</typeparam>
17-
internal class BinaryErrorDiffusionProcessor<TPixel> : ImageProcessor<TPixel>
17+
internal sealed class BinaryErrorDiffusionProcessor<TPixel> : ImageProcessor<TPixel>
1818
where TPixel : struct, IPixel<TPixel>
1919
{
2020
private readonly BinaryErrorDiffusionProcessor definition;
@@ -76,7 +76,7 @@ protected override void OnFrameApply(ImageFrame<TPixel> source)
7676
}
7777

7878
TPixel transformedPixel = luminance >= threshold ? upperColor : lowerColor;
79-
diffuser.Dither(source, sourcePixel, transformedPixel, x, y, startX, startY, endX, endY);
79+
diffuser.Dither(source, sourcePixel, transformedPixel, x, y, startX, endX, endY);
8080
}
8181
}
8282
}

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

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) Six Labors and contributors.
1+
// Copyright (c) Six Labors and contributors.
22
// Licensed under the Apache License, Version 2.0.
33

44
using SixLabors.ImageSharp.Primitives;
@@ -11,22 +11,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
1111
/// </summary>
1212
public sealed class AtkinsonDiffuser : ErrorDiffuser
1313
{
14+
private const float Divisor = 8F;
15+
1416
/// <summary>
1517
/// The diffusion matrix
1618
/// </summary>
1719
private static readonly DenseMatrix<float> AtkinsonMatrix =
1820
new float[,]
1921
{
20-
{ 0, 0, 1, 1 },
21-
{ 1, 1, 1, 0 },
22-
{ 0, 1, 0, 0 }
22+
{ 0, 0, 1 / Divisor, 1 / Divisor },
23+
{ 1 / Divisor, 1 / Divisor, 1 / Divisor, 0 },
24+
{ 0, 1 / Divisor, 0, 0 }
2325
};
2426

2527
/// <summary>
2628
/// Initializes a new instance of the <see cref="AtkinsonDiffuser"/> class.
2729
/// </summary>
2830
public AtkinsonDiffuser()
29-
: base(AtkinsonMatrix, 8)
31+
: base(AtkinsonMatrix)
3032
{
3133
}
3234
}
Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) Six Labors and contributors.
1+
// Copyright (c) Six Labors and contributors.
22
// Licensed under the Apache License, Version 2.0.
33

44
using SixLabors.ImageSharp.Primitives;
@@ -11,22 +11,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
1111
/// </summary>
1212
public sealed class BurksDiffuser : ErrorDiffuser
1313
{
14+
private const float Divisor = 32F;
15+
1416
/// <summary>
1517
/// The diffusion matrix
1618
/// </summary>
1719
private static readonly DenseMatrix<float> BurksMatrix =
1820
new float[,]
1921
{
20-
{ 0, 0, 0, 8, 4 },
21-
{ 2, 4, 8, 4, 2 }
22+
{ 0, 0, 0, 8 / Divisor, 4 / Divisor },
23+
{ 2 / Divisor, 4 / Divisor, 8 / Divisor, 4 / Divisor, 2 / Divisor }
2224
};
2325

2426
/// <summary>
2527
/// Initializes a new instance of the <see cref="BurksDiffuser"/> class.
2628
/// </summary>
2729
public BurksDiffuser()
28-
: base(BurksMatrix, 32)
30+
: base(BurksMatrix)
2931
{
3032
}
3133
}
32-
}
34+
}
Lines changed: 35 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) Six Labors and contributors.
1+
// Copyright (c) Six Labors and contributors.
22
// Licensed under the Apache License, Version 2.0.
33

44
using System;
@@ -15,112 +15,80 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
1515
/// </summary>
1616
public abstract class ErrorDiffuser : IErrorDiffuser
1717
{
18-
/// <summary>
19-
/// The vector to perform division.
20-
/// </summary>
21-
private readonly Vector4 divisorVector;
22-
23-
/// <summary>
24-
/// The matrix width.
25-
/// </summary>
26-
private readonly int matrixHeight;
27-
28-
/// <summary>
29-
/// The matrix height.
30-
/// </summary>
31-
private readonly int matrixWidth;
32-
33-
/// <summary>
34-
/// The offset at which to start the dithering operation.
35-
/// </summary>
36-
private readonly int startingOffset;
37-
38-
/// <summary>
39-
/// The diffusion matrix.
40-
/// </summary>
18+
private readonly int offset;
4119
private readonly DenseMatrix<float> matrix;
4220

4321
/// <summary>
4422
/// Initializes a new instance of the <see cref="ErrorDiffuser"/> class.
4523
/// </summary>
4624
/// <param name="matrix">The dithering matrix.</param>
47-
/// <param name="divisor">The divisor.</param>
48-
internal ErrorDiffuser(in DenseMatrix<float> matrix, byte divisor)
25+
internal ErrorDiffuser(in DenseMatrix<float> matrix)
4926
{
50-
Guard.MustBeGreaterThan(divisor, 0, nameof(divisor));
51-
52-
this.matrix = matrix;
53-
this.matrixWidth = this.matrix.Columns;
54-
this.matrixHeight = this.matrix.Rows;
55-
this.divisorVector = new Vector4(divisor);
27+
// Calculate the offset position of the pixel relative to
28+
// the diffusion matrix.
29+
this.offset = 0;
5630

57-
this.startingOffset = 0;
58-
for (int i = 0; i < this.matrixWidth; i++)
31+
for (int col = 0; col < matrix.Columns; col++)
5932
{
60-
// Good to disable here as we are not comparing mathematical output.
61-
// ReSharper disable once CompareOfFloatsByEqualityOperator
62-
if (matrix[0, i] != 0)
33+
if (matrix[0, col] != 0)
6334
{
64-
this.startingOffset = (byte)(i - 1);
35+
this.offset = col - 1;
6536
break;
6637
}
6738
}
39+
40+
this.matrix = matrix;
6841
}
6942

7043
/// <inheritdoc />
71-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
72-
public void Dither<TPixel>(ImageFrame<TPixel> image, TPixel source, TPixel transformed, int x, int y, int minX, int minY, int maxX, int maxY)
44+
[MethodImpl(InliningOptions.ShortMethod)]
45+
public void Dither<TPixel>(ImageFrame<TPixel> image, TPixel source, TPixel transformed, int x, int y, int minX, int maxX, int maxY)
7346
where TPixel : struct, IPixel<TPixel>
7447
{
7548
image[x, y] = transformed;
7649

77-
// Equal? Break out as there's nothing to pass.
50+
// Equal? Break out as there's no error to pass.
7851
if (source.Equals(transformed))
7952
{
8053
return;
8154
}
8255

8356
// Calculate the error
8457
Vector4 error = source.ToVector4() - transformed.ToVector4();
85-
this.DoDither(image, x, y, minX, minY, maxX, maxY, error);
58+
this.DoDither(image, x, y, minX, maxX, maxY, error);
8659
}
8760

88-
[MethodImpl(MethodImplOptions.NoInlining)]
89-
private void DoDither<TPixel>(ImageFrame<TPixel> image, int x, int y, int minX, int minY, int maxX, int maxY, Vector4 error)
61+
[MethodImpl(InliningOptions.ShortMethod)]
62+
private void DoDither<TPixel>(ImageFrame<TPixel> image, int x, int y, int minX, int maxX, int maxY, Vector4 error)
9063
where TPixel : struct, IPixel<TPixel>
9164
{
65+
int offset = this.offset;
66+
DenseMatrix<float> matrix = this.matrix;
67+
9268
// Loop through and distribute the error amongst neighboring pixels.
93-
for (int row = 0; row < this.matrixHeight; row++)
69+
for (int row = 0, targetY = y; row < matrix.Rows && targetY < maxY; row++, targetY++)
9470
{
95-
int matrixY = y + row;
96-
if (matrixY > minY && matrixY < maxY)
97-
{
98-
Span<TPixel> rowSpan = image.GetPixelRowSpan(matrixY);
71+
Span<TPixel> rowSpan = image.GetPixelRowSpan(targetY);
9972

100-
for (int col = 0; col < this.matrixWidth; col++)
73+
for (int col = 0; col < matrix.Columns; col++)
74+
{
75+
int targetX = x + (col - offset);
76+
if (targetX >= minX && targetX < maxX)
10177
{
102-
int matrixX = x + (col - this.startingOffset);
103-
104-
if (matrixX > minX && matrixX < maxX)
78+
float coefficient = matrix[row, col];
79+
if (coefficient == 0)
10580
{
106-
float coefficient = this.matrix[row, col];
107-
108-
// Good to disable here as we are not comparing mathematical output.
109-
// ReSharper disable once CompareOfFloatsByEqualityOperator
110-
if (coefficient == 0)
111-
{
112-
continue;
113-
}
81+
continue;
82+
}
11483

115-
ref TPixel pixel = ref rowSpan[matrixX];
116-
var offsetColor = pixel.ToVector4();
84+
ref TPixel pixel = ref rowSpan[targetX];
85+
var result = pixel.ToVector4();
11786

118-
Vector4 result = ((error * coefficient) / this.divisorVector) + offsetColor;
119-
pixel.FromVector4(result);
120-
}
87+
result += error * coefficient;
88+
pixel.FromVector4(result);
12189
}
12290
}
12391
}
12492
}
12593
}
126-
}
94+
}

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

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
1313
/// An <see cref="IImageProcessor{TPixel}"/> that dithers an image using error diffusion.
1414
/// </summary>
1515
/// <typeparam name="TPixel">The pixel format.</typeparam>
16-
internal class ErrorDiffusionPaletteProcessor<TPixel> : PaletteDitherProcessor<TPixel>
16+
internal sealed class ErrorDiffusionPaletteProcessor<TPixel> : PaletteDitherProcessor<TPixel>
1717
where TPixel : struct, IPixel<TPixel>
1818
{
1919
/// <summary>
@@ -33,8 +33,6 @@ public ErrorDiffusionPaletteProcessor(ErrorDiffusionPaletteProcessor definition,
3333
protected override void OnFrameApply(ImageFrame<TPixel> source)
3434
{
3535
byte threshold = (byte)MathF.Round(this.Definition.Threshold * 255F);
36-
bool isAlphaOnly = typeof(TPixel) == typeof(Alpha8);
37-
3836
var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds());
3937
int startY = interest.Y;
4038
int endY = interest.Bottom;
@@ -49,7 +47,7 @@ protected override void OnFrameApply(ImageFrame<TPixel> source)
4947
sourcePixel.ToRgba32(ref rgba);
5048

5149
// Convert to grayscale using ITU-R Recommendation BT.709 if required
52-
byte luminance = isAlphaOnly ? rgba.A : ImageMaths.Get8BitBT709Luminance(rgba.R, rgba.G, rgba.B);
50+
byte luminance = ImageMaths.Get8BitBT709Luminance(rgba.R, rgba.G, rgba.B);
5351

5452
for (int y = startY; y < endY; y++)
5553
{
@@ -72,14 +70,14 @@ protected override void OnFrameApply(ImageFrame<TPixel> source)
7270
}
7371

7472
sourcePixel.ToRgba32(ref rgba);
75-
luminance = isAlphaOnly ? rgba.A : ImageMaths.Get8BitBT709Luminance(rgba.R, rgba.G, rgba.B);
73+
luminance = ImageMaths.Get8BitBT709Luminance(rgba.R, rgba.G, rgba.B);
7674

7775
// Setup the previous pointer
7876
previousPixel = sourcePixel;
7977
}
8078

8179
TPixel transformedPixel = luminance >= threshold ? pair.Second : pair.First;
82-
this.Definition.Diffuser.Dither(source, sourcePixel, transformedPixel, x, y, startX, startY, endX, endY);
80+
this.Definition.Diffuser.Dither(source, sourcePixel, transformedPixel, x, y, startX, endX, endY);
8381
}
8482
}
8583
}
Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) Six Labors and contributors.
1+
// Copyright (c) Six Labors and contributors.
22
// Licensed under the Apache License, Version 2.0.
33

44
using SixLabors.ImageSharp.Primitives;
@@ -11,22 +11,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
1111
/// </summary>
1212
public sealed class FloydSteinbergDiffuser : ErrorDiffuser
1313
{
14+
private const float Divisor = 16F;
15+
1416
/// <summary>
1517
/// The diffusion matrix
1618
/// </summary>
1719
private static readonly DenseMatrix<float> FloydSteinbergMatrix =
1820
new float[,]
1921
{
20-
{ 0, 0, 7 },
21-
{ 3, 5, 1 }
22+
{ 0, 0, 7 / Divisor },
23+
{ 3 / Divisor, 5 / Divisor, 1 / Divisor }
2224
};
2325

2426
/// <summary>
2527
/// Initializes a new instance of the <see cref="FloydSteinbergDiffuser"/> class.
2628
/// </summary>
2729
public FloydSteinbergDiffuser()
28-
: base(FloydSteinbergMatrix, 16)
30+
: base(FloydSteinbergMatrix)
2931
{
3032
}
3133
}
32-
}
34+
}

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

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) Six Labors and contributors.
1+
// Copyright (c) Six Labors and contributors.
22
// Licensed under the Apache License, Version 2.0.
33

44
using SixLabors.ImageSharp.PixelFormats;
@@ -19,11 +19,10 @@ public interface IErrorDiffuser
1919
/// <param name="x">The column index.</param>
2020
/// <param name="y">The row index.</param>
2121
/// <param name="minX">The minimum column value.</param>
22-
/// <param name="minY">The minimum row value.</param>
2322
/// <param name="maxX">The maximum column value.</param>
2423
/// <param name="maxY">The maximum row value.</param>
2524
/// <typeparam name="TPixel">The pixel format.</typeparam>
26-
void Dither<TPixel>(ImageFrame<TPixel> image, TPixel source, TPixel transformed, int x, int y, int minX, int minY, int maxX, int maxY)
25+
void Dither<TPixel>(ImageFrame<TPixel> image, TPixel source, TPixel transformed, int x, int y, int minX, int maxX, int maxY)
2726
where TPixel : struct, IPixel<TPixel>;
2827
}
29-
}
28+
}

0 commit comments

Comments
 (0)