diff --git a/src/ImageSharp/Processing/Extensions/ProcessingExtensions.IntegralImage.cs b/src/ImageSharp/Processing/Extensions/ProcessingExtensions.IntegralImage.cs
index ed30c36e7..deed04454 100644
--- a/src/ImageSharp/Processing/Extensions/ProcessingExtensions.IntegralImage.cs
+++ b/src/ImageSharp/Processing/Extensions/ProcessingExtensions.IntegralImage.cs
@@ -22,26 +22,60 @@ namespace SixLabors.ImageSharp.Processing
/// The containing all the sums.
public static Buffer2D CalculateIntegralImage(this Image source)
where TPixel : unmanaged, IPixel
+ => CalculateIntegralImage(source.Frames.RootFrame);
+
+ ///
+ /// Apply an image integral.
+ ///
+ /// The image on which to apply the integral.
+ /// The bounds within the image frame to calculate.
+ /// The type of the pixel.
+ /// The containing all the sums.
+ public static Buffer2D CalculateIntegralImage(this Image source, Rectangle bounds)
+ where TPixel : unmanaged, IPixel
+ => CalculateIntegralImage(source.Frames.RootFrame, bounds);
+
+ ///
+ /// Apply an image integral.
+ ///
+ /// The image frame on which to apply the integral.
+ /// The type of the pixel.
+ /// The containing all the sums.
+ public static Buffer2D CalculateIntegralImage(this ImageFrame source)
+ where TPixel : unmanaged, IPixel
+ => source.CalculateIntegralImage(source.Bounds());
+
+ ///
+ /// Apply an image integral.
+ ///
+ /// The image frame on which to apply the integral.
+ /// The bounds within the image frame to calculate.
+ /// The type of the pixel.
+ /// The containing all the sums.
+ public static Buffer2D CalculateIntegralImage(this ImageFrame source, Rectangle bounds)
+ where TPixel : unmanaged, IPixel
{
Configuration configuration = source.GetConfiguration();
- int endY = source.Height;
- int endX = source.Width;
+ var interest = Rectangle.Intersect(bounds, source.Bounds());
+ int startY = interest.Y;
+ int startX = interest.X;
+ int endY = interest.Height;
- Buffer2D intImage = configuration.MemoryAllocator.Allocate2D(source.Width, source.Height);
+ Buffer2D intImage = configuration.MemoryAllocator.Allocate2D(interest.Width, interest.Height);
ulong sumX0 = 0;
- Buffer2D sourceBuffer = source.Frames.RootFrame.PixelBuffer;
+ Buffer2D sourceBuffer = source.PixelBuffer;
- using (IMemoryOwner tempRow = configuration.MemoryAllocator.Allocate(source.Width))
+ using (IMemoryOwner tempRow = configuration.MemoryAllocator.Allocate(interest.Width))
{
Span tempSpan = tempRow.GetSpan();
- Span sourceRow = sourceBuffer.DangerousGetRowSpan(0);
+ Span sourceRow = sourceBuffer.DangerousGetRowSpan(startY).Slice(startX, tempSpan.Length);
Span destRow = intImage.DangerousGetRowSpan(0);
PixelOperations.Instance.ToL8(configuration, sourceRow, tempSpan);
// First row
- for (int x = 0; x < endX; x++)
+ for (int x = 0; x < tempSpan.Length; x++)
{
sumX0 += tempSpan[x].PackedValue;
destRow[x] = sumX0;
@@ -52,7 +86,7 @@ namespace SixLabors.ImageSharp.Processing
// All other rows
for (int y = 1; y < endY; y++)
{
- sourceRow = sourceBuffer.DangerousGetRowSpan(y);
+ sourceRow = sourceBuffer.DangerousGetRowSpan(y + startY).Slice(startX, tempSpan.Length);
destRow = intImage.DangerousGetRowSpan(y);
PixelOperations.Instance.ToL8(configuration, sourceRow, tempSpan);
@@ -62,7 +96,7 @@ namespace SixLabors.ImageSharp.Processing
destRow[0] = sumX0 + previousDestRow[0];
// Process all other colmns
- for (int x = 1; x < endX; x++)
+ for (int x = 1; x < tempSpan.Length; x++)
{
sumX0 += tempSpan[x].PackedValue;
destRow[x] = sumX0 + previousDestRow[x];
diff --git a/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor{TPixel}.cs
index ecbec84e3..e7c5ad471 100644
--- a/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor{TPixel}.cs
+++ b/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor{TPixel}.cs
@@ -3,7 +3,6 @@
using System;
using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@@ -27,70 +26,33 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization
/// 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.definition = definition;
- }
+ => this.definition = definition;
///
protected override void OnFrameApply(ImageFrame source)
{
- var intersect = Rectangle.Intersect(this.SourceRectangle, source.Bounds());
+ var interest = 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;
- int startY = intersect.Y;
- int endY = intersect.Bottom;
- int startX = intersect.X;
- int endX = intersect.Right;
-
- int width = intersect.Width;
- int height = 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);
-
- Buffer2D sourceBuffer = source.PixelBuffer;
-
- // 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))
- {
- Rgba32 rgb = default;
- for (int x = startX; x < endX; x++)
- {
- ulong sum = 0;
- for (int y = startY; y < endY; y++)
- {
- Span row = sourceBuffer.DangerousGetRowSpan(y);
- ref TPixel rowRef = ref MemoryMarshal.GetReference(row);
- ref TPixel color = ref Unsafe.Add(ref rowRef, x);
- color.ToRgba32(ref rgb);
+ // 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.Clamp(interest.Width / 16F, 0, 255);
- sum += (ulong)(rgb.R + rgb.G + rgb.B);
-
- 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(intersect, source.PixelBuffer, intImage, upper, lower, thresholdLimit, clusterSize, startX, endX, startY);
- ParallelRowIterator.IterateRows(
- configuration,
- intersect,
- in operation);
- }
+ using Buffer2D intImage = source.CalculateIntegralImage(interest);
+ RowOperation operation = new(configuration, interest, source.PixelBuffer, intImage, upper, lower, thresholdLimit, clusterSize);
+ ParallelRowIterator.IterateRows(
+ configuration,
+ interest,
+ in operation);
}
- private readonly struct RowOperation : IRowOperation
+ private readonly struct RowOperation : IRowOperation
{
+ private readonly Configuration configuration;
private readonly Rectangle bounds;
private readonly Buffer2D source;
private readonly Buffer2D intImage;
@@ -98,64 +60,58 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization
private readonly TPixel lower;
private readonly float thresholdLimit;
private readonly int startX;
- private readonly int endX;
private readonly int startY;
private readonly byte clusterSize;
[MethodImpl(InliningOptions.ShortMethod)]
public RowOperation(
+ Configuration configuration,
Rectangle bounds,
Buffer2D source,
Buffer2D intImage,
TPixel upper,
TPixel lower,
float thresholdLimit,
- byte clusterSize,
- int startX,
- int endX,
- int startY)
+ byte clusterSize)
{
+ this.configuration = configuration;
this.bounds = bounds;
+ this.startX = bounds.X;
+ this.startY = bounds.Y;
this.source = source;
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)
+ public void Invoke(int y, Span span)
{
- Rgba32 rgb = default;
- Span pixelRow = this.source.DangerousGetRowSpan(y);
+ Span rowSpan = this.source.DangerousGetRowSpan(y).Slice(this.startX, span.Length);
+ PixelOperations.Instance.ToL8(this.configuration, rowSpan, span);
+
int maxX = this.bounds.Width - 1;
int maxY = this.bounds.Height - 1;
-
- for (int x = this.startX; x < this.endX; x++)
+ for (int x = 0; x < rowSpan.Length; x++)
{
- TPixel pixel = pixelRow[x];
- pixel.ToRgba32(ref rgb);
-
- int x1 = Math.Min(Math.Max(x - this.startX - this.clusterSize + 1, 0), maxX);
- int x2 = Math.Min(x - this.startX + this.clusterSize + 1, maxX);
- int y1 = Math.Min(Math.Max(y - this.startY - this.clusterSize + 1, 0), maxY);
+ int x1 = Math.Clamp(x - this.clusterSize + 1, 0, maxX);
+ int x2 = Math.Min(x + this.clusterSize + 1, maxX);
+ int y1 = Math.Clamp(y - this.startY - this.clusterSize + 1, 0, maxY);
int y2 = Math.Min(y - this.startY + this.clusterSize + 1, maxY);
uint count = (uint)((x2 - x1) * (y2 - y1));
- long sum = (long)Math.Min(this.intImage[x2, y2] - this.intImage[x1, y2] - this.intImage[x2, y1] + this.intImage[x1, y1], long.MaxValue);
+ ulong sum = Math.Min(this.intImage[x2, y2] - this.intImage[x1, y2] - this.intImage[x2, y1] + this.intImage[x1, y1], ulong.MaxValue);
- if ((rgb.R + rgb.G + rgb.B) * count <= sum * this.thresholdLimit)
+ if (span[x].PackedValue * count <= sum * this.thresholdLimit)
{
- this.source[x, y] = this.lower;
+ rowSpan[x] = this.lower;
}
else
{
- this.source[x, y] = this.upper;
+ rowSpan[x] = this.upper;
}
}
}
diff --git a/tests/ImageSharp.Tests/Processing/Binarization/AdaptiveThresholdTests.cs b/tests/ImageSharp.Tests/Processing/Binarization/AdaptiveThresholdTests.cs
index 5c725dbf0..c7378bac9 100644
--- a/tests/ImageSharp.Tests/Processing/Binarization/AdaptiveThresholdTests.cs
+++ b/tests/ImageSharp.Tests/Processing/Binarization/AdaptiveThresholdTests.cs
@@ -137,6 +137,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Binarization
Exception exception = Record.Exception(() =>
{
using Image image = provider.GetImage();
+ image.Mutate(img => img.AdaptiveThreshold(.5F));
+ image.DebugSave(provider);
});
Assert.Null(exception);
diff --git a/tests/ImageSharp.Tests/Processing/IntegralImageTests.cs b/tests/ImageSharp.Tests/Processing/IntegralImageTests.cs
index 330b95a6c..89d8e5330 100644
--- a/tests/ImageSharp.Tests/Processing/IntegralImageTests.cs
+++ b/tests/ImageSharp.Tests/Processing/IntegralImageTests.cs
@@ -32,6 +32,30 @@ namespace SixLabors.ImageSharp.Tests.Processing
});
}
+ [Theory]
+ [WithFile(TestImages.Png.Bradley01, PixelTypes.Rgba32)]
+ [WithFile(TestImages.Png.Bradley02, PixelTypes.Rgba32)]
+ [WithFile(TestImages.Png.Ducky, PixelTypes.Rgba32)]
+ public void CalculateIntegralImage_WithBounds_Rgba32Works(TestImageProvider provider)
+ {
+ using Image image = provider.GetImage();
+
+ Rectangle interest = new(image.Width / 4, image.Height / 4, image.Width / 2, image.Height / 2);
+
+ // Act:
+ Buffer2D integralBuffer = image.CalculateIntegralImage(interest);
+
+ // Assert:
+ VerifySumValues(provider, integralBuffer, interest, (Rgba32 pixel) =>
+ {
+ L8 outputPixel = default;
+
+ outputPixel.FromRgba32(pixel);
+
+ return outputPixel.PackedValue;
+ });
+ }
+
[Theory]
[WithFile(TestImages.Png.Bradley01, PixelTypes.L8)]
[WithFile(TestImages.Png.Bradley02, PixelTypes.L8)]
@@ -43,16 +67,41 @@ namespace SixLabors.ImageSharp.Tests.Processing
Buffer2D integralBuffer = image.CalculateIntegralImage();
// Assert:
- VerifySumValues(provider, integralBuffer, (L8 pixel) => { return pixel.PackedValue; });
+ VerifySumValues(provider, integralBuffer, (L8 pixel) => pixel.PackedValue);
}
+ [Theory]
+ [WithFile(TestImages.Png.Bradley01, PixelTypes.L8)]
+ [WithFile(TestImages.Png.Bradley02, PixelTypes.L8)]
+ public void CalculateIntegralImage_WithBounds_L8Works(TestImageProvider provider)
+ {
+ using Image image = provider.GetImage();
+
+ Rectangle interest = new(image.Width / 4, image.Height / 4, image.Width / 2, image.Height / 2);
+
+ // Act:
+ Buffer2D integralBuffer = image.CalculateIntegralImage(interest);
+
+ // Assert:
+ VerifySumValues(provider, integralBuffer, interest, (L8 pixel) => pixel.PackedValue);
+ }
+
+ private static void VerifySumValues(
+ TestImageProvider provider,
+ Buffer2D integralBuffer,
+ System.Func getPixel)
+ where TPixel : unmanaged, IPixel
+ => VerifySumValues(provider, integralBuffer, integralBuffer.Bounds(), getPixel);
+
private static void VerifySumValues(
TestImageProvider provider,
Buffer2D integralBuffer,
+ Rectangle bounds,
System.Func getPixel)
where TPixel : unmanaged, IPixel
{
- Image image = provider.GetImage();
+ // Image image = provider.GetImage();
+ Buffer2DRegion image = provider.GetImage().GetRootFramePixelBuffer().GetRegion(bounds);
// Check top-left corner
Assert.Equal(getPixel(image[0, 0]), integralBuffer[0, 0]);
diff --git a/tests/Images/External/ReferenceOutput/AdaptiveThresholdTests/AdaptiveThreshold_WithRectangle_Works_Rgba32_Bradley02.png b/tests/Images/External/ReferenceOutput/AdaptiveThresholdTests/AdaptiveThreshold_WithRectangle_Works_Rgba32_Bradley02.png
index ea5a333e8..94d50e1ad 100644
--- a/tests/Images/External/ReferenceOutput/AdaptiveThresholdTests/AdaptiveThreshold_WithRectangle_Works_Rgba32_Bradley02.png
+++ b/tests/Images/External/ReferenceOutput/AdaptiveThresholdTests/AdaptiveThreshold_WithRectangle_Works_Rgba32_Bradley02.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:36f60abb0ade0320779e242716c61b6dbabc8243a125f0a3145be35e233e117c
-size 24542
+oid sha256:5745f61e9b8cd49066b347605deee6dcde17690b9dc0f675466df6b2db706bd6
+size 22348
diff --git a/tests/Images/External/ReferenceOutput/AdaptiveThresholdTests/AdaptiveThreshold_Works_Rgba32_Bradley01.png b/tests/Images/External/ReferenceOutput/AdaptiveThresholdTests/AdaptiveThreshold_Works_Rgba32_Bradley01.png
index 62660ef4b..5e53399d7 100644
--- a/tests/Images/External/ReferenceOutput/AdaptiveThresholdTests/AdaptiveThreshold_Works_Rgba32_Bradley01.png
+++ b/tests/Images/External/ReferenceOutput/AdaptiveThresholdTests/AdaptiveThreshold_Works_Rgba32_Bradley01.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:4c4a92f0ecd0f2ec06b12091b14f2d421605ef178092bf4f7f7cb4e661270945
-size 52876
+oid sha256:7a767913020c3924f0a7ae95b20c064993a2fcdc3007610df6abe6f34c194ef8
+size 1644
diff --git a/tests/Images/External/ReferenceOutput/AdaptiveThresholdTests/AdaptiveThreshold_Works_Rgba32_Bradley02.png b/tests/Images/External/ReferenceOutput/AdaptiveThresholdTests/AdaptiveThreshold_Works_Rgba32_Bradley02.png
index 7c40f64c0..97a594cf6 100644
--- a/tests/Images/External/ReferenceOutput/AdaptiveThresholdTests/AdaptiveThreshold_Works_Rgba32_Bradley02.png
+++ b/tests/Images/External/ReferenceOutput/AdaptiveThresholdTests/AdaptiveThreshold_Works_Rgba32_Bradley02.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:c6d99bcaefa9e344e602465d08714f628b165e7783f73ddb3316e31c3f679825
-size 5760
+oid sha256:e02f5e94b9251be80250926678a2d8bc05318f40c3eff98204e74312ffbca138
+size 2239
diff --git a/tests/Images/External/ReferenceOutput/AdaptiveThresholdTests/AdaptiveThreshold_Works_Rgba32_ducky.png b/tests/Images/External/ReferenceOutput/AdaptiveThresholdTests/AdaptiveThreshold_Works_Rgba32_ducky.png
index 467206ea6..5c19a8421 100644
--- a/tests/Images/External/ReferenceOutput/AdaptiveThresholdTests/AdaptiveThreshold_Works_Rgba32_ducky.png
+++ b/tests/Images/External/ReferenceOutput/AdaptiveThresholdTests/AdaptiveThreshold_Works_Rgba32_ducky.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:6826d39280ffd36f075e52cd055975748fedec25a4b58c148b623a6dc6a517f4
-size 2040
+oid sha256:fdd84a24f616d7f06f78ebca01540b59cf1cf8564f442548fe4c8ede6dc1d412
+size 757