diff --git a/src/ImageSharp/Color/Color.cs b/src/ImageSharp/Color/Color.cs
index 3e20cafbd..bc97393cc 100644
--- a/src/ImageSharp/Color/Color.cs
+++ b/src/ImageSharp/Color/Color.cs
@@ -5,6 +5,7 @@ using System;
using System.Buffers.Binary;
using System.Globalization;
using System.Numerics;
+using System.Runtime.InteropServices;
using SixLabors.ImageSharp.PixelFormats;
@@ -117,6 +118,44 @@ namespace SixLabors.ImageSharp
///
public override string ToString() => this.ToHex();
+ public TPixel ToPixel()
+ where TPixel : struct, IPixel
+ {
+ TPixel pixel = default;
+ pixel.FromRgba64(this.data);
+ return pixel;
+ }
+
+ public bool Equals(Color other)
+ {
+ return this.data.PackedValue == other.data.PackedValue;
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (ReferenceEquals(null, obj))
+ {
+ return false;
+ }
+ return obj is Color other && this.Equals(other);
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ return this.data.PackedValue.GetHashCode();
+ }
+
+ internal static void ToPixel(
+ Configuration configuration,
+ ReadOnlySpan source,
+ Span destination)
+ where TPixel : struct, IPixel
+ {
+ ReadOnlySpan rgba64Span = MemoryMarshal.Cast(source);
+ PixelOperations.Instance.FromRgba64(Configuration.Default, rgba64Span, destination);
+ }
+
///
/// Converts the specified hex value to an rrggbbaa hex value.
///
@@ -153,33 +192,5 @@ namespace SixLabors.ImageSharp
return new string(new[] { r, r, g, g, b, b, a, a });
}
-
- public TPixel ToPixel()
- where TPixel : struct, IPixel
- {
- TPixel pixel = default;
- pixel.FromRgba64(this.data);
- return pixel;
- }
-
- public bool Equals(Color other)
- {
- return this.data.PackedValue == other.data.PackedValue;
- }
-
- public override bool Equals(object obj)
- {
- if (ReferenceEquals(null, obj))
- {
- return false;
- }
- return obj is Color other && this.Equals(other);
- }
-
- ///
- public override int GetHashCode()
- {
- return this.data.PackedValue.GetHashCode();
- }
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/DiffuseExtensions.cs b/src/ImageSharp/Processing/DiffuseExtensions.cs
index 4668363e9..cd714c3da 100644
--- a/src/ImageSharp/Processing/DiffuseExtensions.cs
+++ b/src/ImageSharp/Processing/DiffuseExtensions.cs
@@ -1,14 +1,15 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
-using SixLabors.ImageSharp.PixelFormats;
+using System;
+
using SixLabors.ImageSharp.Processing.Processors.Dithering;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Dithering
{
///
- /// Defines extension methods to apply diffusion to an
+ /// Defines extension methods to apply diffusion to an
/// using Mutate/Clone.
///
public static class DiffuseExtensions
@@ -16,68 +17,68 @@ namespace SixLabors.ImageSharp.Processing.Dithering
///
/// Dithers the image reducing it to a web-safe palette using error diffusion.
///
- /// The pixel format.
/// The image this method extends.
- /// The to allow chaining of operations.
- public static IImageProcessingContext Diffuse(this IImageProcessingContext source)
- where TPixel : struct, IPixel
- => Diffuse(source, KnownDiffusers.FloydSteinberg, .5F);
+ /// The to allow chaining of operations.
+ public static IImageProcessingContext Diffuse(this IImageProcessingContext source) =>
+ Diffuse(source, KnownDiffusers.FloydSteinberg, .5F);
///
/// Dithers the image reducing it to a web-safe palette using error diffusion.
///
- /// The pixel format.
/// The image this method extends.
/// The threshold to apply binarization of the image. Must be between 0 and 1.
- /// The to allow chaining of operations.
- public static IImageProcessingContext Diffuse(this IImageProcessingContext source, float threshold)
- where TPixel : struct, IPixel
- => Diffuse(source, KnownDiffusers.FloydSteinberg, threshold);
+ /// The to allow chaining of operations.
+ public static IImageProcessingContext Diffuse(this IImageProcessingContext source, float threshold) =>
+ Diffuse(source, KnownDiffusers.FloydSteinberg, threshold);
///
/// Dithers the image reducing it to a web-safe palette using error diffusion.
///
- /// The pixel format.
/// The image this method extends.
/// The diffusion algorithm to apply.
/// The threshold to apply binarization of the image. Must be between 0 and 1.
- /// The to allow chaining of operations.
- public static IImageProcessingContext Diffuse(this IImageProcessingContext source, IErrorDiffuser diffuser, float threshold)
- where TPixel : struct, IPixel
- => source.ApplyProcessor(new ErrorDiffusionPaletteProcessor(diffuser, threshold));
+ /// The to allow chaining of operations.
+ public static IImageProcessingContext Diffuse(
+ this IImageProcessingContext source,
+ IErrorDiffuser diffuser,
+ float threshold) =>
+ source.ApplyProcessor(new ErrorDiffusionPaletteProcessor(diffuser, threshold));
///
/// Dithers the image reducing it to a web-safe palette using error diffusion.
///
- /// The pixel format.
/// The image this method extends.
/// The diffusion algorithm to apply.
/// The threshold to apply binarization of the image. Must be between 0 and 1.
///
/// The structure that specifies the portion of the image object to alter.
///
- /// The to allow chaining of operations.
- public static IImageProcessingContext Diffuse(this IImageProcessingContext source, IErrorDiffuser diffuser, float threshold, Rectangle rectangle)
- where TPixel : struct, IPixel
- => source.ApplyProcessor(new ErrorDiffusionPaletteProcessor(diffuser, threshold), rectangle);
+ /// The to allow chaining of operations.
+ public static IImageProcessingContext Diffuse(
+ this IImageProcessingContext source,
+ IErrorDiffuser diffuser,
+ float threshold,
+ Rectangle rectangle) =>
+ source.ApplyProcessor(new ErrorDiffusionPaletteProcessor(diffuser, threshold), rectangle);
///
/// Dithers the image reducing it to the given palette using error diffusion.
///
- /// The pixel format.
/// The image this method extends.
/// The diffusion algorithm to apply.
/// The threshold to apply binarization of the image. Must be between 0 and 1.
/// The palette to select substitute colors from.
- /// The to allow chaining of operations.
- public static IImageProcessingContext Diffuse(this IImageProcessingContext source, IErrorDiffuser diffuser, float threshold, TPixel[] palette)
- where TPixel : struct, IPixel
- => source.ApplyProcessor(new ErrorDiffusionPaletteProcessor(diffuser, threshold, palette));
+ /// The to allow chaining of operations.
+ public static IImageProcessingContext Diffuse(
+ this IImageProcessingContext source,
+ IErrorDiffuser diffuser,
+ float threshold,
+ ReadOnlySpan palette) =>
+ source.ApplyProcessor(new ErrorDiffusionPaletteProcessor(diffuser, threshold, palette));
///
/// Dithers the image reducing it to the given palette using error diffusion.
///
- /// The pixel format.
/// The image this method extends.
/// The diffusion algorithm to apply.
/// The threshold to apply binarization of the image. Must be between 0 and 1.
@@ -85,9 +86,13 @@ namespace SixLabors.ImageSharp.Processing.Dithering
///
/// The structure that specifies the portion of the image object to alter.
///
- /// The to allow chaining of operations.
- public static IImageProcessingContext Diffuse(this IImageProcessingContext source, IErrorDiffuser diffuser, float threshold, TPixel[] palette, Rectangle rectangle)
- where TPixel : struct, IPixel
- => source.ApplyProcessor(new ErrorDiffusionPaletteProcessor(diffuser, threshold, palette), rectangle);
+ /// The to allow chaining of operations.
+ public static IImageProcessingContext Diffuse(
+ this IImageProcessingContext source,
+ IErrorDiffuser diffuser,
+ float threshold,
+ ReadOnlySpan palette,
+ Rectangle rectangle) =>
+ source.ApplyProcessor(new ErrorDiffusionPaletteProcessor(diffuser, threshold, palette), rectangle);
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/DitherExtensions.cs b/src/ImageSharp/Processing/DitherExtensions.cs
index aeb975d1c..55794aec2 100644
--- a/src/ImageSharp/Processing/DitherExtensions.cs
+++ b/src/ImageSharp/Processing/DitherExtensions.cs
@@ -1,14 +1,15 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
-using SixLabors.ImageSharp.PixelFormats;
+using System;
+
using SixLabors.ImageSharp.Processing.Processors.Dithering;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing
{
///
- /// Defines dithering extensions to apply on an
+ /// Defines dithering extensions to apply on an
/// using Mutate/Clone.
///
public static class DitherExtensions
@@ -16,63 +17,63 @@ namespace SixLabors.ImageSharp.Processing
///
/// Dithers the image reducing it to a web-safe palette using Bayer4x4 ordered dithering.
///
- /// The pixel format.
/// The image this method extends.
- /// The to allow chaining of operations.
- public static IImageProcessingContext Dither(this IImageProcessingContext source)
- where TPixel : struct, IPixel
- => Dither(source, KnownDitherers.BayerDither4x4);
+ /// The to allow chaining of operations.
+ public static IImageProcessingContext Dither(this IImageProcessingContext source) =>
+ Dither(source, KnownDitherers.BayerDither4x4);
///
/// Dithers the image reducing it to a web-safe palette using ordered dithering.
///
- /// The pixel format.
/// The image this method extends.
/// The ordered ditherer.
- /// The to allow chaining of operations.
- public static IImageProcessingContext Dither(this IImageProcessingContext source, IOrderedDither dither)
- where TPixel : struct, IPixel
- => source.ApplyProcessor(new OrderedDitherPaletteProcessor(dither));
+ /// The to allow chaining of operations.
+ public static IImageProcessingContext Dither(this IImageProcessingContext source, IOrderedDither dither) =>
+ source.ApplyProcessor(new OrderedDitherPaletteProcessor(dither));
///
/// Dithers the image reducing it to the given palette using ordered dithering.
///
- /// The pixel format.
/// The image this method extends.
/// The ordered ditherer.
/// The palette to select substitute colors from.
- /// The to allow chaining of operations.
- public static IImageProcessingContext Dither(this IImageProcessingContext source, IOrderedDither dither, TPixel[] palette)
- where TPixel : struct, IPixel
- => source.ApplyProcessor(new OrderedDitherPaletteProcessor(dither, palette));
+ /// The to allow chaining of operations.
+ public static IImageProcessingContext Dither(
+ this IImageProcessingContext source,
+ IOrderedDither dither,
+ ReadOnlySpan palette) =>
+ source.ApplyProcessor(new OrderedDitherPaletteProcessor(dither, palette));
///
/// Dithers the image reducing it to a web-safe palette using ordered dithering.
///
- /// The pixel format.
/// The image this method extends.
/// The ordered ditherer.
///
/// The structure that specifies the portion of the image object to alter.
///
- /// The to allow chaining of operations.
- public static IImageProcessingContext Dither(this IImageProcessingContext source, IOrderedDither dither, Rectangle rectangle)
- where TPixel : struct, IPixel
- => source.ApplyProcessor(new OrderedDitherPaletteProcessor(dither), rectangle);
+ /// The to allow chaining of operations.
+ public static IImageProcessingContext Dither(
+ this IImageProcessingContext source,
+ IOrderedDither dither,
+ Rectangle rectangle) =>
+ source.ApplyProcessor(new OrderedDitherPaletteProcessor(dither), rectangle);
///
/// Dithers the image reducing it to the given palette using ordered dithering.
///
- /// The pixel format.
/// The image this method extends.
/// The ordered ditherer.
/// The palette to select substitute colors from.
///
/// The structure that specifies the portion of the image object to alter.
///
- /// The to allow chaining of operations.
- public static IImageProcessingContext Dither(this IImageProcessingContext source, IOrderedDither dither, TPixel[] palette, Rectangle rectangle)
- where TPixel : struct, IPixel
- => source.ApplyProcessor(new OrderedDitherPaletteProcessor(dither, palette), rectangle);
+ /// The to allow chaining of operations.
+ public static IImageProcessingContext Dither(
+ this IImageProcessingContext source,
+ IOrderedDither dither,
+ ReadOnlySpan palette,
+ Rectangle rectangle) =>
+ source.ApplyProcessor(new OrderedDitherPaletteProcessor(dither, palette), rectangle);
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffusionPaletteProcessor.cs b/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffusionPaletteProcessor.cs
index 911d3e8fd..5436a7733 100644
--- a/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffusionPaletteProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffusionPaletteProcessor.cs
@@ -2,21 +2,19 @@
// Licensed under the Apache License, Version 2.0.
using System;
-using SixLabors.ImageSharp.Advanced;
-using SixLabors.ImageSharp.PixelFormats;
-using SixLabors.Primitives;
+
+using SixLabors.ImageSharp.Processing.Processors.Binarization;
namespace SixLabors.ImageSharp.Processing.Processors.Dithering
{
///
- /// An that dithers an image using error diffusion.
+ /// Defines a dither operation using error diffusion.
+ /// If no palette is given this will default to the web safe colors defined in the CSS Color Module Level 4.
///
- /// The pixel format.
- internal class ErrorDiffusionPaletteProcessor : PaletteDitherProcessorBase
- where TPixel : struct, IPixel
+ public sealed class ErrorDiffusionPaletteProcessor : PaletteDitherProcessor
{
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
/// The error diffuser
public ErrorDiffusionPaletteProcessor(IErrorDiffuser diffuser)
@@ -25,22 +23,22 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
}
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
/// The error diffuser
/// The threshold to split the image. Must be between 0 and 1.
public ErrorDiffusionPaletteProcessor(IErrorDiffuser diffuser, float threshold)
- : this(diffuser, threshold, NamedColors.WebSafePalette)
+ : this(diffuser, threshold, Color.WebSafePalette)
{
}
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
/// The error diffuser
/// The threshold to split the image. Must be between 0 and 1.
/// The palette to select substitute colors from.
- public ErrorDiffusionPaletteProcessor(IErrorDiffuser diffuser, float threshold, TPixel[] palette)
+ public ErrorDiffusionPaletteProcessor(IErrorDiffuser diffuser, float threshold, ReadOnlySpan palette)
: base(palette)
{
Guard.NotNull(diffuser, nameof(diffuser));
@@ -60,59 +58,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
///
public float Threshold { get; }
- ///
- protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration)
+ ///
+ public override IImageProcessor CreatePixelSpecificProcessor()
{
- byte threshold = (byte)MathF.Round(this.Threshold * 255F);
- bool isAlphaOnly = typeof(TPixel) == typeof(Alpha8);
-
- var interest = Rectangle.Intersect(sourceRectangle, source.Bounds());
- int startY = interest.Y;
- int endY = interest.Bottom;
- int startX = interest.X;
- int endX = interest.Right;
-
- // Collect the values before looping so we can reduce our calculation count for identical sibling pixels
- TPixel sourcePixel = source[startX, startY];
- TPixel previousPixel = sourcePixel;
- PixelPair pair = this.GetClosestPixelPair(ref sourcePixel);
- Rgba32 rgba = default;
- 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);
-
- for (int y = startY; y < endY; y++)
- {
- Span row = source.GetPixelRowSpan(y);
-
- for (int x = startX; x < endX; x++)
- {
- sourcePixel = row[x];
-
- // Check if this is the same as the last pixel. If so use that value
- // rather than calculating it again. This is an inexpensive optimization.
- if (!previousPixel.Equals(sourcePixel))
- {
- pair = this.GetClosestPixelPair(ref sourcePixel);
-
- // No error to spread, exact match.
- if (sourcePixel.Equals(pair.First))
- {
- continue;
- }
-
- sourcePixel.ToRgba32(ref rgba);
- luminance = isAlphaOnly ? rgba.A : ImageMaths.Get8BitBT709Luminance(rgba.R, rgba.G, rgba.B);
-
- // Setup the previous pointer
- previousPixel = sourcePixel;
- }
-
- TPixel transformedPixel = luminance >= threshold ? pair.Second : pair.First;
- this.Diffuser.Dither(source, sourcePixel, transformedPixel, x, y, startX, startY, endX, endY);
- }
- }
+ return new ErrorDiffusionPaletteProcessor(this);
}
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffusionPaletteProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffusionPaletteProcessor{TPixel}.cs
new file mode 100644
index 000000000..7edf287e8
--- /dev/null
+++ b/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffusionPaletteProcessor{TPixel}.cs
@@ -0,0 +1,81 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+
+using SixLabors.ImageSharp.Advanced;
+using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.Primitives;
+
+namespace SixLabors.ImageSharp.Processing.Processors.Dithering
+{
+ ///
+ /// An that dithers an image using error diffusion.
+ ///
+ /// The pixel format.
+ internal class ErrorDiffusionPaletteProcessor : PaletteDitherProcessor
+ where TPixel : struct, IPixel
+ {
+ public ErrorDiffusionPaletteProcessor(ErrorDiffusionPaletteProcessor definition)
+ : base(definition)
+ {
+ }
+
+ private new ErrorDiffusionPaletteProcessor Definition => (ErrorDiffusionPaletteProcessor)base.Definition;
+
+ ///
+ protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration)
+ {
+ byte threshold = (byte)MathF.Round(this.Definition.Threshold * 255F);
+ bool isAlphaOnly = typeof(TPixel) == typeof(Alpha8);
+
+ var interest = Rectangle.Intersect(sourceRectangle, source.Bounds());
+ int startY = interest.Y;
+ int endY = interest.Bottom;
+ int startX = interest.X;
+ int endX = interest.Right;
+
+ // Collect the values before looping so we can reduce our calculation count for identical sibling pixels
+ TPixel sourcePixel = source[startX, startY];
+ TPixel previousPixel = sourcePixel;
+ PixelPair pair = this.GetClosestPixelPair(ref sourcePixel);
+ Rgba32 rgba = default;
+ 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);
+
+ for (int y = startY; y < endY; y++)
+ {
+ Span row = source.GetPixelRowSpan(y);
+
+ for (int x = startX; x < endX; x++)
+ {
+ sourcePixel = row[x];
+
+ // Check if this is the same as the last pixel. If so use that value
+ // rather than calculating it again. This is an inexpensive optimization.
+ if (!previousPixel.Equals(sourcePixel))
+ {
+ pair = this.GetClosestPixelPair(ref sourcePixel);
+
+ // No error to spread, exact match.
+ if (sourcePixel.Equals(pair.First))
+ {
+ continue;
+ }
+
+ sourcePixel.ToRgba32(ref rgba);
+ luminance = isAlphaOnly ? rgba.A : 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);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/Processors/Dithering/OrderedDitherPaletteProcessor.cs b/src/ImageSharp/Processing/Processors/Dithering/OrderedDitherPaletteProcessor.cs
index 1b4910a14..ba7c1e998 100644
--- a/src/ImageSharp/Processing/Processors/Dithering/OrderedDitherPaletteProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Dithering/OrderedDitherPaletteProcessor.cs
@@ -2,35 +2,30 @@
// Licensed under the Apache License, Version 2.0.
using System;
-using SixLabors.ImageSharp.Advanced;
-using SixLabors.ImageSharp.PixelFormats;
-using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Dithering
{
///
- /// An that dithers an image using error diffusion.
+ /// Defines a dithering operation that dithers an image using error diffusion.
/// If no palette is given this will default to the web safe colors defined in the CSS Color Module Level 4.
///
- /// The pixel format.
- internal class OrderedDitherPaletteProcessor : PaletteDitherProcessorBase
- where TPixel : struct, IPixel
+ public sealed class OrderedDitherPaletteProcessor : PaletteDitherProcessor
{
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
/// The ordered ditherer.
public OrderedDitherPaletteProcessor(IOrderedDither dither)
- : this(dither, NamedColors.WebSafePalette)
+ : this(dither, Color.WebSafePalette)
{
}
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
/// The ordered ditherer.
/// The palette to select substitute colors from.
- public OrderedDitherPaletteProcessor(IOrderedDither dither, TPixel[] palette)
+ public OrderedDitherPaletteProcessor(IOrderedDither dither, ReadOnlySpan palette)
: base(palette) => this.Dither = dither ?? throw new ArgumentNullException(nameof(dither));
///
@@ -38,57 +33,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
///
public IOrderedDither Dither { get; }
- ///
- protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration)
+ ///
+ public override IImageProcessor CreatePixelSpecificProcessor()
{
- bool isAlphaOnly = typeof(TPixel) == typeof(Alpha8);
-
- var interest = Rectangle.Intersect(sourceRectangle, source.Bounds());
- int startY = interest.Y;
- int endY = interest.Bottom;
- int startX = interest.X;
- int endX = interest.Right;
-
- // Collect the values before looping so we can reduce our calculation count for identical sibling pixels
- TPixel sourcePixel = source[startX, startY];
- TPixel previousPixel = sourcePixel;
- PixelPair pair = this.GetClosestPixelPair(ref sourcePixel);
- Rgba32 rgba = default;
- 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);
-
- for (int y = startY; y < endY; y++)
- {
- Span row = source.GetPixelRowSpan(y);
-
- for (int x = startX; x < endX; x++)
- {
- sourcePixel = row[x];
-
- // Check if this is the same as the last pixel. If so use that value
- // rather than calculating it again. This is an inexpensive optimization.
- if (!previousPixel.Equals(sourcePixel))
- {
- pair = this.GetClosestPixelPair(ref sourcePixel);
-
- // No error to spread, exact match.
- if (sourcePixel.Equals(pair.First))
- {
- continue;
- }
-
- sourcePixel.ToRgba32(ref rgba);
- luminance = isAlphaOnly ? rgba.A : ImageMaths.Get8BitBT709Luminance(rgba.R, rgba.G, rgba.B);
-
- // Setup the previous pointer
- previousPixel = sourcePixel;
- }
-
- this.Dither.Dither(source, sourcePixel, pair.Second, pair.First, luminance, x, y);
- }
- }
+ return new OrderedDitherPaletteProcessor(this);
}
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/Processors/Dithering/OrderedDitherPaletteProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Dithering/OrderedDitherPaletteProcessor{TPixel}.cs
new file mode 100644
index 000000000..eefa6e522
--- /dev/null
+++ b/src/ImageSharp/Processing/Processors/Dithering/OrderedDitherPaletteProcessor{TPixel}.cs
@@ -0,0 +1,79 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+
+using SixLabors.ImageSharp.Advanced;
+using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.Primitives;
+
+namespace SixLabors.ImageSharp.Processing.Processors.Dithering
+{
+ ///
+ /// An that dithers an image using error diffusion.
+ ///
+ /// The pixel format.
+ internal class OrderedDitherPaletteProcessor : PaletteDitherProcessor
+ where TPixel : struct, IPixel
+ {
+ public OrderedDitherPaletteProcessor(OrderedDitherPaletteProcessor definition)
+ : base(definition)
+ {
+ }
+
+ private new OrderedDitherPaletteProcessor Definition => (OrderedDitherPaletteProcessor)base.Definition;
+
+ ///
+ protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration)
+ {
+ bool isAlphaOnly = typeof(TPixel) == typeof(Alpha8);
+
+ var interest = Rectangle.Intersect(sourceRectangle, source.Bounds());
+ int startY = interest.Y;
+ int endY = interest.Bottom;
+ int startX = interest.X;
+ int endX = interest.Right;
+
+ // Collect the values before looping so we can reduce our calculation count for identical sibling pixels
+ TPixel sourcePixel = source[startX, startY];
+ TPixel previousPixel = sourcePixel;
+ PixelPair pair = this.GetClosestPixelPair(ref sourcePixel);
+ Rgba32 rgba = default;
+ 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);
+
+ for (int y = startY; y < endY; y++)
+ {
+ Span row = source.GetPixelRowSpan(y);
+
+ for (int x = startX; x < endX; x++)
+ {
+ sourcePixel = row[x];
+
+ // Check if this is the same as the last pixel. If so use that value
+ // rather than calculating it again. This is an inexpensive optimization.
+ if (!previousPixel.Equals(sourcePixel))
+ {
+ pair = this.GetClosestPixelPair(ref sourcePixel);
+
+ // No error to spread, exact match.
+ if (sourcePixel.Equals(pair.First))
+ {
+ continue;
+ }
+
+ sourcePixel.ToRgba32(ref rgba);
+ luminance = isAlphaOnly ? rgba.A : ImageMaths.Get8BitBT709Luminance(rgba.R, rgba.G, rgba.B);
+
+ // Setup the previous pointer
+ previousPixel = sourcePixel;
+ }
+
+ this.Definition.Dither.Dither(source, sourcePixel, pair.Second, pair.First, luminance, x, y);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor.cs b/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor.cs
new file mode 100644
index 000000000..904d02634
--- /dev/null
+++ b/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor.cs
@@ -0,0 +1,37 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+
+using SixLabors.ImageSharp.PixelFormats;
+
+namespace SixLabors.ImageSharp.Processing.Processors.Dithering
+{
+ ///
+ /// The base class for dither and diffusion processors that consume a palette.
+ ///
+ public abstract class PaletteDitherProcessor : IImageProcessor
+ {
+ private readonly Color[] palette;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The palette to select substitute colors from.
+ protected PaletteDitherProcessor(ReadOnlySpan palette)
+ {
+ // This shouldn't be a perf issue:
+ // these arrays are small, and created with low frequency.
+ this.palette = palette.ToArray();
+ }
+
+ ///
+ /// Gets the palette to select substitute colors from.
+ ///
+ public ReadOnlySpan Palette => this.palette;
+
+ ///
+ public abstract IImageProcessor CreatePixelSpecificProcessor()
+ where TPixel : struct, IPixel;
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessorBase.cs b/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs
similarity index 77%
rename from src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessorBase.cs
rename to src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs
index 6313b74c9..334eab833 100644
--- a/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessorBase.cs
+++ b/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs
@@ -1,10 +1,11 @@
-// Copyright (c) Six Labors and contributors.
+// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Collections.Generic;
using System.Numerics;
using System.Runtime.CompilerServices;
+
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;
@@ -14,7 +15,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
/// The base class for dither and diffusion processors that consume a palette.
///
/// The pixel format.
- internal abstract class PaletteDitherProcessorBase : ImageProcessor
+ internal abstract class PaletteDitherProcessor : ImageProcessor
where TPixel : struct, IPixel
{
private readonly Dictionary> cache = new Dictionary>();
@@ -24,19 +25,17 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
///
private Vector4[] paletteVector;
+ private TPixel[] palette;
+
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
- /// The palette to select substitute colors from.
- protected PaletteDitherProcessorBase(TPixel[] palette)
+ protected PaletteDitherProcessor(PaletteDitherProcessor definition)
{
- this.Palette = palette ?? throw new ArgumentNullException(nameof(palette));
+ this.Definition = definition;
}
- ///
- /// Gets the palette to select substitute colors from.
- ///
- public TPixel[] Palette { get; }
+ protected PaletteDitherProcessor Definition { get; }
///
/// Returns the two closest colors from the palette calculated via Euclidean distance in the Rgba space.
@@ -74,12 +73,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
{
leastDistance = distance;
secondClosest = closest;
- closest = this.Palette[index];
+ closest = this.palette[index];
}
else if (distance < secondLeastDistance)
{
secondLeastDistance = distance;
- secondClosest = this.Palette[index];
+ secondClosest = this.palette[index];
}
}
@@ -94,11 +93,19 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
{
base.BeforeFrameApply(source, sourceRectangle, configuration);
+ // Lazy init palette:
+ if (this.palette is null)
+ {
+ ReadOnlySpan sourcePalette = this.Definition.Palette;
+ this.palette = new TPixel[sourcePalette.Length];
+ Color.ToPixel(configuration, sourcePalette, this.palette);
+ }
+
// Lazy init paletteVector:
if (this.paletteVector is null)
{
- this.paletteVector = new Vector4[this.Palette.Length];
- PixelOperations.Instance.ToVector4(configuration, (ReadOnlySpan)this.Palette, (Span)this.paletteVector, PixelConversionModifiers.Scale);
+ this.paletteVector = new Vector4[this.palette.Length];
+ PixelOperations.Instance.ToVector4(configuration, (ReadOnlySpan)this.palette, (Span)this.paletteVector, PixelConversionModifiers.Scale);
}
}
}
diff --git a/tests/ImageSharp.Tests/Processing/Dithering/DitherTest.cs b/tests/ImageSharp.Tests/Processing/Dithering/DitherTest.cs
index f393d5923..59dcde2a6 100644
--- a/tests/ImageSharp.Tests/Processing/Dithering/DitherTest.cs
+++ b/tests/ImageSharp.Tests/Processing/Dithering/DitherTest.cs
@@ -1,6 +1,8 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
+using System;
+
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Dithering;
@@ -12,13 +14,21 @@ namespace SixLabors.ImageSharp.Tests.Processing.Binarization
{
public class DitherTest : BaseImageOperationsExtensionTest
{
+ private class Assert : Xunit.Assert
+ {
+ public static void Equal(ReadOnlySpan a, ReadOnlySpan b)
+ {
+ Xunit.Assert.True(a.SequenceEqual(b));
+ }
+ }
+
private readonly IOrderedDither orderedDither;
private readonly IErrorDiffuser errorDiffuser;
- private readonly Rgba32[] TestPalette =
+ private readonly Color[] TestPalette =
{
- Rgba32.Red,
- Rgba32.Green,
- Rgba32.Blue
+ Color.Red,
+ Color.Green,
+ Color.Blue
};
public DitherTest()
@@ -31,24 +41,24 @@ namespace SixLabors.ImageSharp.Tests.Processing.Binarization
public void Dither_CorrectProcessor()
{
this.operations.Dither(this.orderedDither);
- OrderedDitherPaletteProcessor p = this.Verify>();
+ OrderedDitherPaletteProcessor p = this.Verify();
Assert.Equal(this.orderedDither, p.Dither);
- Assert.Equal(NamedColors.WebSafePalette, p.Palette);
+ Assert.Equal(Color.WebSafePalette.ToArray(), p.Palette);
}
[Fact]
public void Dither_rect_CorrectProcessor()
{
this.operations.Dither(this.orderedDither, this.rect);
- OrderedDitherPaletteProcessor p = this.Verify>(this.rect);
+ OrderedDitherPaletteProcessor p = this.Verify(this.rect);
Assert.Equal(this.orderedDither, p.Dither);
- Assert.Equal(NamedColors.WebSafePalette, p.Palette);
+ Assert.Equal(Color.WebSafePalette, p.Palette);
}
[Fact]
public void Dither_index_CorrectProcessor()
{
this.operations.Dither(this.orderedDither, this.TestPalette);
- OrderedDitherPaletteProcessor p = this.Verify>();
+ OrderedDitherPaletteProcessor p = this.Verify();
Assert.Equal(this.orderedDither, p.Dither);
Assert.Equal(this.TestPalette, p.Palette);
}
@@ -57,7 +67,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Binarization
public void Dither_index_rect_CorrectProcessor()
{
this.operations.Dither(this.orderedDither, this.TestPalette, this.rect);
- OrderedDitherPaletteProcessor p = this.Verify>(this.rect);
+ OrderedDitherPaletteProcessor p = this.Verify(this.rect);
Assert.Equal(this.orderedDither, p.Dither);
Assert.Equal(this.TestPalette, p.Palette);
}
@@ -67,27 +77,27 @@ namespace SixLabors.ImageSharp.Tests.Processing.Binarization
public void Dither_ErrorDiffuser_CorrectProcessor()
{
this.operations.Diffuse(this.errorDiffuser, .4F);
- ErrorDiffusionPaletteProcessor p = this.Verify>();
+ ErrorDiffusionPaletteProcessor p = this.Verify();
Assert.Equal(this.errorDiffuser, p.Diffuser);
Assert.Equal(.4F, p.Threshold);
- Assert.Equal(NamedColors.WebSafePalette, p.Palette);
+ Assert.Equal(Color.WebSafePalette, p.Palette);
}
[Fact]
public void Dither_ErrorDiffuser_rect_CorrectProcessor()
{
this.operations.Diffuse(this.errorDiffuser, .3F, this.rect);
- ErrorDiffusionPaletteProcessor p = this.Verify>(this.rect);
+ ErrorDiffusionPaletteProcessor p = this.Verify(this.rect);
Assert.Equal(this.errorDiffuser, p.Diffuser);
Assert.Equal(.3F, p.Threshold);
- Assert.Equal(NamedColors.WebSafePalette, p.Palette);
+ Assert.Equal(Color.WebSafePalette, p.Palette);
}
[Fact]
public void Dither_ErrorDiffuser_CorrectProcessorWithColors()
{
this.operations.Diffuse(this.errorDiffuser, .5F, this.TestPalette);
- ErrorDiffusionPaletteProcessor p = this.Verify>();
+ ErrorDiffusionPaletteProcessor p = this.Verify();
Assert.Equal(this.errorDiffuser, p.Diffuser);
Assert.Equal(.5F, p.Threshold);
Assert.Equal(this.TestPalette, p.Palette);
@@ -97,7 +107,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Binarization
public void Dither_ErrorDiffuser_rect_CorrectProcessorWithColors()
{
this.operations.Diffuse(this.errorDiffuser, .5F, this.TestPalette, this.rect);
- ErrorDiffusionPaletteProcessor p = this.Verify>(this.rect);
+ ErrorDiffusionPaletteProcessor p = this.Verify(this.rect);
Assert.Equal(this.errorDiffuser, p.Diffuser);
Assert.Equal(.5F, p.Threshold);
Assert.Equal(this.TestPalette, p.Palette);