Browse Source

Adjustments to changes from the upstream

pull/725/head
Brian Popow 6 years ago
parent
commit
39d5a93d14
  1. 49
      src/ImageSharp/Processing/AdaptiveThresholdExtensions.cs
  2. 149
      src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor.cs
  3. 168
      src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor{TPixel}.cs

49
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
{
/// <summary>
/// Extensions to perform AdaptiveThreshold through Mutator
/// Extensions to perform AdaptiveThreshold through Mutator.
/// </summary>
public static class AdaptiveThresholdExtensions
{
@ -13,47 +14,39 @@ namespace SixLabors.ImageSharp.Processing
/// Applies Bradley Adaptive Threshold to the image.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> AdaptiveThreshold<TPixel>(this IImageProcessingContext<TPixel> source)
where TPixel : struct, IPixel<TPixel>
=> source.ApplyProcessor(new AdaptiveThresholdProcessor<TPixel>());
public static IImageProcessingContext AdaptiveThreshold(this IImageProcessingContext source)
=> source.ApplyProcessor(new AdaptiveThresholdProcessor());
/// <summary>
/// Applies Bradley Adaptive Threshold to the image.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="thresholdLimit">Threshold limit (0.0-1.0) to consider for binarization.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> AdaptiveThreshold<TPixel>(this IImageProcessingContext<TPixel> source, float thresholdLimit)
where TPixel : struct, IPixel<TPixel>
=> source.ApplyProcessor(new AdaptiveThresholdProcessor<TPixel>(thresholdLimit));
public static IImageProcessingContext AdaptiveThreshold(this IImageProcessingContext source, float thresholdLimit)
=> source.ApplyProcessor(new AdaptiveThresholdProcessor(thresholdLimit));
/// <summary>
/// Applies Bradley Adaptive Threshold to the image.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="upper">Upper (white) color for thresholding.</param>
/// <param name="lower">Lower (black) color for thresholding</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="lower">Lower (black) color for thresholding.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> AdaptiveThreshold<TPixel>(this IImageProcessingContext<TPixel> source, TPixel upper, TPixel lower)
where TPixel : struct, IPixel<TPixel>
=> source.ApplyProcessor(new AdaptiveThresholdProcessor<TPixel>(upper, lower));
public static IImageProcessingContext AdaptiveThreshold(this IImageProcessingContext source, Color upper, Color lower)
=> source.ApplyProcessor(new AdaptiveThresholdProcessor(upper, lower));
/// <summary>
/// Applies Bradley Adaptive Threshold to the image.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="upper">Upper (white) color for thresholding.</param>
/// <param name="lower">Lower (black) color for thresholding</param>
/// <param name="lower">Lower (black) color for thresholding.</param>
/// <param name="thresholdLimit">Threshold limit (0.0-1.0) to consider for binarization.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> AdaptiveThreshold<TPixel>(this IImageProcessingContext<TPixel> source, TPixel upper, TPixel lower, float thresholdLimit)
where TPixel : struct, IPixel<TPixel>
=> source.ApplyProcessor(new AdaptiveThresholdProcessor<TPixel>(upper, lower, thresholdLimit));
public static IImageProcessingContext AdaptiveThreshold(this IImageProcessingContext source, Color upper, Color lower, float thresholdLimit)
=> source.ApplyProcessor(new AdaptiveThresholdProcessor(upper, lower, thresholdLimit));
/// <summary>
/// Applies Bradley Adaptive Threshold to the image.
@ -62,11 +55,9 @@ namespace SixLabors.ImageSharp.Processing
/// <param name="upper">Upper (white) color for thresholding.</param>
/// <param name="lower">Lower (black) color for thresholding</param>
/// <param name="rectangle">Rectangle region to apply the processor on.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> AdaptiveThreshold<TPixel>(this IImageProcessingContext<TPixel> source, TPixel upper, TPixel lower, Rectangle rectangle)
where TPixel : struct, IPixel<TPixel>
=> source.ApplyProcessor(new AdaptiveThresholdProcessor<TPixel>(upper, lower), rectangle);
public static IImageProcessingContext AdaptiveThreshold(this IImageProcessingContext source, Color upper, Color lower, Rectangle rectangle)
=> source.ApplyProcessor(new AdaptiveThresholdProcessor(upper, lower), rectangle);
/// <summary>
/// Applies Bradley Adaptive Threshold to the image.
@ -76,10 +67,8 @@ namespace SixLabors.ImageSharp.Processing
/// <param name="lower">Lower (black) color for thresholding</param>
/// <param name="thresholdLimit">Threshold limit (0.0-1.0) to consider for binarization.</param>
/// <param name="rectangle">Rectangle region to apply the processor on.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> AdaptiveThreshold<TPixel>(this IImageProcessingContext<TPixel> source, TPixel upper, TPixel lower, float thresholdLimit, Rectangle rectangle)
where TPixel : struct, IPixel<TPixel>
=> source.ApplyProcessor(new AdaptiveThresholdProcessor<TPixel>(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);
}
}
}

149
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
{
/// <summary>
/// Performs Bradley Adaptive Threshold filter against an image
/// Performs Bradley Adaptive Threshold filter against an image.
/// </summary>
/// <typeparam name="TPixel">The pixel format of the image</typeparam>
internal class AdaptiveThresholdProcessor<TPixel> : ImageProcessor<TPixel>
where TPixel : struct, IPixel<TPixel>
/// <remarks>
/// Implements "Adaptive Thresholding Using the Integral Image",
/// see paper: http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.420.7883&amp;rep=rep1&amp;type=pdf
/// </remarks>
public class AdaptiveThresholdProcessor : IImageProcessor
{
private readonly PixelOperations<TPixel> pixelOpInstance;
/// <summary>
/// Initializes a new instance of the <see cref="AdaptiveThresholdProcessor{TPixel}"/> class.
/// Initializes a new instance of the <see cref="AdaptiveThresholdProcessor"/> class.
/// </summary>
public AdaptiveThresholdProcessor()
: this(NamedColors<TPixel>.White, NamedColors<TPixel>.Black, 0.85f)
: this(Color.White, Color.Black, 0.85f)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="AdaptiveThresholdProcessor{TPixel}"/> class.
/// Initializes a new instance of the <see cref="AdaptiveThresholdProcessor"/> class.
/// </summary>
/// <param name="thresholdLimit">Threshold limit</param>
/// <param name="thresholdLimit">Threshold limit.</param>
public AdaptiveThresholdProcessor(float thresholdLimit)
: this(NamedColors<TPixel>.White, NamedColors<TPixel>.Black, thresholdLimit)
: this(Color.White, Color.Black, thresholdLimit)
{
}
public AdaptiveThresholdProcessor(TPixel upper, TPixel lower)
/// <summary>
/// Initializes a new instance of the <see cref="AdaptiveThresholdProcessor"/> class.
/// </summary>
/// <param name="upper">Color for upper threshold.</param>
/// <param name="lower">Color for lower threshold.</param>
public AdaptiveThresholdProcessor(Color upper, Color lower)
: this(upper, lower, 0.85f)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="AdaptiveThresholdProcessor{TPixel}"/> class.
/// Initializes a new instance of the <see cref="AdaptiveThresholdProcessor"/> class.
/// </summary>
/// <param name="upper">Color for upper threshold</param>
/// <param name="lower">Color for lower threshold</param>
/// <param name="thresholdLimit">Threshold limit</param>
public AdaptiveThresholdProcessor(TPixel upper, TPixel lower, float thresholdLimit)
/// <param name="upper">Color for upper threshold.</param>
/// <param name="lower">Color for lower threshold.</param>
/// <param name="thresholdLimit">Threshold limit.</param>
public AdaptiveThresholdProcessor(Color upper, Color lower, float thresholdLimit)
{
this.pixelOpInstance = PixelOperations<TPixel>.Instance;
this.Upper = upper;
this.Lower = lower;
this.ThresholdLimit = thresholdLimit;
}
/// <summary>
/// Gets or sets upper color limit for thresholding
/// Gets or sets upper color limit for thresholding.
/// </summary>
public TPixel Upper { get; set; }
public Color Upper { get; set; }
/// <summary>
/// Gets or sets lower color limit for threshold
/// Gets or sets lower color limit for threshold.
/// </summary>
public TPixel Lower { get; set; }
public Color Lower { get; set; }
/// <summary>
/// Gets or sets the value for threshold limit
/// Gets or sets the value for threshold limit.
/// </summary>
public float ThresholdLimit { get; set; }
/// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> 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<ulong> intImage = configuration.MemoryAllocator.Allocate2D<ulong>(width, height))
using (IMemoryOwner<Rgb24> tmpBuffer = configuration.MemoryAllocator.Allocate<Rgb24>(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<Rgb24> 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<Rgb24> 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;
}
}
}
});
}
}
/// <inheritdoc />
public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Configuration configuration, Image<TPixel> source, Rectangle sourceRectangle)
where TPixel : unmanaged, IPixel<TPixel>
=> new AdaptiveThresholdProcessor<TPixel>(configuration, this, source, sourceRectangle);
}
}
}

168
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
{
/// <summary>
/// Performs Bradley Adaptive Threshold filter against an image.
/// </summary>
internal class AdaptiveThresholdProcessor<TPixel> : ImageProcessor<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly AdaptiveThresholdProcessor definition;
private readonly PixelOperations<TPixel> pixelOpInstance;
/// <summary>
/// Initializes a new instance of the <see cref="AdaptiveThresholdProcessor{TPixel}"/> class.
/// </summary>
/// <param name="configuration">The configuration which allows altering default behaviour or extending the library.</param>
/// <param name="definition">The <see cref="AdaptiveThresholdProcessor"/> defining the processor parameters.</param>
/// <param name="source">The source <see cref="Image{TPixel}"/> for the current processor instance.</param>
/// <param name="sourceRectangle">The source area to process for the current processor instance.</param>
public AdaptiveThresholdProcessor(Configuration configuration, AdaptiveThresholdProcessor definition, Image<TPixel> source, Rectangle sourceRectangle)
: base(configuration, source, sourceRectangle)
{
this.pixelOpInstance = PixelOperations<TPixel>.Instance;
this.definition = definition;
}
/// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source)
{
var intersect = Rectangle.Intersect(this.SourceRectangle, source.Bounds());
Configuration configuration = this.Configuration;
TPixel upper = this.definition.Upper.ToPixel<TPixel>();
TPixel lower = this.definition.Lower.ToPixel<TPixel>();
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<ulong> intImage = this.Configuration.MemoryAllocator.Allocate2D<ulong>(width, height))
using (IMemoryOwner<Rgb24> tmpBuffer = this.Configuration.MemoryAllocator.Allocate<Rgb24>(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<Rgb24> 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<TPixel> source;
private readonly IMemoryOwner<Rgb24> tmpBuffer;
private readonly Buffer2D<ulong> 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<TPixel> source,
IMemoryOwner<Rgb24> tmpBuffer,
Buffer2D<ulong> 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;
}
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(int y)
{
Span<Rgb24> 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;
}
}
}
}
}
}
Loading…
Cancel
Save