diff --git a/src/ImageSharp/Processing/AdaptiveThresholdExtensions.cs b/src/ImageSharp/Processing/AdaptiveThresholdExtensions.cs
index 33cf4b45be..4fc78a9588 100644
--- a/src/ImageSharp/Processing/AdaptiveThresholdExtensions.cs
+++ b/src/ImageSharp/Processing/AdaptiveThresholdExtensions.cs
@@ -1,11 +1,12 @@
-using SixLabors.ImageSharp.PixelFormats;
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
using SixLabors.ImageSharp.Processing.Processors.Binarization;
-using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing
{
///
- /// Extensions to perform AdaptiveThreshold through Mutator
+ /// Extensions to perform AdaptiveThreshold through Mutator.
///
public static class AdaptiveThresholdExtensions
{
@@ -13,47 +14,39 @@ namespace SixLabors.ImageSharp.Processing
/// Applies Bradley Adaptive Threshold to the image.
///
/// The image this method extends.
- /// The pixel format.
/// The .
- public static IImageProcessingContext AdaptiveThreshold(this IImageProcessingContext source)
- where TPixel : struct, IPixel
- => source.ApplyProcessor(new AdaptiveThresholdProcessor());
+ public static IImageProcessingContext AdaptiveThreshold(this IImageProcessingContext source)
+ => source.ApplyProcessor(new AdaptiveThresholdProcessor());
///
/// Applies Bradley Adaptive Threshold to the image.
///
/// The image this method extends.
/// Threshold limit (0.0-1.0) to consider for binarization.
- /// The pixel format.
/// The .
- public static IImageProcessingContext AdaptiveThreshold(this IImageProcessingContext source, float thresholdLimit)
- where TPixel : struct, IPixel
- => source.ApplyProcessor(new AdaptiveThresholdProcessor(thresholdLimit));
+ public static IImageProcessingContext AdaptiveThreshold(this IImageProcessingContext source, float thresholdLimit)
+ => source.ApplyProcessor(new AdaptiveThresholdProcessor(thresholdLimit));
///
/// Applies Bradley Adaptive Threshold to the image.
///
/// The image this method extends.
/// Upper (white) color for thresholding.
- /// Lower (black) color for thresholding
- /// The pixel format.
+ /// Lower (black) color for thresholding.
/// The .
- public static IImageProcessingContext AdaptiveThreshold(this IImageProcessingContext source, TPixel upper, TPixel lower)
- where TPixel : struct, IPixel
- => source.ApplyProcessor(new AdaptiveThresholdProcessor(upper, lower));
+ public static IImageProcessingContext AdaptiveThreshold(this IImageProcessingContext source, Color upper, Color lower)
+ => source.ApplyProcessor(new AdaptiveThresholdProcessor(upper, lower));
///
/// Applies Bradley Adaptive Threshold to the image.
///
/// The image this method extends.
/// Upper (white) color for thresholding.
- /// Lower (black) color for thresholding
+ /// Lower (black) color for thresholding.
/// Threshold limit (0.0-1.0) to consider for binarization.
- /// The pixel format.
/// The .
- public static IImageProcessingContext AdaptiveThreshold(this IImageProcessingContext source, TPixel upper, TPixel lower, float thresholdLimit)
- where TPixel : struct, IPixel
- => source.ApplyProcessor(new AdaptiveThresholdProcessor(upper, lower, thresholdLimit));
+ public static IImageProcessingContext AdaptiveThreshold(this IImageProcessingContext source, Color upper, Color lower, float thresholdLimit)
+ => source.ApplyProcessor(new AdaptiveThresholdProcessor(upper, lower, thresholdLimit));
///
/// Applies Bradley Adaptive Threshold to the image.
@@ -62,11 +55,9 @@ namespace SixLabors.ImageSharp.Processing
/// Upper (white) color for thresholding.
/// Lower (black) color for thresholding
/// Rectangle region to apply the processor on.
- /// The pixel format.
/// The .
- public static IImageProcessingContext AdaptiveThreshold(this IImageProcessingContext source, TPixel upper, TPixel lower, Rectangle rectangle)
- where TPixel : struct, IPixel
- => source.ApplyProcessor(new AdaptiveThresholdProcessor(upper, lower), rectangle);
+ public static IImageProcessingContext AdaptiveThreshold(this IImageProcessingContext source, Color upper, Color lower, Rectangle rectangle)
+ => source.ApplyProcessor(new AdaptiveThresholdProcessor(upper, lower), rectangle);
///
/// Applies Bradley Adaptive Threshold to the image.
@@ -76,10 +67,8 @@ namespace SixLabors.ImageSharp.Processing
/// Lower (black) color for thresholding
/// Threshold limit (0.0-1.0) to consider for binarization.
/// Rectangle region to apply the processor on.
- /// The pixel format.
/// The .
- public static IImageProcessingContext AdaptiveThreshold(this IImageProcessingContext source, TPixel upper, TPixel lower, float thresholdLimit, Rectangle rectangle)
- where TPixel : struct, IPixel
- => source.ApplyProcessor(new AdaptiveThresholdProcessor(upper, lower, thresholdLimit), rectangle);
+ public static IImageProcessingContext AdaptiveThreshold(this IImageProcessingContext source, Color upper, Color lower, float thresholdLimit, Rectangle rectangle)
+ => source.ApplyProcessor(new AdaptiveThresholdProcessor(upper, lower, thresholdLimit), rectangle);
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor.cs b/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor.cs
index 2ad170e380..3558a94899 100644
--- a/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor.cs
@@ -1,160 +1,77 @@
-// 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 SixLabors.ImageSharp.Advanced;
-using SixLabors.ImageSharp.Memory;
-using SixLabors.ImageSharp.ParallelUtils;
using SixLabors.ImageSharp.PixelFormats;
-using SixLabors.Memory;
-using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Binarization
{
///
- /// Performs Bradley Adaptive Threshold filter against an image
+ /// Performs Bradley Adaptive Threshold filter against an image.
///
- /// The pixel format of the image
- internal class AdaptiveThresholdProcessor : ImageProcessor
- where TPixel : struct, IPixel
+ ///
+ /// Implements "Adaptive Thresholding Using the Integral Image",
+ /// see paper: http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.420.7883&rep=rep1&type=pdf
+ ///
+ public class AdaptiveThresholdProcessor : IImageProcessor
{
- private readonly PixelOperations pixelOpInstance;
-
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
public AdaptiveThresholdProcessor()
- : this(NamedColors.White, NamedColors.Black, 0.85f)
+ : this(Color.White, Color.Black, 0.85f)
{
}
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
- /// Threshold limit
+ /// Threshold limit.
public AdaptiveThresholdProcessor(float thresholdLimit)
- : this(NamedColors.White, NamedColors.Black, thresholdLimit)
+ : this(Color.White, Color.Black, thresholdLimit)
{
}
- public AdaptiveThresholdProcessor(TPixel upper, TPixel lower)
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Color for upper threshold.
+ /// Color for lower threshold.
+ public AdaptiveThresholdProcessor(Color upper, Color lower)
: this(upper, lower, 0.85f)
{
}
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
- /// Color for upper threshold
- /// Color for lower threshold
- /// Threshold limit
- public AdaptiveThresholdProcessor(TPixel upper, TPixel lower, float thresholdLimit)
+ /// Color for upper threshold.
+ /// Color for lower threshold.
+ /// Threshold limit.
+ public AdaptiveThresholdProcessor(Color upper, Color lower, float thresholdLimit)
{
- this.pixelOpInstance = PixelOperations.Instance;
-
this.Upper = upper;
this.Lower = lower;
this.ThresholdLimit = thresholdLimit;
}
///
- /// Gets or sets upper color limit for thresholding
+ /// Gets or sets upper color limit for thresholding.
///
- public TPixel Upper { get; set; }
+ public Color Upper { get; set; }
///
- /// Gets or sets lower color limit for threshold
+ /// Gets or sets lower color limit for threshold.
///
- public TPixel Lower { get; set; }
+ public Color Lower { get; set; }
///
- /// Gets or sets the value for threshold limit
+ /// Gets or sets the value for threshold limit.
///
public float ThresholdLimit { get; set; }
- ///
- protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration)
- {
- Rectangle intersect = Rectangle.Intersect(sourceRectangle, source.Bounds());
-
- // Used ushort because the values should never exceed max ushort value
- ushort startY = (ushort)intersect.Y;
- ushort endY = (ushort)intersect.Bottom;
- ushort startX = (ushort)intersect.X;
- ushort endX = (ushort)intersect.Right;
-
- ushort width = (ushort)intersect.Width;
- ushort height = (ushort)intersect.Height;
-
- // ClusterSize defines the size of cluster to used to check for average. Tweaked to support upto 4k wide pixels and not more. 4096 / 16 is 256 thus the '-1'
- byte clusterSize = (byte)Math.Truncate((width / 16f) - 1);
-
- // Using pooled 2d buffer for integer image table and temp memory to hold Rgb24 converted pixel data
- using (Buffer2D intImage = configuration.MemoryAllocator.Allocate2D(width, height))
- using (IMemoryOwner tmpBuffer = configuration.MemoryAllocator.Allocate(width * height))
- {
- // Defines the rectangle section of the image to work on
- Rectangle workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY);
-
- this.pixelOpInstance.ToRgb24(source.GetPixelSpan(), tmpBuffer.GetSpan());
-
- for (ushort x = startX; x < endX; x++)
- {
- Span rgbSpan = tmpBuffer.GetSpan();
- ulong sum = 0;
- for (ushort y = startY; y < endY; y++)
- {
- ref Rgb24 rgb = ref rgbSpan[(width * y) + x];
-
- sum += (ulong)(rgb.R + rgb.G + rgb.G);
- if (x - startX != 0)
- {
- intImage[x - startX, y - startY] = intImage[x - startX - 1, y - startY] + sum;
- }
- else
- {
- intImage[x - startX, y - startY] = sum;
- }
- }
- }
-
- ParallelHelper.IterateRows(
- workingRectangle,
- configuration,
- rows =>
- {
- Span rgbSpan = tmpBuffer.GetSpan();
- ushort x1, y1, x2, y2;
- uint count = 0;
- long sum = 0;
-
- for (ushort x = startX; x < endX; x++)
- {
- for (ushort y = (ushort)rows.Min; y < (ushort)rows.Max; y++)
- {
- ref Rgb24 rgb = ref rgbSpan[(width * y) + x];
-
- x1 = (ushort)Math.Max(x - startX - clusterSize + 1, 0);
- x2 = (ushort)Math.Min(x - startX + clusterSize + 1, width - 1);
- y1 = (ushort)Math.Max(y - startY - clusterSize + 1, 0);
- y2 = (ushort)Math.Min(y - startY + clusterSize + 1, height - 1);
-
- count = (uint)((x2 - x1) * (y2 - y1));
- sum = (long)Math.Min(intImage[x2, y2] - intImage[x1, y2] - intImage[x2, y1] + intImage[x1, y1], long.MaxValue);
-
- if ((rgb.R + rgb.G + rgb.B) * count <= sum * this.ThresholdLimit)
- {
- source[x, y] = this.Lower;
- }
- else
- {
- source[x, y] = this.Upper;
- }
- }
- }
- });
- }
- }
+ ///
+ public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle)
+ where TPixel : unmanaged, IPixel
+ => new AdaptiveThresholdProcessor(configuration, this, source, sourceRectangle);
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor{TPixel}.cs
new file mode 100644
index 0000000000..130fc40f3f
--- /dev/null
+++ b/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor{TPixel}.cs
@@ -0,0 +1,168 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Buffers;
+using System.Runtime.CompilerServices;
+
+using SixLabors.ImageSharp.Advanced;
+using SixLabors.ImageSharp.Memory;
+using SixLabors.ImageSharp.PixelFormats;
+
+namespace SixLabors.ImageSharp.Processing.Processors.Binarization
+{
+ ///
+ /// Performs Bradley Adaptive Threshold filter against an image.
+ ///
+ internal class AdaptiveThresholdProcessor : ImageProcessor
+ where TPixel : unmanaged, IPixel
+ {
+ private readonly AdaptiveThresholdProcessor definition;
+ private readonly PixelOperations pixelOpInstance;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The configuration which allows altering default behaviour or extending the library.
+ /// The defining the processor parameters.
+ /// The source for the current processor instance.
+ /// The source area to process for the current processor instance.
+ public AdaptiveThresholdProcessor(Configuration configuration, AdaptiveThresholdProcessor definition, Image source, Rectangle sourceRectangle)
+ : base(configuration, source, sourceRectangle)
+ {
+ this.pixelOpInstance = PixelOperations.Instance;
+ this.definition = definition;
+ }
+
+ ///
+ protected override void OnFrameApply(ImageFrame source)
+ {
+ var intersect = Rectangle.Intersect(this.SourceRectangle, source.Bounds());
+
+ Configuration configuration = this.Configuration;
+ TPixel upper = this.definition.Upper.ToPixel();
+ TPixel lower = this.definition.Lower.ToPixel();
+ float thresholdLimit = this.definition.ThresholdLimit;
+
+ // Used ushort because the values should never exceed max ushort value.
+ ushort startY = (ushort)intersect.Y;
+ ushort endY = (ushort)intersect.Bottom;
+ ushort startX = (ushort)intersect.X;
+ ushort endX = (ushort)intersect.Right;
+
+ ushort width = (ushort)intersect.Width;
+ ushort height = (ushort)intersect.Height;
+
+ // ClusterSize defines the size of cluster to used to check for average. Tweaked to support up to 4k wide pixels and not more. 4096 / 16 is 256 thus the '-1'
+ byte clusterSize = (byte)Math.Truncate((width / 16f) - 1);
+
+ // Using pooled 2d buffer for integer image table and temp memory to hold Rgb24 converted pixel data.
+ using (Buffer2D intImage = this.Configuration.MemoryAllocator.Allocate2D(width, height))
+ using (IMemoryOwner tmpBuffer = this.Configuration.MemoryAllocator.Allocate(width * height))
+ {
+ // Defines the rectangle section of the image to work on.
+ var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY);
+
+ this.pixelOpInstance.ToRgb24(this.Configuration, source.GetPixelSpan(), tmpBuffer.GetSpan());
+
+ for (ushort x = startX; x < endX; x++)
+ {
+ Span rgbSpan = tmpBuffer.GetSpan();
+ ulong sum = 0;
+ for (ushort y = startY; y < endY; y++)
+ {
+ ref Rgb24 rgb = ref rgbSpan[(width * y) + x];
+
+ sum += (ulong)(rgb.R + rgb.G + rgb.G);
+ if (x - startX != 0)
+ {
+ intImage[x - startX, y - startY] = intImage[x - startX - 1, y - startY] + sum;
+ }
+ else
+ {
+ intImage[x - startX, y - startY] = sum;
+ }
+ }
+ }
+
+ var operation = new RowOperation(workingRectangle, source, tmpBuffer, intImage, upper, lower, thresholdLimit, clusterSize, startX, endX, startY);
+ ParallelRowIterator.IterateRows(
+ configuration,
+ workingRectangle,
+ in operation);
+ }
+ }
+
+ private readonly struct RowOperation : IRowOperation
+ {
+ private readonly Rectangle bounds;
+ private readonly ImageFrame source;
+ private readonly IMemoryOwner tmpBuffer;
+ private readonly Buffer2D intImage;
+ private readonly TPixel upper;
+ private readonly TPixel lower;
+ private readonly float thresholdLimit;
+ private readonly ushort startX;
+ private readonly ushort endX;
+ private readonly ushort startY;
+ private readonly byte clusterSize;
+
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public RowOperation(
+ Rectangle bounds,
+ ImageFrame source,
+ IMemoryOwner tmpBuffer,
+ Buffer2D intImage,
+ TPixel upper,
+ TPixel lower,
+ float thresholdLimit,
+ byte clusterSize,
+ ushort startX,
+ ushort endX,
+ ushort startY)
+ {
+ this.bounds = bounds;
+ this.source = source;
+ this.tmpBuffer = tmpBuffer;
+ this.intImage = intImage;
+ this.upper = upper;
+ this.lower = lower;
+ this.thresholdLimit = thresholdLimit;
+ this.startX = startX;
+ this.endX = endX;
+ this.startY = startY;
+ this.clusterSize = clusterSize;
+ }
+
+ ///
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public void Invoke(int y)
+ {
+ Span rgbSpan = this.tmpBuffer.GetSpan();
+ ushort x1, y1, x2, y2;
+
+ for (ushort x = this.startX; x < this.endX; x++)
+ {
+ ref Rgb24 rgb = ref rgbSpan[(this.bounds.Width * y) + x];
+
+ x1 = (ushort)Math.Max(x - this.startX - this.clusterSize + 1, 0);
+ x2 = (ushort)Math.Min(x - this.startX + this.clusterSize + 1, this.bounds.Width - 1);
+ y1 = (ushort)Math.Max(y - this.startY - this.clusterSize + 1, 0);
+ y2 = (ushort)Math.Min(y - this.startY + this.clusterSize + 1, this.bounds.Height - 1);
+
+ var count = (uint)((x2 - x1) * (y2 - y1));
+ var sum = (long)Math.Min(this.intImage[x2, y2] - this.intImage[x1, y2] - this.intImage[x2, y1] + this.intImage[x1, y1], long.MaxValue);
+
+ if ((rgb.R + rgb.G + rgb.B) * count <= sum * this.thresholdLimit)
+ {
+ this.source[x, y] = this.lower;
+ }
+ else
+ {
+ this.source[x, y] = this.upper;
+ }
+ }
+ }
+ }
+ }
+}