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