From 0ef2c3881daff0394330df5ccbe38fb53a46383e Mon Sep 17 00:00:00 2001 From: Simanto Rahman Date: Fri, 5 Oct 2018 00:41:07 -0230 Subject: [PATCH 01/22] Addition of Breadley AdaptiveThreshold --- .../Processing/AdaptiveThresholdExtensions.cs | 47 +++++++ .../AdaptiveThresholdProcessor.cs | 115 ++++++++++++++++++ 2 files changed, 162 insertions(+) create mode 100644 src/ImageSharp/Processing/AdaptiveThresholdExtensions.cs create mode 100644 src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor.cs diff --git a/src/ImageSharp/Processing/AdaptiveThresholdExtensions.cs b/src/ImageSharp/Processing/AdaptiveThresholdExtensions.cs new file mode 100644 index 000000000..9a6d63342 --- /dev/null +++ b/src/ImageSharp/Processing/AdaptiveThresholdExtensions.cs @@ -0,0 +1,47 @@ +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Extensions to perform AdaptiveThreshold through Mutator + /// + public static class AdaptiveThresholdExtensions + { + /// + /// 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()); + + /// + /// 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. + /// The . + public static IImageProcessingContext AdaptiveThreshold(this IImageProcessingContext source, TPixel upper, TPixel lower) + where TPixel : struct, IPixel + => 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 + /// 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); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor.cs b/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor.cs new file mode 100644 index 000000000..b14de6679 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor.cs @@ -0,0 +1,115 @@ +using System; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors +{ + /// + /// Performs Bradley Adaptive Threshold filter against an image + /// + /// The pixel format of the image + internal class AdaptiveThresholdProcessor : IImageProcessor + where TPixel : struct, IPixel + { + /// + /// Initializes a new instance of the class. + /// + public AdaptiveThresholdProcessor() + : this(NamedColors.White, NamedColors.Black) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Color for upper threshold + /// Color for lower threshold + public AdaptiveThresholdProcessor(TPixel upper, TPixel lower) + { + this.Upper = upper; + this.Lower = lower; + } + + /// + /// Gets or sets upper color limit for thresholding + /// + public TPixel Upper { get; set; } + + /// + /// Gets or sets lower color limit for threshold + /// + public TPixel Lower { get; set; } + + public unsafe void Apply(Image source, Rectangle sourceRectangle) + { + ushort xStart = (ushort)Math.Max(0, sourceRectangle.X); + ushort yStart = (ushort)Math.Max(0, sourceRectangle.Y); + ushort xEnd = (ushort)Math.Min(xStart + sourceRectangle.Width, source.Width); + ushort yEnd = (ushort)Math.Min(yStart + sourceRectangle.Height, source.Height); + + // Algorithm variables + uint sum, count; + ushort s = (ushort)Math.Truncate((xEnd / 16f) - 1); + uint[,] intImage = new uint[yEnd, xEnd]; + + // Trying to figure out how to do this + // Using (Buffer2D intImg = source.GetConfiguration().MemoryAllocator.Allocate2D) + Rgb24 rgb = default; + + for (ushort i = yStart; i < yEnd; i++) + { + Span span = source.GetPixelRowSpan(i); + + sum = 0; + + for (ushort j = xStart; j < xEnd; j++) + { + span[j].ToRgb24(ref rgb); + + sum += (uint)(rgb.R + rgb.G + rgb.B); + + if (i != 0) + { + intImage[i, j] = intImage[i - 1, j] + sum; + } + else + { + intImage[i, j] = sum; + } + } + } + + // How can I parallelize this? + ushort x1, x2, y1, y2; + + for (ushort i = yStart; i < yEnd; i++) + { + Span span = source.GetPixelRowSpan(i); + + for (ushort j = xStart; j < xEnd; j++) + { + x1 = (ushort)Math.Max(i - s + 1, 0); + x2 = (ushort)Math.Min(i + s + 1, yEnd - 1); + y1 = (ushort)Math.Max(j - s + 1, 0); + y2 = (ushort)Math.Min(j + s + 1, xEnd - 1); + + count = (ushort)((x2 - x1) * (y2 - y1)); + + sum = intImage[x2, y2] - intImage[x1, y2] - intImage[x2, y1] + intImage[x1, y1]; + + span[j].ToRgb24(ref rgb); + + if ((rgb.R + rgb.G + rgb.B) * count < sum * (1.0 - 0.15)) + { + span[j] = this.Lower; + } + else + { + span[j] = this.Upper; + } + } + } + } + } +} \ No newline at end of file From 5a080aab4cbbb079f8d48c9c4af456a0079ad1df Mon Sep 17 00:00:00 2001 From: Simanto Rahman Date: Fri, 5 Oct 2018 12:35:15 -0230 Subject: [PATCH 02/22] # Added Rect.Intersect # Inherited from ImageProcessor # Minor changes to variables # Minor Tweaks --- .../AdaptiveThresholdProcessor.cs | 107 ++++++++++-------- 1 file changed, 59 insertions(+), 48 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor.cs b/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor.cs index b14de6679..46e2e67c0 100644 --- a/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor.cs @@ -1,6 +1,8 @@ using System; using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Memory; using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors @@ -9,7 +11,7 @@ namespace SixLabors.ImageSharp.Processing.Processors /// Performs Bradley Adaptive Threshold filter against an image /// /// The pixel format of the image - internal class AdaptiveThresholdProcessor : IImageProcessor + internal class AdaptiveThresholdProcessor : ImageProcessor where TPixel : struct, IPixel { /// @@ -41,72 +43,81 @@ namespace SixLabors.ImageSharp.Processing.Processors /// public TPixel Lower { get; set; } - public unsafe void Apply(Image source, Rectangle sourceRectangle) + /// + protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) { - ushort xStart = (ushort)Math.Max(0, sourceRectangle.X); - ushort yStart = (ushort)Math.Max(0, sourceRectangle.Y); - ushort xEnd = (ushort)Math.Min(xStart + sourceRectangle.Width, source.Width); - ushort yEnd = (ushort)Math.Min(yStart + sourceRectangle.Height, source.Height); + var interest = Rectangle.Intersect(sourceRectangle, source.Bounds()); + ushort startY = (ushort)interest.Y; + ushort endY = (ushort)interest.Bottom; + ushort startX = (ushort)interest.X; + ushort endX = (ushort)interest.Right; - // Algorithm variables - uint sum, count; - ushort s = (ushort)Math.Truncate((xEnd / 16f) - 1); - uint[,] intImage = new uint[yEnd, xEnd]; + ushort width = (ushort)(endX - startX); + ushort height = (ushort)(endY - startY); - // Trying to figure out how to do this - // Using (Buffer2D intImg = source.GetConfiguration().MemoryAllocator.Allocate2D) - Rgb24 rgb = default; + // Algorithm variables // + ulong sum; + uint count; - for (ushort i = yStart; i < yEnd; i++) - { - Span span = source.GetPixelRowSpan(i); + // Tweaked to support upto 4k wide pixels + ushort s = (ushort)Math.Truncate((width / 16f) - 1); - sum = 0; + // Trying to figure out how to do this + using (Buffer2D intImage = configuration.MemoryAllocator.Allocate2D(width, height, AllocationOptions.Clean)) + { + Rgb24 rgb = default; - for (ushort j = xStart; j < xEnd; j++) + for (ushort i = startY; i < endY; i++) { - span[j].ToRgb24(ref rgb); + Span span = source.GetPixelRowSpan(i); - sum += (uint)(rgb.R + rgb.G + rgb.B); + sum = 0; - if (i != 0) + for (ushort j = startX; j < endX; j++) { - intImage[i, j] = intImage[i - 1, j] + sum; - } - else - { - intImage[i, j] = sum; + span[j].ToRgb24(ref rgb); + + sum += (uint)(rgb.R + rgb.G + rgb.B); + + if (i != 0) + { + intImage[i, j] = intImage[i - 1, j] + sum; + } + else + { + intImage[i, j] = sum; + } } } - } - // How can I parallelize this? - ushort x1, x2, y1, y2; + // How can I parallelize this? + ushort x1, x2, y1, y2; - for (ushort i = yStart; i < yEnd; i++) - { - Span span = source.GetPixelRowSpan(i); - - for (ushort j = xStart; j < xEnd; j++) + for (ushort i = startY; i < endY; i++) { - x1 = (ushort)Math.Max(i - s + 1, 0); - x2 = (ushort)Math.Min(i + s + 1, yEnd - 1); - y1 = (ushort)Math.Max(j - s + 1, 0); - y2 = (ushort)Math.Min(j + s + 1, xEnd - 1); + Span span = source.GetPixelRowSpan(i); - count = (ushort)((x2 - x1) * (y2 - y1)); + for (ushort j = startX; j < endX; j++) + { + x1 = (ushort)Math.Max(i - s + 1, 0); + x2 = (ushort)Math.Min(i + s + 1, endY - 1); + y1 = (ushort)Math.Max(j - s + 1, 0); + y2 = (ushort)Math.Min(j + s + 1, endX - 1); - sum = intImage[x2, y2] - intImage[x1, y2] - intImage[x2, y1] + intImage[x1, y1]; + count = (uint)((x2 - x1) * (y2 - y1)); - span[j].ToRgb24(ref rgb); + sum = intImage[x2, y2] - intImage[x1, y2] - intImage[x2, y1] + intImage[x1, y1]; - if ((rgb.R + rgb.G + rgb.B) * count < sum * (1.0 - 0.15)) - { - span[j] = this.Lower; - } - else - { - span[j] = this.Upper; + span[j].ToRgb24(ref rgb); + + if ((rgb.R + rgb.G + rgb.B) * count < sum * (1.0 - 0.15)) + { + span[j] = this.Lower; + } + else + { + span[j] = this.Upper; + } } } } From 625ea86dd36ea4a79dab51698bd00b7607dc2d00 Mon Sep 17 00:00:00 2001 From: SimantoR Date: Sat, 13 Oct 2018 04:01:48 -0230 Subject: [PATCH 03/22] Added parallelism to loops --- .../AdaptiveThresholdProcessor.cs | 127 +++++++++++------- tests/Images/External | 2 +- 2 files changed, 76 insertions(+), 53 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor.cs b/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor.cs index 46e2e67c0..898e1f8fd 100644 --- a/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor.cs @@ -1,6 +1,11 @@ -using System; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.ParallelUtils; using SixLabors.ImageSharp.PixelFormats; using SixLabors.Memory; using SixLabors.Primitives; @@ -46,80 +51,98 @@ namespace SixLabors.ImageSharp.Processing.Processors /// protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) { - var interest = Rectangle.Intersect(sourceRectangle, source.Bounds()); - ushort startY = (ushort)interest.Y; - ushort endY = (ushort)interest.Bottom; - ushort startX = (ushort)interest.X; - ushort endX = (ushort)interest.Right; + var intersect = Rectangle.Intersect(sourceRectangle, source.Bounds()); + ushort startY = (ushort)intersect.Y; + ushort endY = (ushort)intersect.Bottom; + ushort startX = (ushort)intersect.X; + ushort endX = (ushort)intersect.Right; ushort width = (ushort)(endX - startX); ushort height = (ushort)(endY - startY); - // Algorithm variables // - ulong sum; - uint count; - - // Tweaked to support upto 4k wide pixels - ushort s = (ushort)Math.Truncate((width / 16f) - 1); + // Tweaked to support upto 4k wide pixels and not more. 4096 / 16 is 256 thus the '-1' + byte s = (byte)Math.Truncate((width / 16f) - 1); - // Trying to figure out how to do this + // Using pooled 2d buffer for integer image table using (Buffer2D intImage = configuration.MemoryAllocator.Allocate2D(width, height, AllocationOptions.Clean)) { - Rgb24 rgb = default; - - for (ushort i = startY; i < endY; i++) - { - Span span = source.GetPixelRowSpan(i); - - sum = 0; + var workingnRectangle = Rectangle.FromLTRB(startX, startY, endX, endY); - for (ushort j = startX; j < endX; j++) + ParallelHelper.IterateRows( + workingnRectangle, + configuration, + rows => { - span[j].ToRgb24(ref rgb); + ulong sum; - sum += (uint)(rgb.R + rgb.G + rgb.B); + Rgb24 rgb = default; - if (i != 0) + for (int i = rows.Min; i < rows.Max; i++) { - intImage[i, j] = intImage[i - 1, j] + sum; - } - else - { - intImage[i, j] = sum; + var row = source.GetPixelRowSpan(i); + + sum = 0; + + for (int j = startX; j < endX; j++) + { + row[j].ToRgb24(ref rgb); + sum += (ulong)(rgb.B + rgb.G + rgb.B); + + if (i != 0) + { + intImage[i, j] = intImage[i - 1, j] + sum; + } + else + { + intImage[i, j] = sum; + } + } } } - } + ); - // How can I parallelize this? - ushort x1, x2, y1, y2; + ParallelHelper.IterateRows( + workingnRectangle, + configuration, + rows => + { + ushort x1, x2, y1, y2; - for (ushort i = startY; i < endY; i++) - { - Span span = source.GetPixelRowSpan(i); + uint count; - for (ushort j = startX; j < endX; j++) - { - x1 = (ushort)Math.Max(i - s + 1, 0); - x2 = (ushort)Math.Min(i + s + 1, endY - 1); - y1 = (ushort)Math.Max(j - s + 1, 0); - y2 = (ushort)Math.Min(j + s + 1, endX - 1); + long sum; - count = (uint)((x2 - x1) * (y2 - y1)); + for (int i = rows.Min; i < rows.Max; i++) + { + var row = source.GetPixelRowSpan(i); - sum = intImage[x2, y2] - intImage[x1, y2] - intImage[x2, y1] + intImage[x1, y1]; + Rgb24 rgb = default; - span[j].ToRgb24(ref rgb); + for (int j = startX; j < endX; j++) + { + x1 = (ushort)Math.Max(i - s + 1, 0); + x2 = (ushort)Math.Min(i + s + 1, endY - 1); + y1 = (ushort)Math.Max(j - s + 1, 0); + y2 = (ushort)Math.Min(j + s + 1, endX - 1); - if ((rgb.R + rgb.G + rgb.B) * count < sum * (1.0 - 0.15)) - { - span[j] = this.Lower; - } - else - { - span[j] = this.Upper; + count = (uint)((x2 - x1) * (y2 - y1)); + + sum = (long)(intImage[x2, y2] - intImage[x1, y2] - intImage[x2, y1] + intImage[x1, y1]); + + row[j].ToRgb24(ref rgb); + + if ((rgb.R + rgb.G + rgb.B) * count < sum * (1.0 - 0.15)) + { + row[j] = this.Lower; + } + else + { + row[j] = this.Upper; + } + } } } - } + ); } } } diff --git a/tests/Images/External b/tests/Images/External index ee90e5f32..5f3cbd839 160000 --- a/tests/Images/External +++ b/tests/Images/External @@ -1 +1 @@ -Subproject commit ee90e5f32218027744b5d40058b587cc1047b76f +Subproject commit 5f3cbd839fbbffae615d294d1dabafdcabc64cf9 From 616c7731b8e9d6c257f819e5f58a1e4a1eb17b3c Mon Sep 17 00:00:00 2001 From: Simanto Rahman Date: Wed, 24 Oct 2018 13:38:07 -0230 Subject: [PATCH 04/22] Temporary fix to accomodate #744 --- .../AdaptiveThresholdProcessor.cs | 85 ++++++++++--------- 1 file changed, 46 insertions(+), 39 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor.cs b/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor.cs index 898e1f8fd..baecdf7f0 100644 --- a/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor.cs @@ -2,7 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.Threading.Tasks; +using System.Buffers; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.ParallelUtils; @@ -34,8 +34,8 @@ namespace SixLabors.ImageSharp.Processing.Processors /// Color for lower threshold public AdaptiveThresholdProcessor(TPixel upper, TPixel lower) { - this.Upper = upper; - this.Lower = lower; + this.Upper.PackFromRgba32(upper.ToRgba32()); + this.Lower.PackFromRgba32(lower.ToRgba32()); } /// @@ -75,26 +75,29 @@ namespace SixLabors.ImageSharp.Processing.Processors { ulong sum; - Rgb24 rgb = default; - for (int i = rows.Min; i < rows.Max; i++) { - var row = source.GetPixelRowSpan(i); - - sum = 0; - - for (int j = startX; j < endX; j++) + using (IMemoryOwner tmpPixels = configuration.MemoryAllocator.Allocate(width, AllocationOptions.None)) { - row[j].ToRgb24(ref rgb); - sum += (ulong)(rgb.B + rgb.G + rgb.B); + Span span = tmpPixels.GetSpan(); + PixelOperations.Instance.ToRgb24(source.GetPixelRowSpan(i), span, width); - if (i != 0) - { - intImage[i, j] = intImage[i - 1, j] + sum; - } - else + sum = 0; + + for (int j = startX; j < endX; j++) { - intImage[i, j] = sum; + ref Rgb24 rgb = ref span[(width * j) + i]; + + sum += (ulong)(rgb.B + rgb.G + rgb.B); + + if (i != 0) + { + intImage[i, j] = intImage[i - 1, j] + sum; + } + else + { + intImage[i, j] = sum; + } } } } @@ -114,30 +117,34 @@ namespace SixLabors.ImageSharp.Processing.Processors for (int i = rows.Min; i < rows.Max; i++) { - var row = source.GetPixelRowSpan(i); - - Rgb24 rgb = default; - - for (int j = startX; j < endX; j++) + using (IMemoryOwner tmpPixes = configuration.MemoryAllocator.Allocate(width)) { - x1 = (ushort)Math.Max(i - s + 1, 0); - x2 = (ushort)Math.Min(i + s + 1, endY - 1); - y1 = (ushort)Math.Max(j - s + 1, 0); - y2 = (ushort)Math.Min(j + s + 1, endX - 1); - - count = (uint)((x2 - x1) * (y2 - y1)); - - sum = (long)(intImage[x2, y2] - intImage[x1, y2] - intImage[x2, y1] + intImage[x1, y1]); + Span span = tmpPixes.GetSpan(); + PixelOperations.Instance.ToRgb24(source.GetPixelRowSpan(i), span, width); - row[j].ToRgb24(ref rgb); - - if ((rgb.R + rgb.G + rgb.B) * count < sum * (1.0 - 0.15)) - { - row[j] = this.Lower; - } - else + for (int j = startX; j < endX; j++) { - row[j] = this.Upper; + ref Rgb24 rgb = ref span[(width * j) + 1]; + + x1 = (ushort)Math.Max(i - s + 1, 0); + x2 = (ushort)Math.Min(i + s + 1, endY - 1); + y1 = (ushort)Math.Max(j - s + 1, 0); + y2 = (ushort)Math.Min(j + s + 1, endX - 1); + + count = (uint)((x2 - x1) * (y2 - y1)); + + sum = (long)(intImage[x2, y2] - intImage[x1, y2] - intImage[x2, y1] + intImage[x1, y1]) + + if ((rgb.R + rgb.G + rgb.B) * count < sum * (1.0 - 0.15)) + { + //row[j] = this.Lower; + rgb = this.Lower.ToRgba32().Rgb; + } + else + { + //row[j] = this.Upper; + rgb = this.Upper.ToRgba32().Rgb; + } } } } From c82f0c567ec3de0c471744a36bb1ed904fe073fd Mon Sep 17 00:00:00 2001 From: Simanto Rahman Date: Wed, 24 Oct 2018 13:42:10 -0230 Subject: [PATCH 05/22] Few general changes without effecting the algorithm implementation --- .../AdaptiveThresholdProcessor.cs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor.cs b/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor.cs index baecdf7f0..50dfdfd9e 100644 --- a/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor.cs @@ -57,11 +57,13 @@ namespace SixLabors.ImageSharp.Processing.Processors ushort startX = (ushort)intersect.X; ushort endX = (ushort)intersect.Right; - ushort width = (ushort)(endX - startX); - ushort height = (ushort)(endY - startY); + ushort width = (ushort)intersect.Width; + ushort height = (ushort)intersect.Height; // Tweaked to support upto 4k wide pixels and not more. 4096 / 16 is 256 thus the '-1' - byte s = (byte)Math.Truncate((width / 16f) - 1); + byte clusterSize = (byte)((width / 16) - 1); + + float threshold = 0.85f; // Using pooled 2d buffer for integer image table using (Buffer2D intImage = configuration.MemoryAllocator.Allocate2D(width, height, AllocationOptions.Clean)) @@ -126,23 +128,21 @@ namespace SixLabors.ImageSharp.Processing.Processors { ref Rgb24 rgb = ref span[(width * j) + 1]; - x1 = (ushort)Math.Max(i - s + 1, 0); - x2 = (ushort)Math.Min(i + s + 1, endY - 1); - y1 = (ushort)Math.Max(j - s + 1, 0); - y2 = (ushort)Math.Min(j + s + 1, endX - 1); + x1 = (ushort)Math.Max(i - clusterSize + 1, 0); + x2 = (ushort)Math.Min(i + clusterSize + 1, endY - 1); + y1 = (ushort)Math.Max(j - clusterSize + 1, 0); + y2 = (ushort)Math.Min(j + clusterSize + 1, endX - 1); count = (uint)((x2 - x1) * (y2 - y1)); sum = (long)(intImage[x2, y2] - intImage[x1, y2] - intImage[x2, y1] + intImage[x1, y1]) - if ((rgb.R + rgb.G + rgb.B) * count < sum * (1.0 - 0.15)) + if ((rgb.R + rgb.G + rgb.B) * count < sum * threshold) { - //row[j] = this.Lower; rgb = this.Lower.ToRgba32().Rgb; } else { - //row[j] = this.Upper; rgb = this.Upper.ToRgba32().Rgb; } } From cecfcbaaf3166883da9496c1e3a6c41cf2876d38 Mon Sep 17 00:00:00 2001 From: Simanto Rahman Date: Wed, 24 Oct 2018 13:57:29 -0230 Subject: [PATCH 06/22] Fixed few breaking changes --- .../AdaptiveThresholdProcessor.cs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor.cs b/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor.cs index 50dfdfd9e..d13b84b8a 100644 --- a/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor.cs @@ -19,6 +19,8 @@ namespace SixLabors.ImageSharp.Processing.Processors internal class AdaptiveThresholdProcessor : ImageProcessor where TPixel : struct, IPixel { + private readonly PixelOperations pixelOpInstance; + /// /// Initializes a new instance of the class. /// @@ -34,19 +36,20 @@ namespace SixLabors.ImageSharp.Processing.Processors /// Color for lower threshold public AdaptiveThresholdProcessor(TPixel upper, TPixel lower) { - this.Upper.PackFromRgba32(upper.ToRgba32()); - this.Lower.PackFromRgba32(lower.ToRgba32()); + this.pixelOpInstance = PixelOperations.Instance; + this.Upper.FromRgba32(upper.ToRgba32()); + this.Lower.FromRgba32(lower.ToRgba32()); } /// /// Gets or sets upper color limit for thresholding /// - public TPixel Upper { get; set; } + public Rgb24 Upper { get; set; } /// /// Gets or sets lower color limit for threshold /// - public TPixel Lower { get; set; } + public Rgb24 Lower { get; set; } /// protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) @@ -82,7 +85,7 @@ namespace SixLabors.ImageSharp.Processing.Processors using (IMemoryOwner tmpPixels = configuration.MemoryAllocator.Allocate(width, AllocationOptions.None)) { Span span = tmpPixels.GetSpan(); - PixelOperations.Instance.ToRgb24(source.GetPixelRowSpan(i), span, width); + this.pixelOpInstance.ToRgb24(source.GetPixelRowSpan(i), span); sum = 0; @@ -122,7 +125,7 @@ namespace SixLabors.ImageSharp.Processing.Processors using (IMemoryOwner tmpPixes = configuration.MemoryAllocator.Allocate(width)) { Span span = tmpPixes.GetSpan(); - PixelOperations.Instance.ToRgb24(source.GetPixelRowSpan(i), span, width); + this.pixelOpInstance.ToRgb24(source.GetPixelRowSpan(i), span); for (int j = startX; j < endX; j++) { @@ -139,11 +142,11 @@ namespace SixLabors.ImageSharp.Processing.Processors if ((rgb.R + rgb.G + rgb.B) * count < sum * threshold) { - rgb = this.Lower.ToRgba32().Rgb; + rgb = this.Lower; } else { - rgb = this.Upper.ToRgba32().Rgb; + rgb = this.Upper; } } } From 7f457f5d4573e0aa5ebd39bf6a6b1763a47353aa Mon Sep 17 00:00:00 2001 From: Simanto Rahman Date: Wed, 24 Oct 2018 14:14:20 -0230 Subject: [PATCH 07/22] Missed an end of line by accident :p --- .../AdaptiveThresholdProcessor.cs | 92 +++++++++---------- 1 file changed, 46 insertions(+), 46 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor.cs b/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor.cs index d13b84b8a..b6748ce00 100644 --- a/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor.cs @@ -77,81 +77,81 @@ namespace SixLabors.ImageSharp.Processing.Processors workingnRectangle, configuration, rows => - { - ulong sum; - - for (int i = rows.Min; i < rows.Max; i++) { - using (IMemoryOwner tmpPixels = configuration.MemoryAllocator.Allocate(width, AllocationOptions.None)) - { - Span span = tmpPixels.GetSpan(); - this.pixelOpInstance.ToRgb24(source.GetPixelRowSpan(i), span); + ulong sum; - sum = 0; - - for (int j = startX; j < endX; j++) + for (int i = rows.Min; i < rows.Max; i++) + { + using (IMemoryOwner tmpPixels = configuration.MemoryAllocator.Allocate(width, AllocationOptions.None)) { - ref Rgb24 rgb = ref span[(width * j) + i]; + Span span = tmpPixels.GetSpan(); + this.pixelOpInstance.ToRgb24(source.GetPixelRowSpan(i), span); - sum += (ulong)(rgb.B + rgb.G + rgb.B); + sum = 0; - if (i != 0) + for (int j = startX; j < endX; j++) { - intImage[i, j] = intImage[i - 1, j] + sum; - } - else - { - intImage[i, j] = sum; + ref Rgb24 rgb = ref span[(width * j) + i]; + + sum += (ulong)(rgb.B + rgb.G + rgb.B); + + if (i != 0) + { + intImage[i, j] = intImage[i - 1, j] + sum; + } + else + { + intImage[i, j] = sum; + } } } } } - } ); ParallelHelper.IterateRows( workingnRectangle, configuration, rows => - { - ushort x1, x2, y1, y2; + { + ushort x1, x2, y1, y2; - uint count; + uint count; - long sum; + long sum; - for (int i = rows.Min; i < rows.Max; i++) - { - using (IMemoryOwner tmpPixes = configuration.MemoryAllocator.Allocate(width)) + for (int i = rows.Min; i < rows.Max; i++) { - Span span = tmpPixes.GetSpan(); - this.pixelOpInstance.ToRgb24(source.GetPixelRowSpan(i), span); - - for (int j = startX; j < endX; j++) + using (IMemoryOwner tmpPixes = configuration.MemoryAllocator.Allocate(width)) { - ref Rgb24 rgb = ref span[(width * j) + 1]; + Span span = tmpPixes.GetSpan(); + this.pixelOpInstance.ToRgb24(source.GetPixelRowSpan(i), span); - x1 = (ushort)Math.Max(i - clusterSize + 1, 0); - x2 = (ushort)Math.Min(i + clusterSize + 1, endY - 1); - y1 = (ushort)Math.Max(j - clusterSize + 1, 0); - y2 = (ushort)Math.Min(j + clusterSize + 1, endX - 1); + for (int j = startX; j < endX; j++) + { + ref Rgb24 rgb = ref span[(width * j) + 1]; - count = (uint)((x2 - x1) * (y2 - y1)); + x1 = (ushort)Math.Max(i - clusterSize + 1, 0); + x2 = (ushort)Math.Min(i + clusterSize + 1, endY - 1); + y1 = (ushort)Math.Max(j - clusterSize + 1, 0); + y2 = (ushort)Math.Min(j + clusterSize + 1, endX - 1); - sum = (long)(intImage[x2, y2] - intImage[x1, y2] - intImage[x2, y1] + intImage[x1, y1]) + count = (uint)((x2 - x1) * (y2 - y1)); - if ((rgb.R + rgb.G + rgb.B) * count < sum * threshold) - { - rgb = this.Lower; - } - else - { - rgb = this.Upper; + sum = (long)(intImage[x2, y2] - intImage[x1, y2] - intImage[x2, y1] + intImage[x1, y1]); + + if ((rgb.R + rgb.G + rgb.B) * count < sum * threshold) + { + rgb = this.Lower; + } + else + { + rgb = this.Upper; + } } } } } - } ); } } From 865607b3e83a8bec7eacbbe0ac22e8b870bf7af2 Mon Sep 17 00:00:00 2001 From: Simanto Rahman Date: Thu, 25 Oct 2018 12:13:48 -0230 Subject: [PATCH 08/22] Used TempBuffer and fixed few logical errors --- .../AdaptiveThresholdProcessor.cs | 130 +++++++++--------- 1 file changed, 62 insertions(+), 68 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor.cs b/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor.cs index b6748ce00..0602777e3 100644 --- a/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor.cs @@ -37,19 +37,20 @@ namespace SixLabors.ImageSharp.Processing.Processors public AdaptiveThresholdProcessor(TPixel upper, TPixel lower) { this.pixelOpInstance = PixelOperations.Instance; - this.Upper.FromRgba32(upper.ToRgba32()); - this.Lower.FromRgba32(lower.ToRgba32()); + + this.Upper = upper; + this.Lower = lower; } /// /// Gets or sets upper color limit for thresholding /// - public Rgb24 Upper { get; set; } + public TPixel Upper { get; set; } /// /// Gets or sets lower color limit for threshold /// - public Rgb24 Lower { get; set; } + public TPixel Lower { get; set; } /// protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) @@ -69,90 +70,83 @@ namespace SixLabors.ImageSharp.Processing.Processors float threshold = 0.85f; // Using pooled 2d buffer for integer image table - using (Buffer2D intImage = configuration.MemoryAllocator.Allocate2D(width, height, AllocationOptions.Clean)) + using (Buffer2D intImage = configuration.MemoryAllocator.Allocate2D(width, height)) { - var workingnRectangle = Rectangle.FromLTRB(startX, startY, endX, endY); + var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY); - ParallelHelper.IterateRows( - workingnRectangle, + ParallelHelper.IterateRowsWithTempBuffer( + workingRectangle, configuration, - rows => + (rows, memory) => + { + ulong sum = 0; + + Span tmpSpan = memory.Span; + + for (int i = rows.Min; i < rows.Max; i++) { - ulong sum; + this.pixelOpInstance.ToRgb24(source.GetPixelRowSpan(i), tmpSpan); + + sum = 0; - for (int i = rows.Min; i < rows.Max; i++) + for (int j = startX; j < endX; j++) { - using (IMemoryOwner tmpPixels = configuration.MemoryAllocator.Allocate(width, AllocationOptions.None)) + ref Rgb24 rgb = ref tmpSpan[j]; + + sum += (ulong)(rgb.R + rgb.G + rgb.B); + + if (i != 0) + { + intImage[i, j] = intImage[i - 1, j] + sum; + } + else { - Span span = tmpPixels.GetSpan(); - this.pixelOpInstance.ToRgb24(source.GetPixelRowSpan(i), span); - - sum = 0; - - for (int j = startX; j < endX; j++) - { - ref Rgb24 rgb = ref span[(width * j) + i]; - - sum += (ulong)(rgb.B + rgb.G + rgb.B); - - if (i != 0) - { - intImage[i, j] = intImage[i - 1, j] + sum; - } - else - { - intImage[i, j] = sum; - } - } + intImage[i, j] = sum; } } } - ); + }); - ParallelHelper.IterateRows( - workingnRectangle, + ParallelHelper.IterateRowsWithTempBuffer( + workingRectangle, configuration, - rows => - { - ushort x1, x2, y1, y2; + (rows, memory) => + { + ushort x1, x2, y1, y2; + uint count = 0; + long sum = 0; - uint count; + Span tmpSpan = memory.Span; - long sum; + for (int i = rows.Min; i < rows.Max; i++) + { + Span originalSpan = source.GetPixelRowSpan(i); + this.pixelOpInstance.ToRgb24(originalSpan, tmpSpan); - for (int i = rows.Min; i < rows.Max; i++) + for (int j = startX; j < endX; j++) { - using (IMemoryOwner tmpPixes = configuration.MemoryAllocator.Allocate(width)) + ref Rgb24 rgb = ref tmpSpan[j]; + + x1 = (ushort)Math.Max(i - clusterSize + 1, 0); + x2 = (ushort)Math.Min(i + clusterSize + 1, endY - 1); + y1 = (ushort)Math.Max(j - clusterSize + 1, 0); + y2 = (ushort)Math.Min(j + clusterSize + 1, endX - 1); + + count = (uint)((x2 - x1) * (y2 - y1)); + + sum = (long)(intImage[x2, y2] - intImage[x1, y2] - intImage[x2, y1] + intImage[x1, y1]); + + if ((rgb.R + rgb.G + rgb.B) * count < sum * threshold) + { + originalSpan[j] = this.Lower; + } + else { - Span span = tmpPixes.GetSpan(); - this.pixelOpInstance.ToRgb24(source.GetPixelRowSpan(i), span); - - for (int j = startX; j < endX; j++) - { - ref Rgb24 rgb = ref span[(width * j) + 1]; - - x1 = (ushort)Math.Max(i - clusterSize + 1, 0); - x2 = (ushort)Math.Min(i + clusterSize + 1, endY - 1); - y1 = (ushort)Math.Max(j - clusterSize + 1, 0); - y2 = (ushort)Math.Min(j + clusterSize + 1, endX - 1); - - count = (uint)((x2 - x1) * (y2 - y1)); - - sum = (long)(intImage[x2, y2] - intImage[x1, y2] - intImage[x2, y1] + intImage[x1, y1]); - - if ((rgb.R + rgb.G + rgb.B) * count < sum * threshold) - { - rgb = this.Lower; - } - else - { - rgb = this.Upper; - } - } + originalSpan[j] = this.Upper; } } } - ); + }); } } } From 3c6410294025fc88b25f8a99ccbd8b067c22b154 Mon Sep 17 00:00:00 2001 From: Simanto Rahman Date: Thu, 25 Oct 2018 12:50:40 -0230 Subject: [PATCH 09/22] Added contructor to control threshold limit --- .../Processing/AdaptiveThresholdExtensions.cs | 42 ++++++++++++++++++- .../AdaptiveThresholdProcessor.cs | 31 +++++++++++--- 2 files changed, 65 insertions(+), 8 deletions(-) diff --git a/src/ImageSharp/Processing/AdaptiveThresholdExtensions.cs b/src/ImageSharp/Processing/AdaptiveThresholdExtensions.cs index 9a6d63342..6c8795aa5 100644 --- a/src/ImageSharp/Processing/AdaptiveThresholdExtensions.cs +++ b/src/ImageSharp/Processing/AdaptiveThresholdExtensions.cs @@ -1,5 +1,5 @@ using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Processors; +using SixLabors.ImageSharp.Processing.Processors.Binarization; using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing @@ -19,18 +19,42 @@ namespace SixLabors.ImageSharp.Processing where TPixel : struct, IPixel => 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 threshold) + where TPixel : struct, IPixel + => source.ApplyProcessor(new AdaptiveThresholdProcessor(threshold)); + /// /// 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. + /// The pixel format. /// The . public static IImageProcessingContext AdaptiveThreshold(this IImageProcessingContext source, TPixel upper, TPixel lower) where TPixel : struct, IPixel => 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 + /// 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 threshold) + where TPixel : struct, IPixel + => source.ApplyProcessor(new AdaptiveThresholdProcessor(upper, lower, threshold)); + /// /// Applies Bradley Adaptive Threshold to the image. /// @@ -43,5 +67,19 @@ namespace SixLabors.ImageSharp.Processing public static IImageProcessingContext AdaptiveThreshold(this IImageProcessingContext source, TPixel upper, TPixel lower, Rectangle rectangle) where TPixel : struct, IPixel => source.ApplyProcessor(new AdaptiveThresholdProcessor(upper, lower), rectangle); + + /// + /// Applies Bradley Adaptive Threshold to the image. + /// + /// The image this method extends. + /// Upper (white) color for thresholding. + /// 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 threshold, Rectangle rectangle) + where TPixel : struct, IPixel + => source.ApplyProcessor(new AdaptiveThresholdProcessor(upper, lower, threshold), 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 0602777e3..2bed73be0 100644 --- a/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor.cs @@ -10,7 +10,7 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.Memory; using SixLabors.Primitives; -namespace SixLabors.ImageSharp.Processing.Processors +namespace SixLabors.ImageSharp.Processing.Processors.Binarization { /// /// Performs Bradley Adaptive Threshold filter against an image @@ -25,7 +25,21 @@ namespace SixLabors.ImageSharp.Processing.Processors /// Initializes a new instance of the class. /// public AdaptiveThresholdProcessor() - : this(NamedColors.White, NamedColors.Black) + : this(NamedColors.White, NamedColors.Black, 0.85f) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Threshold limit + public AdaptiveThresholdProcessor(float threshold) + : this(NamedColors.White, NamedColors.Black, threshold) + { + } + + public AdaptiveThresholdProcessor(TPixel upper, TPixel lower) + : this(upper, lower, 0.85f) { } @@ -34,12 +48,14 @@ namespace SixLabors.ImageSharp.Processing.Processors /// /// Color for upper threshold /// Color for lower threshold - public AdaptiveThresholdProcessor(TPixel upper, TPixel lower) + /// Threshold limit + public AdaptiveThresholdProcessor(TPixel upper, TPixel lower, float threshold) { this.pixelOpInstance = PixelOperations.Instance; this.Upper = upper; this.Lower = lower; + this.Threshold = threshold; } /// @@ -52,6 +68,11 @@ namespace SixLabors.ImageSharp.Processing.Processors /// public TPixel Lower { get; set; } + /// + /// Gets or sets the value for threshold limit + /// + public float Threshold { get; set; } + /// protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) { @@ -67,8 +88,6 @@ namespace SixLabors.ImageSharp.Processing.Processors // Tweaked to support upto 4k wide pixels and not more. 4096 / 16 is 256 thus the '-1' byte clusterSize = (byte)((width / 16) - 1); - float threshold = 0.85f; - // Using pooled 2d buffer for integer image table using (Buffer2D intImage = configuration.MemoryAllocator.Allocate2D(width, height)) { @@ -136,7 +155,7 @@ namespace SixLabors.ImageSharp.Processing.Processors sum = (long)(intImage[x2, y2] - intImage[x1, y2] - intImage[x2, y1] + intImage[x1, y1]); - if ((rgb.R + rgb.G + rgb.B) * count < sum * threshold) + if ((rgb.R + rgb.G + rgb.B) * count < sum * this.Threshold) { originalSpan[j] = this.Lower; } From 4b801a414c79d43bbb004e7511461b7ecdfe5e75 Mon Sep 17 00:00:00 2001 From: Simanto Rahman Date: Fri, 26 Oct 2018 19:22:03 -0230 Subject: [PATCH 10/22] Fixed several bugs produced during parallelism implementations --- .../AdaptiveThresholdProcessor.cs | 74 +++++++++---------- 1 file changed, 33 insertions(+), 41 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor.cs b/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor.cs index 2bed73be0..21c7f4f57 100644 --- a/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor.cs @@ -55,7 +55,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization this.Upper = upper; this.Lower = lower; - this.Threshold = threshold; + this.ThresholdLimit = threshold; } /// @@ -71,7 +71,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization /// /// Gets or sets the value for threshold limit /// - public float Threshold { get; set; } + public float ThresholdLimit { get; set; } /// protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) @@ -90,81 +90,73 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization // Using pooled 2d buffer for integer image table using (Buffer2D intImage = configuration.MemoryAllocator.Allocate2D(width, height)) + using (IMemoryOwner tmpBuffer = configuration.MemoryAllocator.Allocate(width * height)) { - var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY); + Rectangle workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY); - ParallelHelper.IterateRowsWithTempBuffer( + this.pixelOpInstance.ToRgb24(source.GetPixelSpan(), tmpBuffer.GetSpan()); + + ParallelHelper.IterateRows( workingRectangle, configuration, - (rows, memory) => + rows => { - ulong sum = 0; - - Span tmpSpan = memory.Span; - - for (int i = rows.Min; i < rows.Max; i++) + Span rgbSpan = tmpBuffer.GetSpan(); + uint sum; + for (int x = startX; x < endX; x++) { - this.pixelOpInstance.ToRgb24(source.GetPixelRowSpan(i), tmpSpan); - sum = 0; - - for (int j = startX; j < endX; j++) + for (int y = rows.Min; y < rows.Max; y++) { - ref Rgb24 rgb = ref tmpSpan[j]; - - sum += (ulong)(rgb.R + rgb.G + rgb.B); + ref Rgb24 rgb = ref rgbSpan[(width * y) + x]; + sum += (uint)(rgb.R + rgb.G + rgb.B); - if (i != 0) + if (x > 0) { - intImage[i, j] = intImage[i - 1, j] + sum; + intImage[x - startX, y - startY] = intImage[x - 1 - startX, y - startY] + sum; } else { - intImage[i, j] = sum; + intImage[x - startX, y - startY] = sum; } } } }); - ParallelHelper.IterateRowsWithTempBuffer( + ParallelHelper.IterateRows( workingRectangle, configuration, - (rows, memory) => + rows => { ushort x1, x2, y1, y2; - uint count = 0; + Span rgbSpan = tmpBuffer.GetSpan(); long sum = 0; + uint count = 0; - Span tmpSpan = memory.Span; - - for (int i = rows.Min; i < rows.Max; i++) + for (int x = startX; x < endX; x++) { - Span originalSpan = source.GetPixelRowSpan(i); - this.pixelOpInstance.ToRgb24(originalSpan, tmpSpan); - - for (int j = startX; j < endX; j++) + for (int y = rows.Min; y < rows.Max; y++) { - ref Rgb24 rgb = ref tmpSpan[j]; - - x1 = (ushort)Math.Max(i - clusterSize + 1, 0); - x2 = (ushort)Math.Min(i + clusterSize + 1, endY - 1); - y1 = (ushort)Math.Max(j - clusterSize + 1, 0); - y2 = (ushort)Math.Min(j + clusterSize + 1, endX - 1); + ref Rgb24 rgb = ref rgbSpan[(width * y) + x]; + x1 = (ushort)Math.Max(x - clusterSize + 1 - startX, 0); + x2 = (ushort)Math.Min(x + clusterSize + 1 - startX, endX - startX - 1); + y1 = (ushort)Math.Max(y - clusterSize + 1 - startY, 0); + y2 = (ushort)Math.Min(y + clusterSize + 1 - startY, endY - startY - 1); count = (uint)((x2 - x1) * (y2 - y1)); - sum = (long)(intImage[x2, y2] - intImage[x1, y2] - intImage[x2, y1] + intImage[x1, y1]); + sum = (long)(intImage[x2, y2] - intImage[x2, y1] - intImage[x1, y2] + intImage[x1, y1]); - if ((rgb.R + rgb.G + rgb.B) * count < sum * this.Threshold) + if ((rgb.R + rgb.G + rgb.B) * count <= sum * this.ThresholdLimit) { - originalSpan[j] = this.Lower; + source[x, y] = this.Lower; } else { - originalSpan[j] = this.Upper; + source[x, y] = this.Upper; } } - } + } }); } } From de721e89749f92776d1df091602fc9630d0eed3d Mon Sep 17 00:00:00 2001 From: Simanto Rahman Date: Tue, 30 Oct 2018 13:52:02 -0230 Subject: [PATCH 11/22] Algorithm behaves abnormally when applied with ParallelHelpers --- .../AdaptiveThresholdProcessor.cs | 118 +++++++++--------- 1 file changed, 57 insertions(+), 61 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor.cs b/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor.cs index 21c7f4f57..030768e69 100644 --- a/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor.cs @@ -76,7 +76,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization /// protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) { - var intersect = Rectangle.Intersect(sourceRectangle, source.Bounds()); + 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; @@ -85,79 +87,73 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization ushort width = (ushort)intersect.Width; ushort height = (ushort)intersect.Height; - // Tweaked to support upto 4k wide pixels and not more. 4096 / 16 is 256 thus the '-1' - byte clusterSize = (byte)((width / 16) - 1); + // 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 + // 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)) + using (IMemoryOwner bulkRgbBuf = 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()); + // TPixel span of the original image + Span pixelSpan = source.GetPixelSpan(); + + // RGB24 span of the converted pixel buffer + Span rgbSpan = bulkRgbBuf.GetSpan(); - ParallelHelper.IterateRows( - workingRectangle, - configuration, - rows => + // Bulk conversion to RGB24 + this.pixelOpInstance.ToRgb24(pixelSpan, rgbSpan); + + for (int x = startX; x < endX; x++) + { + ulong sum = 0; + for (int y = startY; y < endY; y++) { - Span rgbSpan = tmpBuffer.GetSpan(); - uint sum; - for (int x = startX; x < endX; x++) + ref Rgb24 rgb = ref rgbSpan[(width * y) + x]; + + sum += (ulong)(rgb.R + rgb.G + rgb.B); + + if (x != 0) + { + intImage[x - startX, y - startY] = intImage[x - startX - 1, y - startY] + sum; + } + else { - sum = 0; - for (int y = rows.Min; y < rows.Max; y++) - { - ref Rgb24 rgb = ref rgbSpan[(width * y) + x]; - sum += (uint)(rgb.R + rgb.G + rgb.B); - - if (x > 0) - { - intImage[x - startX, y - startY] = intImage[x - 1 - startX, y - startY] + sum; - } - else - { - intImage[x - startX, y - startY] = sum; - } - } + intImage[x - startX, y - startY] = sum; } - }); + } + } - ParallelHelper.IterateRows( - workingRectangle, - configuration, - rows => + ushort x1, x2, y1, y2; + uint count = 0; + + for (int x = startX; x < endX; x++) + { + long sum = 0; + for (int y = startY; y < endY; y++) { - ushort x1, x2, y1, y2; - Span rgbSpan = tmpBuffer.GetSpan(); - long sum = 0; - uint count = 0; + 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)(intImage[x2, y2] - intImage[x1, y2] - intImage[x2, y1] + intImage[x1, y1]); - for (int x = startX; x < endX; x++) + if ((rgb.R + rgb.G + rgb.B) * count <= sum * this.ThresholdLimit) { - for (int y = rows.Min; y < rows.Max; y++) - { - ref Rgb24 rgb = ref rgbSpan[(width * y) + x]; - x1 = (ushort)Math.Max(x - clusterSize + 1 - startX, 0); - x2 = (ushort)Math.Min(x + clusterSize + 1 - startX, endX - startX - 1); - y1 = (ushort)Math.Max(y - clusterSize + 1 - startY, 0); - y2 = (ushort)Math.Min(y + clusterSize + 1 - startY, endY - startY - 1); - - count = (uint)((x2 - x1) * (y2 - y1)); - - sum = (long)(intImage[x2, y2] - intImage[x2, y1] - intImage[x1, y2] + intImage[x1, y1]); - - if ((rgb.R + rgb.G + rgb.B) * count <= sum * this.ThresholdLimit) - { - source[x, y] = this.Lower; - } - else - { - source[x, y] = this.Upper; - } - } - } - }); + pixelSpan[(width * y) + x] = this.Lower; + } + else + { + pixelSpan[(width * y) + x] = this.Upper; + } + } + } } } } From d89daae13a504db2a699265e803e9bdda26629ac Mon Sep 17 00:00:00 2001 From: Simanto Rahman Date: Tue, 30 Oct 2018 19:01:56 -0230 Subject: [PATCH 12/22] Fully working implementation --- .../AdaptiveThresholdProcessor.cs | 70 +++++++++---------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor.cs b/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor.cs index 030768e69..4fb97aa9f 100644 --- a/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor.cs @@ -92,29 +92,22 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization // 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 bulkRgbBuf = configuration.MemoryAllocator.Allocate(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); - // TPixel span of the original image - Span pixelSpan = source.GetPixelSpan(); - - // RGB24 span of the converted pixel buffer - Span rgbSpan = bulkRgbBuf.GetSpan(); - - // Bulk conversion to RGB24 - this.pixelOpInstance.ToRgb24(pixelSpan, rgbSpan); + this.pixelOpInstance.ToRgb24(source.GetPixelSpan(), tmpBuffer.GetSpan()); for (int x = startX; x < endX; x++) { + Span rgbSpan = tmpBuffer.GetSpan(); ulong sum = 0; for (int y = startY; y < endY; y++) { ref Rgb24 rgb = ref rgbSpan[(width * y) + x]; - sum += (ulong)(rgb.R + rgb.G + rgb.B); - + sum += (ulong)(rgb.R + rgb.G + rgb.G); if (x != 0) { intImage[x - startX, y - startY] = intImage[x - startX - 1, y - startY] + sum; @@ -126,34 +119,41 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization } } - ushort x1, x2, y1, y2; - uint count = 0; - - for (int x = startX; x < endX; x++) - { - long sum = 0; - for (int y = startY; y < endY; y++) + ParallelHelper.IterateRows( + workingRectangle, + configuration, + rows => { - ref Rgb24 rgb = ref rgbSpan[(width * y) + x]; + Span rgbSpan = tmpBuffer.GetSpan(); + ushort x1, y1, x2, y2; + uint count = 0; + long sum = 0; - 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)(intImage[x2, y2] - intImage[x1, y2] - intImage[x2, y1] + intImage[x1, y1]); - - if ((rgb.R + rgb.G + rgb.B) * count <= sum * this.ThresholdLimit) + for (int x = startX; x < endX; x++) { - pixelSpan[(width * y) + x] = this.Lower; + for (int y = rows.Min; y < 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)(intImage[x2, y2] - intImage[x1, y2] - intImage[x2, y1] + intImage[x1, y1]); + + if ((rgb.R + rgb.G + rgb.B) * count <= sum * this.ThresholdLimit) + { + source[x, y] = this.Lower; + } + else + { + source[x, y] = this.Upper; + } + } } - else - { - pixelSpan[(width * y) + x] = this.Upper; - } - } - } + }); } } } From 2a58efb39f619577f3085850fe16ffa93effab8b Mon Sep 17 00:00:00 2001 From: Simanto Rahman Date: Wed, 31 Oct 2018 12:45:56 -0230 Subject: [PATCH 13/22] Changed naming convention for threshold limit param --- .../Processing/AdaptiveThresholdExtensions.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/ImageSharp/Processing/AdaptiveThresholdExtensions.cs b/src/ImageSharp/Processing/AdaptiveThresholdExtensions.cs index 6c8795aa5..33cf4b45b 100644 --- a/src/ImageSharp/Processing/AdaptiveThresholdExtensions.cs +++ b/src/ImageSharp/Processing/AdaptiveThresholdExtensions.cs @@ -23,12 +23,12 @@ namespace SixLabors.ImageSharp.Processing /// Applies Bradley Adaptive Threshold to the image. /// /// The image this method extends. - /// Threshold limit (0.0-1.0) to consider for binarization. + /// Threshold limit (0.0-1.0) to consider for binarization. /// The pixel format. /// The . - public static IImageProcessingContext AdaptiveThreshold(this IImageProcessingContext source, float threshold) + public static IImageProcessingContext AdaptiveThreshold(this IImageProcessingContext source, float thresholdLimit) where TPixel : struct, IPixel - => source.ApplyProcessor(new AdaptiveThresholdProcessor(threshold)); + => source.ApplyProcessor(new AdaptiveThresholdProcessor(thresholdLimit)); /// /// Applies Bradley Adaptive Threshold to the image. @@ -48,12 +48,12 @@ namespace SixLabors.ImageSharp.Processing /// The image this method extends. /// Upper (white) color for thresholding. /// Lower (black) color for thresholding - /// Threshold limit (0.0-1.0) to consider for binarization. + /// 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 threshold) + public static IImageProcessingContext AdaptiveThreshold(this IImageProcessingContext source, TPixel upper, TPixel lower, float thresholdLimit) where TPixel : struct, IPixel - => source.ApplyProcessor(new AdaptiveThresholdProcessor(upper, lower, threshold)); + => source.ApplyProcessor(new AdaptiveThresholdProcessor(upper, lower, thresholdLimit)); /// /// Applies Bradley Adaptive Threshold to the image. @@ -74,12 +74,12 @@ namespace SixLabors.ImageSharp.Processing /// The image this method extends. /// Upper (white) color for thresholding. /// Lower (black) color for thresholding - /// Threshold limit (0.0-1.0) to consider for binarization. + /// 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 threshold, Rectangle rectangle) + 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, threshold), rectangle); + => source.ApplyProcessor(new AdaptiveThresholdProcessor(upper, lower, thresholdLimit), rectangle); } } \ No newline at end of file From a8705176728a88804de6e6184723809b2a2cfae4 Mon Sep 17 00:00:00 2001 From: Simanto Rahman Date: Wed, 31 Oct 2018 12:46:26 -0230 Subject: [PATCH 14/22] Fixed a minor bug --- .../AdaptiveThresholdProcessor.cs | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor.cs b/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor.cs index 4fb97aa9f..2ad170e38 100644 --- a/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor.cs @@ -32,9 +32,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization /// /// Initializes a new instance of the class. /// - /// Threshold limit - public AdaptiveThresholdProcessor(float threshold) - : this(NamedColors.White, NamedColors.Black, threshold) + /// Threshold limit + public AdaptiveThresholdProcessor(float thresholdLimit) + : this(NamedColors.White, NamedColors.Black, thresholdLimit) { } @@ -48,14 +48,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization /// /// Color for upper threshold /// Color for lower threshold - /// Threshold limit - public AdaptiveThresholdProcessor(TPixel upper, TPixel lower, float threshold) + /// Threshold limit + public AdaptiveThresholdProcessor(TPixel upper, TPixel lower, float thresholdLimit) { this.pixelOpInstance = PixelOperations.Instance; this.Upper = upper; this.Lower = lower; - this.ThresholdLimit = threshold; + this.ThresholdLimit = thresholdLimit; } /// @@ -99,16 +99,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization this.pixelOpInstance.ToRgb24(source.GetPixelSpan(), tmpBuffer.GetSpan()); - for (int x = startX; x < endX; x++) + for (ushort x = startX; x < endX; x++) { Span rgbSpan = tmpBuffer.GetSpan(); ulong sum = 0; - for (int y = startY; y < endY; y++) + 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 != 0) + if (x - startX != 0) { intImage[x - startX, y - startY] = intImage[x - startX - 1, y - startY] + sum; } @@ -129,9 +129,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization uint count = 0; long sum = 0; - for (int x = startX; x < endX; x++) + for (ushort x = startX; x < endX; x++) { - for (int y = rows.Min; y < rows.Max; y++) + for (ushort y = (ushort)rows.Min; y < (ushort)rows.Max; y++) { ref Rgb24 rgb = ref rgbSpan[(width * y) + x]; @@ -141,7 +141,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization y2 = (ushort)Math.Min(y - startY + clusterSize + 1, height - 1); count = (uint)((x2 - x1) * (y2 - y1)); - sum = (long)(intImage[x2, y2] - intImage[x1, y2] - intImage[x2, y1] + intImage[x1, 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) { From 485098fbdb0d85a94b82c91e820b56e646e6c3af Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 2 Apr 2020 12:05:51 +0200 Subject: [PATCH 15/22] Re-add external test images --- tests/Images/External | 1 + 1 file changed, 1 insertion(+) create mode 160000 tests/Images/External diff --git a/tests/Images/External b/tests/Images/External new file mode 160000 index 000000000..fe694a393 --- /dev/null +++ b/tests/Images/External @@ -0,0 +1 @@ +Subproject commit fe694a3938bea3565071a96cb1c90c4cbc586ff9 From 225df574880a5bcc1c9a2bd9996479ac63440a55 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 2 Apr 2020 12:31:35 +0200 Subject: [PATCH 16/22] Adjustments to changes from the upstream --- .../Processing/AdaptiveThresholdExtensions.cs | 49 ++--- .../AdaptiveThresholdProcessor.cs | 149 ++++------------ .../AdaptiveThresholdProcessor{TPixel}.cs | 168 ++++++++++++++++++ 3 files changed, 220 insertions(+), 146 deletions(-) create mode 100644 src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor{TPixel}.cs diff --git a/src/ImageSharp/Processing/AdaptiveThresholdExtensions.cs b/src/ImageSharp/Processing/AdaptiveThresholdExtensions.cs index 33cf4b45b..4fc78a958 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 2ad170e38..3558a9489 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 000000000..130fc40f3 --- /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; + } + } + } + } + } +} From 4d3d0fb2f5f1104274a07e9b17df6d2a75e7bfa7 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 2 Apr 2020 15:18:53 +0200 Subject: [PATCH 17/22] Add tests for the AdaptiveThreshold processor --- .../Binarization/AdaptiveThresholdTests.cs | 77 +++++++++++++++++++ tests/ImageSharp.Tests/TestImages.cs | 3 + tests/Images/External | 2 +- tests/Images/Input/Png/Bradley01.png | 3 + tests/Images/Input/Png/Bradley02.png | 3 + 5 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 tests/ImageSharp.Tests/Processing/Binarization/AdaptiveThresholdTests.cs create mode 100644 tests/Images/Input/Png/Bradley01.png create mode 100644 tests/Images/Input/Png/Bradley02.png diff --git a/tests/ImageSharp.Tests/Processing/Binarization/AdaptiveThresholdTests.cs b/tests/ImageSharp.Tests/Processing/Binarization/AdaptiveThresholdTests.cs new file mode 100644 index 000000000..309716eb5 --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/Binarization/AdaptiveThresholdTests.cs @@ -0,0 +1,77 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Formats.Tga; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Processing.Processors.Binarization; +using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Processing.Binarization +{ + public class AdaptiveThresholdTests : BaseImageOperationsExtensionTest + { + [Fact] + public void AdaptiveThreshold_UsesDefaults_Works() + { + var expectedThresholdLimit = .85f; + Color expectedUpper = Color.White; + Color expectedLower = Color.Black; + this.operations.AdaptiveThreshold(); + AdaptiveThresholdProcessor p = this.Verify(); + Assert.Equal(expectedThresholdLimit, p.ThresholdLimit); + Assert.Equal(expectedUpper, p.Upper); + Assert.Equal(expectedLower, p.Lower); + } + + [Fact] + public void AdaptiveThreshold_SettingThresholdLimit_Works() + { + var expectedThresholdLimit = .65f; + this.operations.AdaptiveThreshold(expectedThresholdLimit); + AdaptiveThresholdProcessor p = this.Verify(); + Assert.Equal(expectedThresholdLimit, p.ThresholdLimit); + Assert.Equal(Color.White, p.Upper); + Assert.Equal(Color.Black, p.Lower); + } + + [Fact] + public void AdaptiveThreshold_SettingUpperLowerThresholds_Works() + { + Color expectedUpper = Color.HotPink; + Color expectedLower = Color.Yellow; + this.operations.AdaptiveThreshold(expectedUpper, expectedLower); + AdaptiveThresholdProcessor p = this.Verify(); + Assert.Equal(expectedUpper, p.Upper); + Assert.Equal(expectedLower, p.Lower); + } + + [Fact] + public void AdaptiveThreshold_SettingUpperLowerWithThresholdLimit_Works() + { + var expectedThresholdLimit = .77f; + Color expectedUpper = Color.HotPink; + Color expectedLower = Color.Yellow; + this.operations.AdaptiveThreshold(expectedUpper, expectedLower, expectedThresholdLimit); + AdaptiveThresholdProcessor p = this.Verify(); + Assert.Equal(expectedThresholdLimit, p.ThresholdLimit); + Assert.Equal(expectedUpper, p.Upper); + Assert.Equal(expectedLower, p.Lower); + } + + [Theory] + [WithFile(TestImages.Png.Bradley01, PixelTypes.Rgba32)] + [WithFile(TestImages.Png.Bradley02, PixelTypes.Rgba32)] + public void AdaptiveThreshold_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage()) + { + image.Mutate(img => img.AdaptiveThreshold()); + image.DebugSave(provider); + image.CompareToReferenceOutput(ImageComparer.Exact, provider); + } + } + } +} diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 272998a89..e475a7712 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -83,6 +83,9 @@ namespace SixLabors.ImageSharp.Tests public const string Ducky = "Png/ducky.png"; public const string Rainbow = "Png/rainbow.png"; + public const string Bradley01 = "Png/Bradley01.png"; + public const string Bradley02 = "Png/Bradley02.png"; + // Issue 1014: https://github.com/SixLabors/ImageSharp/issues/1014 public const string Issue1014_1 = "Png/issues/Issue_1014_1.png"; public const string Issue1014_2 = "Png/issues/Issue_1014_2.png"; diff --git a/tests/Images/External b/tests/Images/External index fe694a393..c04c8b73a 160000 --- a/tests/Images/External +++ b/tests/Images/External @@ -1 +1 @@ -Subproject commit fe694a3938bea3565071a96cb1c90c4cbc586ff9 +Subproject commit c04c8b73a99c1b198597ae640394d91ddd8e033b diff --git a/tests/Images/Input/Png/Bradley01.png b/tests/Images/Input/Png/Bradley01.png new file mode 100644 index 000000000..5af2913e6 --- /dev/null +++ b/tests/Images/Input/Png/Bradley01.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d7eddc690c9d50fcaca3b0045d225b08c2fb172ceff5eead1d476c4df0354d02 +size 25266 diff --git a/tests/Images/Input/Png/Bradley02.png b/tests/Images/Input/Png/Bradley02.png new file mode 100644 index 000000000..917bf9310 --- /dev/null +++ b/tests/Images/Input/Png/Bradley02.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a73ebf6e35d5336bdf194d5098bcbe0ad240bbd09cd357816aacb1e0e7e6a614 +size 26467 From 2bd2f7b0413083365971b1a1d61bfc402938b740 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 2 Apr 2020 17:25:46 +0200 Subject: [PATCH 18/22] Remove not needed tmp buffer --- .../AdaptiveThresholdProcessor{TPixel}.cs | 31 +++++++++---------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor{TPixel}.cs index 130fc40f3..109631ab8 100644 --- a/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor{TPixel}.cs @@ -2,8 +2,8 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.Buffers; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; @@ -58,20 +58,20 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization // 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()); - + Rgba32 rgb = default; 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]; + Span row = source.GetPixelRowSpan(y); + ref TPixel rowRef = ref MemoryMarshal.GetReference(row); + ref TPixel color = ref Unsafe.Add(ref rowRef, x); + color.ToRgba32(ref rgb); sum += (ulong)(rgb.R + rgb.G + rgb.G); if (x - startX != 0) @@ -85,7 +85,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization } } - var operation = new RowOperation(workingRectangle, source, tmpBuffer, intImage, upper, lower, thresholdLimit, clusterSize, startX, endX, startY); + var operation = new RowOperation(workingRectangle, source, intImage, upper, lower, thresholdLimit, clusterSize, startX, endX, startY); ParallelRowIterator.IterateRows( configuration, workingRectangle, @@ -97,7 +97,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization { private readonly Rectangle bounds; private readonly ImageFrame source; - private readonly IMemoryOwner tmpBuffer; private readonly Buffer2D intImage; private readonly TPixel upper; private readonly TPixel lower; @@ -111,7 +110,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization public RowOperation( Rectangle bounds, ImageFrame source, - IMemoryOwner tmpBuffer, Buffer2D intImage, TPixel upper, TPixel lower, @@ -123,7 +121,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization { this.bounds = bounds; this.source = source; - this.tmpBuffer = tmpBuffer; this.intImage = intImage; this.upper = upper; this.lower = lower; @@ -138,17 +135,17 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization [MethodImpl(InliningOptions.ShortMethod)] public void Invoke(int y) { - Span rgbSpan = this.tmpBuffer.GetSpan(); - ushort x1, y1, x2, y2; + Rgba32 rgb = default; for (ushort x = this.startX; x < this.endX; x++) { - ref Rgb24 rgb = ref rgbSpan[(this.bounds.Width * y) + x]; + TPixel pixel = this.source.PixelBuffer[x, y]; + pixel.ToRgba32(ref rgb); - 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 x1 = (ushort)Math.Max(x - this.startX - this.clusterSize + 1, 0); + var x2 = (ushort)Math.Min(x - this.startX + this.clusterSize + 1, this.bounds.Width - 1); + var y1 = (ushort)Math.Max(y - this.startY - this.clusterSize + 1, 0); + var 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); From c1fc52b676489a2f6caf58a78b06779e8961160f Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 2 Apr 2020 17:48:06 +0200 Subject: [PATCH 19/22] Changed startX and endX from ushort to int, Add test with rectangle --- .../AdaptiveThresholdProcessor{TPixel}.cs | 31 ++++++----- .../Binarization/AdaptiveThresholdTests.cs | 52 ++++++++++++++++++- tests/Images/External | 2 +- 3 files changed, 67 insertions(+), 18 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor{TPixel}.cs index 109631ab8..6daf3a8ed 100644 --- a/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor{TPixel}.cs @@ -44,14 +44,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization 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; + int startY = intersect.Y; + int endY = intersect.Bottom; + int startX = intersect.X; + int endX = intersect.Right; - ushort width = (ushort)intersect.Width; - ushort height = (ushort)intersect.Height; + 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); @@ -63,10 +62,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY); Rgba32 rgb = default; - for (ushort x = startX; x < endX; x++) + for (int x = startX; x < endX; x++) { ulong sum = 0; - for (ushort y = startY; y < endY; y++) + for (int y = startY; y < endY; y++) { Span row = source.GetPixelRowSpan(y); ref TPixel rowRef = ref MemoryMarshal.GetReference(row); @@ -101,9 +100,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization 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 int startX; + private readonly int endX; + private readonly int startY; private readonly byte clusterSize; [MethodImpl(InliningOptions.ShortMethod)] @@ -115,9 +114,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization TPixel lower, float thresholdLimit, byte clusterSize, - ushort startX, - ushort endX, - ushort startY) + int startX, + int endX, + int startY) { this.bounds = bounds; this.source = source; @@ -137,7 +136,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization { Rgba32 rgb = default; - for (ushort x = this.startX; x < this.endX; x++) + for (int x = this.startX; x < this.endX; x++) { TPixel pixel = this.source.PixelBuffer[x, y]; pixel.ToRgba32(ref rgb); diff --git a/tests/ImageSharp.Tests/Processing/Binarization/AdaptiveThresholdTests.cs b/tests/ImageSharp.Tests/Processing/Binarization/AdaptiveThresholdTests.cs index 309716eb5..f992ac35b 100644 --- a/tests/ImageSharp.Tests/Processing/Binarization/AdaptiveThresholdTests.cs +++ b/tests/ImageSharp.Tests/Processing/Binarization/AdaptiveThresholdTests.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.Formats.Tga; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Binarization; @@ -15,10 +14,15 @@ namespace SixLabors.ImageSharp.Tests.Processing.Binarization [Fact] public void AdaptiveThreshold_UsesDefaults_Works() { + // arrange var expectedThresholdLimit = .85f; Color expectedUpper = Color.White; Color expectedLower = Color.Black; + + // act this.operations.AdaptiveThreshold(); + + // assert AdaptiveThresholdProcessor p = this.Verify(); Assert.Equal(expectedThresholdLimit, p.ThresholdLimit); Assert.Equal(expectedUpper, p.Upper); @@ -28,8 +32,13 @@ namespace SixLabors.ImageSharp.Tests.Processing.Binarization [Fact] public void AdaptiveThreshold_SettingThresholdLimit_Works() { + // arrange var expectedThresholdLimit = .65f; + + // act this.operations.AdaptiveThreshold(expectedThresholdLimit); + + // assert AdaptiveThresholdProcessor p = this.Verify(); Assert.Equal(expectedThresholdLimit, p.ThresholdLimit); Assert.Equal(Color.White, p.Upper); @@ -39,9 +48,14 @@ namespace SixLabors.ImageSharp.Tests.Processing.Binarization [Fact] public void AdaptiveThreshold_SettingUpperLowerThresholds_Works() { + // arrange Color expectedUpper = Color.HotPink; Color expectedLower = Color.Yellow; + + // act this.operations.AdaptiveThreshold(expectedUpper, expectedLower); + + // assert AdaptiveThresholdProcessor p = this.Verify(); Assert.Equal(expectedUpper, p.Upper); Assert.Equal(expectedLower, p.Lower); @@ -50,16 +64,39 @@ namespace SixLabors.ImageSharp.Tests.Processing.Binarization [Fact] public void AdaptiveThreshold_SettingUpperLowerWithThresholdLimit_Works() { + // arrange var expectedThresholdLimit = .77f; Color expectedUpper = Color.HotPink; Color expectedLower = Color.Yellow; + + // act this.operations.AdaptiveThreshold(expectedUpper, expectedLower, expectedThresholdLimit); + + // assert AdaptiveThresholdProcessor p = this.Verify(); Assert.Equal(expectedThresholdLimit, p.ThresholdLimit); Assert.Equal(expectedUpper, p.Upper); Assert.Equal(expectedLower, p.Lower); } + [Fact] + public void AdaptiveThreshold_SettingUpperLowerWithThresholdLimit_WithRectangle_Works() + { + // arrange + var expectedThresholdLimit = .77f; + Color expectedUpper = Color.HotPink; + Color expectedLower = Color.Yellow; + + // act + this.operations.AdaptiveThreshold(expectedUpper, expectedLower, expectedThresholdLimit, this.rect); + + // assert + AdaptiveThresholdProcessor p = this.Verify(this.rect); + Assert.Equal(expectedThresholdLimit, p.ThresholdLimit); + Assert.Equal(expectedUpper, p.Upper); + Assert.Equal(expectedLower, p.Lower); + } + [Theory] [WithFile(TestImages.Png.Bradley01, PixelTypes.Rgba32)] [WithFile(TestImages.Png.Bradley02, PixelTypes.Rgba32)] @@ -73,5 +110,18 @@ namespace SixLabors.ImageSharp.Tests.Processing.Binarization image.CompareToReferenceOutput(ImageComparer.Exact, provider); } } + + [Theory] + [WithFile(TestImages.Png.Bradley02, PixelTypes.Rgba32)] + public void AdaptiveThreshold_WithRectangle_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage()) + { + image.Mutate(img => img.AdaptiveThreshold(Color.White, Color.Black, new Rectangle(60, 90, 200, 30))); + image.DebugSave(provider); + image.CompareToReferenceOutput(ImageComparer.Exact, provider); + } + } } } diff --git a/tests/Images/External b/tests/Images/External index c04c8b73a..6fdc6d19b 160000 --- a/tests/Images/External +++ b/tests/Images/External @@ -1 +1 @@ -Subproject commit c04c8b73a99c1b198597ae640394d91ddd8e033b +Subproject commit 6fdc6d19b101dc1c00a297d3e92257df60c413d0 From d032998b1cf50b1422f4c0cef273fbca66f4a767 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 2 Apr 2020 19:59:24 +0200 Subject: [PATCH 20/22] Using pixel row span to access the pixels --- .../Binarization/AdaptiveThresholdProcessor{TPixel}.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor{TPixel}.cs index 6daf3a8ed..7b3e10b0d 100644 --- a/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor{TPixel}.cs @@ -135,10 +135,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization public void Invoke(int y) { Rgba32 rgb = default; + Span pixelRow = this.source.GetPixelRowSpan(y); for (int x = this.startX; x < this.endX; x++) { - TPixel pixel = this.source.PixelBuffer[x, y]; + TPixel pixel = pixelRow[x]; pixel.ToRgba32(ref rgb); var x1 = (ushort)Math.Max(x - this.startX - this.clusterSize + 1, 0); From ca2ed0d68285d72800a4e6a155308aac380c79c6 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 2 Apr 2020 23:15:48 +0200 Subject: [PATCH 21/22] Review changes --- .../AdaptiveThresholdProcessor{TPixel}.cs | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor{TPixel}.cs index 7b3e10b0d..dd8833ad9 100644 --- a/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor{TPixel}.cs @@ -18,7 +18,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization where TPixel : unmanaged, IPixel { private readonly AdaptiveThresholdProcessor definition; - private readonly PixelOperations pixelOpInstance; /// /// Initializes a new instance of the class. @@ -30,7 +29,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization public AdaptiveThresholdProcessor(Configuration configuration, AdaptiveThresholdProcessor definition, Image source, Rectangle sourceRectangle) : base(configuration, source, sourceRectangle) { - this.pixelOpInstance = PixelOperations.Instance; this.definition = definition; } @@ -58,9 +56,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization // 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)) { - // Defines the rectangle section of the image to work on. - var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY); - Rgba32 rgb = default; for (int x = startX; x < endX; x++) { @@ -84,10 +79,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization } } - var operation = new RowOperation(workingRectangle, source, intImage, upper, lower, thresholdLimit, clusterSize, startX, endX, startY); + var operation = new RowOperation(intersect, source, intImage, upper, lower, thresholdLimit, clusterSize, startX, endX, startY); ParallelRowIterator.IterateRows( configuration, - workingRectangle, + intersect, in operation); } } @@ -142,10 +137,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization TPixel pixel = pixelRow[x]; pixel.ToRgba32(ref rgb); - var x1 = (ushort)Math.Max(x - this.startX - this.clusterSize + 1, 0); - var x2 = (ushort)Math.Min(x - this.startX + this.clusterSize + 1, this.bounds.Width - 1); - var y1 = (ushort)Math.Max(y - this.startY - this.clusterSize + 1, 0); - var y2 = (ushort)Math.Min(y - this.startY + this.clusterSize + 1, this.bounds.Height - 1); + var x1 = Math.Max(x - this.startX - this.clusterSize + 1, 0); + var x2 = Math.Min(x - this.startX + this.clusterSize + 1, this.bounds.Width - 1); + var y1 = Math.Max(y - this.startY - this.clusterSize + 1, 0); + var y2 = 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); From 3df1471af6c738f808d808eabee3a11a4002661b Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 3 Apr 2020 11:48:15 +0200 Subject: [PATCH 22/22] Minor formatting change --- src/ImageSharp/Processing/AdaptiveThresholdExtensions.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Processing/AdaptiveThresholdExtensions.cs b/src/ImageSharp/Processing/AdaptiveThresholdExtensions.cs index 4fc78a958..3279d96e3 100644 --- a/src/ImageSharp/Processing/AdaptiveThresholdExtensions.cs +++ b/src/ImageSharp/Processing/AdaptiveThresholdExtensions.cs @@ -53,7 +53,7 @@ namespace SixLabors.ImageSharp.Processing /// /// The image this method extends. /// Upper (white) color for thresholding. - /// Lower (black) color for thresholding + /// Lower (black) color for thresholding. /// Rectangle region to apply the processor on. /// The . public static IImageProcessingContext AdaptiveThreshold(this IImageProcessingContext source, Color upper, Color lower, Rectangle rectangle) @@ -64,7 +64,7 @@ namespace SixLabors.ImageSharp.Processing /// /// 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. /// Rectangle region to apply the processor on. /// The .