From e1d39de2a6227a00a2406ef92c08a2ef2677c7c5 Mon Sep 17 00:00:00 2001 From: Simanto Rahman Date: Fri, 5 Oct 2018 00:41:07 -0230 Subject: [PATCH 01/25] 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 0000000000..9a6d63342f --- /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 0000000000..b14de6679f --- /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 152b8e680d584bec691e56c68f3100137c8a0e3c Mon Sep 17 00:00:00 2001 From: Simanto Rahman Date: Fri, 5 Oct 2018 12:35:15 -0230 Subject: [PATCH 02/25] # 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 b14de6679f..46e2e67c0d 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 95a7b0db1a127c728ff33d80c708392184d5c420 Mon Sep 17 00:00:00 2001 From: SimantoR Date: Sat, 13 Oct 2018 04:01:48 -0230 Subject: [PATCH 03/25] 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 46e2e67c0d..898e1f8fd7 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 ee90e5f322..5f3cbd839f 160000 --- a/tests/Images/External +++ b/tests/Images/External @@ -1 +1 @@ -Subproject commit ee90e5f32218027744b5d40058b587cc1047b76f +Subproject commit 5f3cbd839fbbffae615d294d1dabafdcabc64cf9 From 2b6d93aabe3b958b6376c6ef598b056ec5d5e443 Mon Sep 17 00:00:00 2001 From: Simanto Rahman Date: Wed, 24 Oct 2018 13:38:07 -0230 Subject: [PATCH 04/25] 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 898e1f8fd7..baecdf7f0d 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 bb5cc29ba2d8d993e10de6c528c90504eb03e228 Mon Sep 17 00:00:00 2001 From: Simanto Rahman Date: Wed, 24 Oct 2018 13:42:10 -0230 Subject: [PATCH 05/25] 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 baecdf7f0d..50dfdfd9e8 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 d8f3b397bb063b3695fe7dbe3e65643ed52d289d Mon Sep 17 00:00:00 2001 From: Simanto Rahman Date: Wed, 24 Oct 2018 13:57:29 -0230 Subject: [PATCH 06/25] 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 50dfdfd9e8..d13b84b8a4 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 8978bc34749b5a335b5aab51658de6a5966c05e7 Mon Sep 17 00:00:00 2001 From: Simanto Rahman Date: Wed, 24 Oct 2018 14:14:20 -0230 Subject: [PATCH 07/25] 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 d13b84b8a4..b6748ce002 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 6805f6d277eb2be7cd40acaab77185ce1ef78de5 Mon Sep 17 00:00:00 2001 From: Simanto Rahman Date: Thu, 25 Oct 2018 12:13:48 -0230 Subject: [PATCH 08/25] 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 b6748ce002..0602777e35 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 b0541025b2804755edc7253aba3cdadf252d6fde Mon Sep 17 00:00:00 2001 From: Simanto Rahman Date: Thu, 25 Oct 2018 12:50:40 -0230 Subject: [PATCH 09/25] 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 9a6d63342f..6c8795aa5d 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 0602777e35..2bed73be00 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 319fc9559ca9a7773aea2659322626f69029236b Mon Sep 17 00:00:00 2001 From: Simanto Rahman Date: Fri, 26 Oct 2018 19:22:03 -0230 Subject: [PATCH 10/25] 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 2bed73be00..21c7f4f57c 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 a239e60885030c15d3fffa505415657c159f5b4d Mon Sep 17 00:00:00 2001 From: Simanto Rahman Date: Tue, 30 Oct 2018 13:52:02 -0230 Subject: [PATCH 11/25] 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 21c7f4f57c..030768e69b 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 1f52c9d77c83ec06edb3a4afda5c4c85a9b30744 Mon Sep 17 00:00:00 2001 From: Simanto Rahman Date: Tue, 30 Oct 2018 19:01:56 -0230 Subject: [PATCH 12/25] 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 030768e69b..4fb97aa9f8 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 31e5d8d35d3153906abaf30ff499282a05850a8a Mon Sep 17 00:00:00 2001 From: Simanto Rahman Date: Wed, 31 Oct 2018 12:45:56 -0230 Subject: [PATCH 13/25] 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 6c8795aa5d..33cf4b45be 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 8610f5c6b4f7f91e5d2a6f884361be03f6e3a246 Mon Sep 17 00:00:00 2001 From: Simanto Rahman Date: Wed, 31 Oct 2018 12:46:26 -0230 Subject: [PATCH 14/25] 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 4fb97aa9f8..2ad170e380 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 a011640daa3f5e82e605ba5e4958ebf0c63f2dc0 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 31 Mar 2020 19:11:50 +0200 Subject: [PATCH 15/25] Reading paletted rle tga will not ignore alpha even if alpha bits is 0 --- src/ImageSharp/Formats/Tga/TgaDecoderCore.cs | 20 +++----------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs b/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs index a4e9bdcd75..7753b916d3 100644 --- a/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs +++ b/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs @@ -321,7 +321,6 @@ namespace SixLabors.ImageSharp.Formats.Tga using (IMemoryOwner buffer = this.memoryAllocator.Allocate(width * height * bytesPerPixel, AllocationOptions.Clean)) { TPixel color = default; - var alphaBits = this.tgaMetadata.AlphaChannelBits; Span bufferSpan = buffer.GetSpan(); this.UncompressRle(width, height, bufferSpan, bytesPerPixel: 1); @@ -339,30 +338,17 @@ namespace SixLabors.ImageSharp.Formats.Tga color.FromL8(Unsafe.As(ref palette[bufferSpan[idx] * colorMapPixelSizeInBytes])); break; case 2: - Bgra5551 bgra = Unsafe.As(ref palette[bufferSpan[idx] * colorMapPixelSizeInBytes]); - if (!this.hasAlpha) - { - // Set alpha value to 1, to treat it as opaque for Bgra5551. - bgra.PackedValue = (ushort)(bgra.PackedValue | 0x8000); - } + // Set alpha value to 1, to treat it as opaque for Bgra5551. + bgra.PackedValue = (ushort)(bgra.PackedValue | 0x8000); color.FromBgra5551(bgra); break; case 3: color.FromBgr24(Unsafe.As(ref palette[bufferSpan[idx] * colorMapPixelSizeInBytes])); break; case 4: - if (this.hasAlpha) - { - color.FromBgra32(Unsafe.As(ref palette[bufferSpan[idx] * colorMapPixelSizeInBytes])); - } - else - { - var alpha = alphaBits == 0 ? byte.MaxValue : bufferSpan[idx + 3]; - color.FromBgra32(new Bgra32(bufferSpan[idx + 2], bufferSpan[idx + 1], bufferSpan[idx], (byte)alpha)); - } - + color.FromBgra32(Unsafe.As(ref palette[bufferSpan[idx] * colorMapPixelSizeInBytes])); break; } From 96fc00d28f44a3a394918d21b7e80e1c22445111 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 31 Mar 2020 19:14:36 +0200 Subject: [PATCH 16/25] Add more tga test cases for paletted rle images --- .../Formats/Tga/TgaDecoderTests.cs | 97 ++++++++++++++++++ tests/ImageSharp.Tests/TestImages.cs | 10 ++ tests/Images/Input/Tga/indexed_a_rle_LL.tga | Bin 0 -> 31665 bytes tests/Images/Input/Tga/indexed_a_rle_LR.tga | Bin 0 -> 31776 bytes tests/Images/Input/Tga/indexed_a_rle_UL.tga | Bin 0 -> 31765 bytes tests/Images/Input/Tga/indexed_a_rle_UR.tga | Bin 0 -> 31666 bytes tests/Images/Input/Tga/indexed_rle_LL.tga | Bin 0 -> 30549 bytes tests/Images/Input/Tga/indexed_rle_LR.tga | Bin 0 -> 30610 bytes tests/Images/Input/Tga/indexed_rle_UL.tga | Bin 0 -> 30640 bytes tests/Images/Input/Tga/indexed_rle_UR.tga | Bin 0 -> 30500 bytes 10 files changed, 107 insertions(+) create mode 100644 tests/Images/Input/Tga/indexed_a_rle_LL.tga create mode 100644 tests/Images/Input/Tga/indexed_a_rle_LR.tga create mode 100644 tests/Images/Input/Tga/indexed_a_rle_UL.tga create mode 100644 tests/Images/Input/Tga/indexed_a_rle_UR.tga create mode 100644 tests/Images/Input/Tga/indexed_rle_LL.tga create mode 100644 tests/Images/Input/Tga/indexed_rle_LR.tga create mode 100644 tests/Images/Input/Tga/indexed_rle_UL.tga create mode 100644 tests/Images/Input/Tga/indexed_rle_UR.tga diff --git a/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs index f932f994dc..840bb55f20 100644 --- a/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs @@ -6,6 +6,7 @@ using Microsoft.DotNet.RemoteExecutor; using SixLabors.ImageSharp.Formats.Tga; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Tests.TestUtilities; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using Xunit; @@ -499,6 +500,54 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga } } + [Theory] + [WithFile(Bit32PalRleTopLeft, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_RLE_Paletted_WithTopLeftOrigin_32Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) + { + image.DebugSave(provider); + TgaTestUtils.CompareWithReferenceDecoder(provider, image); + } + } + + [Theory] + [WithFile(Bit32PalRleBottomLeft, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_RLE_Paletted_WithBottomLeftOrigin_32Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) + { + image.DebugSave(provider); + TgaTestUtils.CompareWithReferenceDecoder(provider, image); + } + } + + [Theory] + [WithFile(Bit32PalRleTopRight, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_RLE_WithTopRightOrigin_32Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) + { + image.DebugSave(provider); + TgaTestUtils.CompareWithReferenceDecoder(provider, image); + } + } + + [Theory] + [WithFile(Bit32PalRleBottomRight, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_RLE_Paletted_WithBottomRightOrigin_32Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) + { + image.DebugSave(provider); + TgaTestUtils.CompareWithReferenceDecoder(provider, image); + } + } + [Theory] [WithFile(Bit16PalBottomLeft, PixelTypes.Rgba32)] public void TgaDecoder_CanDecode_WithPaletteBottomLeftOrigin_16Bit(TestImageProvider provider) @@ -559,6 +608,54 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga } } + [Theory] + [WithFile(Bit24PalRleTopLeft, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_RLE_WithPaletteTopLeftOrigin_24Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) + { + image.DebugSave(provider); + TgaTestUtils.CompareWithReferenceDecoder(provider, image); + } + } + + [Theory] + [WithFile(Bit24PalRleTopRight, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_RLE_WithPaletteTopRightOrigin_24Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) + { + image.DebugSave(provider); + TgaTestUtils.CompareWithReferenceDecoder(provider, image); + } + } + + [Theory] + [WithFile(Bit24PalRleBottomLeft, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_RLE_WithPaletteBottomLeftOrigin_24Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) + { + image.DebugSave(provider); + TgaTestUtils.CompareWithReferenceDecoder(provider, image); + } + } + + [Theory] + [WithFile(Bit24PalRleBottomRight, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_RLE_WithPaletteBottomRightOrigin_24Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) + { + image.DebugSave(provider); + TgaTestUtils.CompareWithReferenceDecoder(provider, image); + } + } + [Theory] [WithFile(Bit32PalTopLeft, PixelTypes.Rgba32)] public void TgaDecoder_CanDecode_WithPalette_32Bit(TestImageProvider provider) diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 272998a896..53a27eb310 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -417,6 +417,11 @@ namespace SixLabors.ImageSharp.Tests public const string Bit24PalBottomLeft = "Tga/targa_24bit_pal.tga"; public const string Bit24PalBottomRight = "Tga/indexed_LR.tga"; + public const string Bit24PalRleTopLeft = "Tga/indexed_rle_UL.tga"; + public const string Bit24PalRleBottomLeft = "Tga/indexed_rle_LL.tga"; + public const string Bit24PalRleTopRight = "Tga/indexed_rle_UR.tga"; + public const string Bit24PalRleBottomRight = "Tga/indexed_rle_LR.tga"; + public const string Bit32TopLeft = "Tga/rgb_a_UL.tga"; public const string Bit32BottomLeft = "Tga/targa_32bit.tga"; public const string Bit32TopRight = "Tga/rgb_a_UR.tga"; @@ -432,6 +437,11 @@ namespace SixLabors.ImageSharp.Tests public const string Bit32RleBottomRight = "Tga/rgb_a_rle_LR.tga"; public const string Bit32RleBottomLeft = "Tga/targa_32bit_rle.tga"; + public const string Bit32PalRleTopLeft = "Tga/indexed_a_rle_UL.tga"; + public const string Bit32PalRleBottomLeft = "Tga/indexed_a_rle_LL.tga"; + public const string Bit32PalRleTopRight = "Tga/indexed_a_rle_UR.tga"; + public const string Bit32PalRleBottomRight = "Tga/indexed_a_rle_LR.tga"; + public const string NoAlphaBits16Bit = "Tga/16bit_noalphabits.tga"; public const string NoAlphaBits16BitRle = "Tga/16bit_rle_noalphabits.tga"; public const string NoAlphaBits32Bit = "Tga/32bit_no_alphabits.tga"; diff --git a/tests/Images/Input/Tga/indexed_a_rle_LL.tga b/tests/Images/Input/Tga/indexed_a_rle_LL.tga new file mode 100644 index 0000000000000000000000000000000000000000..519e9322d744a1f9a2b83517fb033541d091f281 GIT binary patch literal 31665 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≤fS<`2C{QM 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 N{GO}d|L&_2{})3M%2@ya literal 0 HcmV?d00001 diff --git a/tests/Images/Input/Tga/indexed_a_rle_UL.tga b/tests/Images/Input/Tga/indexed_a_rle_UL.tga new file mode 100644 index 0000000000000000000000000000000000000000..755528010040bfd902f5a7ea5286fe0c05221fe0 GIT binary patch literal 31765 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 literal 0 HcmV?d00001 diff --git a/tests/Images/Input/Tga/indexed_a_rle_UR.tga b/tests/Images/Input/Tga/indexed_a_rle_UR.tga new file mode 100644 index 0000000000000000000000000000000000000000..3d0cee36156f7890e62aba1bbd7f8ae387c3a5ff GIT binary patch literal 31666 zcmaLA3wT^tdG9@Yq|xYR$GI2|xzL2m@d-YJ77C@Mr92e)NZ}Odi5!w9Z7Drz3Y?UX zQpkZO9ZSAnWLtK8i*s?D3n3Adm=ZZcC}o%&dYYLi2z8PnLMYjp!AxL0X0}IL@?PKX zzt$ef&UZl%(RgO|df)Y~_xfM&T1t3biA2J?RR7UW=Wp^~lSuOacF!a`+Dv)pR&-tz~)Z~owY?=^q;{`Z+b`p^f=2S5BFv%qJk`TvLi z_)7D~AN^xvQm0LnGUlTn`)}qGSN*BE>gqnz_sKP8{PnLsYn>U*1&y3l)Ws}*ob+bAB z7xQNGwk>As;5HNe^(kZiDl*N#4$Zb5+fD6nPnhjH2Tk+Fzzpu%VRj7dG7C2vvun?g zsea)VGjzvpvwQCzvuEEO#{AuBbI1O@X6_5~#@uYozJvSC{zC`N!MpA>(U*)lbkAMp z?!))MWMuBS?_P87k;CTr#^dI`2acE{58e+$3+BOxA2JVp{cGmM-M)GF(XX3@Z42h> z!;hF>@A|cQ^qa%xmwR6^-}u%y&Foz-ntwj}Eo1IDZNByG$IQ|1eA|5c@qaP@GV&es z_!A?>+_PYgefJ3yJw9rp`%jzE?|s*dJ^4Lz^6R1b-uIt0PiFtsEIeY&zy9F+Ci~P6 zjQLh%azA{^oX-Bx{O|wsL$mNNk@?kV-Td(1|IL^m=FGnro;GH5!Q>|j=JfZBF@eris?Fl&A~J7Y|7 z!OXn$Gh=3_&CmQ7jd^j#oG$U}<(K%*f-(Nj%u8k8oc3Qfe&uEJ^3TgADwj?9c*U3s z|9>$wm0$ean4kaL{JeVHEWBjQ@n8PJoc_hHOjNCzU;OG7bF#|1|LPa!mD(@)@U;2m ziC-CW;uZ6&dd-;nFU{#+o;J0UC(P-3-JJM!-P9W=jrleIZ!}EUXquC!er>{EM<_RX z+*mNbZU$!IRA_>^Q|5GY-kfImQ(@CI=jTimE||H6(3tttCOnO=$m+H2!xChU(og3>WFoUI>-Ry1cu z&3GkR`qtVg)9eV#<7Q+g9E>``YHq|#H}^z?mkvj%pzfP$a7WY~&N9BnJ&T43?|=sH z)-zFO7?@FB=FP!qc>|7eX2w1-X2P{mXTX!888@|^QMx(9V16?kg2m~ZWDzo* zur&YcFx%K0T>$fXDQS+FS`?a^shhn~cK|6%NK;7%_O<0# zZ1LG_a|d(=qEy(ZmTS#*Q6k*$7Dmf5GB(@1gSXZO{6AXO68%)3xz_haD{DDBpa}S3 z(=TMlOsUH1b%n72eIDxyrt}3(kr@Otxlxm?YIgm|(}#zwY4%6S+6??I(~{^%)2Rn{ zM(4C>nr2P!A<>m)!U|+eG-Ki93`^3(AXCx!)735$wcQ_-H}ua*ntFBG`pj;eZ{;oQHLq<;Y6?x=35hi$qv3Zf-USL z3rTHx)R*%@_~d7)V&>qY7{Q*C?`x)(%{)yJdRW2PRjS9vH(hrZTVHjxe{ zcm?5sC==|6I_viPSmD~JqlQV1&V+YGUXyROm9u6t(`cU@LC<$Y>4sLsez7xN<6P~i zh{iO>Ow}&0T?WL^5hBVF(?I2$mI!OyAGHM&rZI>vxJg@%?5E<@ZB0{< zaWP^!tipVKXOybv-MleU5kd984gK@ol*cjDdfu(9mN}&hKZR`7^hM9I&?CY!vm?&<*3M)WCZr~J_Nnw7yz z+Y7IrdWH`|Il{8MhNc&jm&Qlvm8@5j${H3t6T;~+bjqJ$g=cWW7us3mW9FxuQBdH6 zNgl}8w?-Yl{J2k`N;dt*H;~AT8N9U3OuE%~?y-o=>4`E9$N}sSb}8 zSi3Hbsh?bJD@s#5He#wc5o>LQ#yV)unp|N<-o9BuDchUap22Z1Y6?7tOyd|o&0R~1 z&5}92&>WG=T}w)XRp6rKvuX^4{fx7E(?kHNnB%2iv)>q{th*xKI|Ch&C+!ThlDJnU zh2)MIxe2@r?}>cTepn3j#YS~{%#12tp6NbXY!%3*V5_%Tl;TQ*tWR$brjXNa!az{X zRJ=x$$Ydig1d5n$8)^Y-%j$CG#1%`A*`5pYPETr)wK)xvYQll!)qw`cschgaXypvP zxalUcsN7CV65-vX8#19WJV?&u(XX};KXVTj;by0gSW%G{D{YNQIUn@ZTFUt`BV&9^ zmLXvgYMm^z$)Jg9wl@kegP{fF52+eS-E6pn^%qBH@>9Pr>d>&GNIOi{rqCE3xs#}q zi7tq_$rjMwSPo)`FAPMW^SfvGE*Inmoj3pG254-<1muOXE>gy!b>%a6nV1^ zUe4m=2p+Y-XVrcj*a701%?StRBD9^7iy{bc zf7H&pA2Vq*(i-+o1g$J+1``%5*>V_#O1C?3yILCMDEDE3Vvkg;aU?c|0wU~=MTjDh z-CK=znu863ole+>D#d3+D2kq zrkP;eP9pUvKeOQxG~YLVqhA@llP^q_`3`1%_C&GtTBU*iCrp?G`FlI_&6!;`nRSoi zd!jn(bd1Gns+>`_QsS#A8w+adS$-u5q-mZZW9SGge0!4RO`0Y;K;_~FQbf#308C*vOS^p`#@phC1@Y77?J?0=RM`ZNh;?%qhwu-$WkW*tyu}Do(!tk zYg=H=_Ix*IA~XMtJ(U?_cCHptb{5*OW3!RaguC1nm8*sLLd=Dxtkn*=t^` zb}`$TpgfKg_n|ZRy4Lh+fyy&6nyZaR9e7iWoV>52qE}fYkqXMQejRC|QV>eke8iVh zL6IXp$L)@o!_H0FJQ+$wxEQY_s$eB8=-8=haMOr_DO4 z=B7o(E>dL|QZfw@QPhSdrK4K#Dj70r)!CYi=wQiiHea&<#PWI>WhlnEoeZL@2_b!D zg2WUJ!6@;r*4vC#hDeB_`=S;2reRY<^Q>`trXCCJ2=UBZkj&`15!>E-@qrN$mJIloqMK6i>7C{aBU zT?kvlXjE)H)GBt>c__;)ts2?2Yt7JR76H+;%U0^*2g;pj^sx0i83>sGf6Fr2a;Aj; zT_zch+mZE~gg2;FOb~8o@$Sd3YBuo7&nyD&32nZuf6kD2Bm>{BYRo|m6D3cb@Pm7z zo;annDzrE101YaY8(QH81~kp2X84Cq9aO@v)KWV-WG7*H0#stsZ*GeoWObZa#^3>Q@Z0hie+>Rs|Jt#`s?coHZ^k|c+es=z4!AcKao#44dSQ4eu3 zihtS~J%qYeOND%4rb-IbS##f#(@i$3s?+1_G=~o*CKV;ANg*aN_EHF%fdt#QlUe~c zFB&jKLf(!tWE(?>uUyRJSKxD1)wtQqz_&yXBmGR^8T>{Uvv7~Xbvu?&G^*9zU`n&W zU}Qzp0icO92$k5B`qI81<{bt5zeOsu!2wanLo8ncpJhJF*#(B zM|`Z%*c=Tw2wq2lAjqJ<~i8r4+HW=1YK6?4@OgS|@~z zLu7J^@J=c)UvaKW^9<04akDuhRHDjxf7Y!k;9ot{j4`J%f2XD)bMo^{$z=i)0fZ%) z0x~=UaiZb_naBZ7ItkSuj1slXm^Xu;#Gt%0l;fn4lvT&ToKXSf6w zD`9`OMZLC==3mt0e)Et5js0lM4`ysFZ7HiplhYHLJIn})zOzLG>g|mg z09ib*Ba7lOQt_qwRx5HMWXW0Bv5`G*fn=D+IR|fk~Y5qJ5o( zsRI52&L&yzuW6YlG4;G5A6~@Y%3GrqHZ>Y41!06)uvb6^xM*iK<+jnGDr7jQWy)S% z8sTcZ9N#bE*;_KSQ}0L;PAQKbv0&({N-au+SjXdKs6A_|7YlNU=p=RyU6#uk`J%`f zn{<;!gz^fjl{1TpS;2;7yJI2%SKzbKUFlNYF1tJw;y7!*&qgccq+LOcF_=m{+7hi$ z;sJ}%B65sh)@$Aayd_N;%~cy!3xBa^Vm(q870gc7hzYnh-b7lOw!BB3thHIUbW1kw z=4wBRddk!rZvNRaA`7-g%jHrFah@52DK*T5{FD-PS)h-dn?js+YFa_wV}GS1Bn`8Z zOS`Hp3s9zQ9I6Tu8&=y9t357n9VBpt*387uuz>;3Ojr{pHrshEf;lqO0#oR*@xGX!cjDb`lo^1ua|riC2@nv>bO z#n7`8%-+Hi4g>dL!8*zb?54e00cy^Gg&i3j^|g2+929?oULaGX%V1> zaRIb$RWuRiNk+@mr+`$c%_}7OH47XU?Kq=|i7c+Mk2xk84_+C&Mabh4y>SXBo6o8Wo(#zJ?oYNbVnA7^wx6s`_yoE2P7Kv`P!c zk!b9Uv_~tn0Q+*WJ!hirc7sve*s>2<)WK=1(j}Z5JCKJl^Qm%x;aPpj)OwbHTD1VM zU$MkMz8b_(B;EvBguYS&#VkQj`T@9W=PR>3X}Qv3GKfIomXHbt$W{#I$u|M_nL@0r zq|nE19o8voDbk*NsAr3 z|Gy}4D;FTs6jCsfuSaK0GaZE1f@^~1s|3?2cAg!Nf^R#BuR=8LS8x;y&KQ>%#@o3~ zy0h`r{n0Y(Ia(bB08wo5?3q)E53}wMK3{T#B$^@H$c?3boF|ch+ z#?E3Af6zl4Fhma|fJ9f7?b(xKGq9nc*cCT}Sh8-r9s(e_4Kvm@mY)WC?2ui+4G^mk zrc$sTe_h3IuYeh>ow%};@_Gdc>{9H=@{PvFJN`ED3*4twJ`!0L=r@@gwc|JRgnEmQy8uFT&OD{ zK$`}HRQ3g1N(|}lMN`V+{u5zJEajoST>0))%MfzdAM5L&6u~hF25twHVKkTE*#eWb zJ4ot~Aa8eZ65^d6&UTQqGVN}hSC2KAMdB0d z=9i#ZIT8!{WX(RDqALjqFl0jk=>#4?zzR>rB5$>46orN$sk9G>Hv%dQI9v7Q^{O^* zEkdRs@-C1t*228_Yy_)4Q>w`QvDYqdf@xqTpRqceby@py`CKtLMC`zqGaF1v z2(DLSdW$G<(!+_bkt`=W7j}>OtJG zm%<83{hWg?0s_*YVjU``L1=)r+B8$|Y{vaaA{a-sTJHi=usTJ25uRp_ zi|{Gt0<6y5Cgh!BgqBd4hrk-$4#Eo>+06hxSC8jwTD8bqme zoZV=H1pj0)EI!mG2N?Im767ICEPB&hRN(Xd~JP1j6Fnxn5;^tR>Fii8beR7>lR_Y8gY z4piuNT4whaNLZ-hz~+`(;Fy%EgE2LJ$-Pae-2|qZe$H7*7WCNXDM2!F%~I|>y3%1s zECM5TRHpzE%7mz7qk>t>gO!Ew#)>Y03T;JCt^1N{;~04@J1B!Eo)ahI6XhC=OltFp zMQ0J%utleNw!W+jdDw6n72aH*mdH3$jp?0B6}^X{$B@ezn>& z)b@if$&*FI$%9MTGeOD*XE4@yV{QugBbbDxQWF3;wtaakZKJhO(E$K=G#k}QUCkw` zBeg=BAPb3Wf=3`^>q3O@p0`*=0B>1A&LX}9OMGsuI}&naQ9Y$Nnq1BR6;t%FgLHtU z<+o&m(b{c6suQr{CQf4+9!A;1vpFK8z-acc!dW##9zc0#_=2+SmM0k_ zCt4Ov4ez<_nm@eaO_!Y4lTKy4-p=J0T=<5!f8ZlmG8m(@HGOr{B*@PJi%a-98n-9` zBAR0JcDrmHmN(6`FQ89Wj2<)o#5i56dadGfh4t^XSd7J>QCY&FsiTQGPzgz1gm)+y z%~I6ZK0ab4J34U)#d4$4rVm__-s`oeF8ss(vK-0bsJ+a<$g~Tt@`#v}c0V6jb}S1F z5-QwN3AZ|!-|OJ2SKQ`0Eo-bQY;7eThb zJQ_$hMTsl+dTkd1Mq+}3y>iM?`!DZ)#7mxcZS8=4UU32`s40m+;uL{z`2o!>#ne78 zxksK+ZZ|=$nvS9A3;)?muDGSS&kjK>59?*WN+jxy2aq~X)QO-s?)SRhT?w1j*{Py0 zaFiVI_m+R#^Da8DmxsLyL4G|B1>(!&vDFGh=7{+uF5B<*ybd@=rkCYyT395`5zqTD zCCv}KHas!wx0#^ngfr%|MTdQjPGdlc;e%fC(b;2}kq#sY%&k3VyBxu&41nt; z!-+O6$_-7yaaw}tRN~5mUeAYG2qjhsyzkcY3D4`=IiwLRYthCtVNvZbsp!b5g(zHc z(Cb}6cFR78KLH!~5L>g2nD2UtFYnSA+K5)+Or9snkwoH>T`)jCiS38H-a(uU%(09# zPvC{*xyQ!5#C1D0fZbj8H(>-=ENW~WiL_Qe#S7oc0PlzxAzPvCjg|^FzsXPh?|F$Y z?9lTO&TN>CpA`Ze(R#d!p0rMIgdF;gQ3#uPr+)MNiTI!l?VGBS6`CML^1>-J1S6okjZR~#yu}}FawCNNh4Tt`E7A)gL_=UGb``*dWa5A z1?+BFx=}Mi>FQgeA#X+Jn?Ll$y+@1HusSum=gS{>WBN|7V+9QZri=nJuw`Uwn_wqd zt-yHTxWtiry_E-j=V;W#gIIm#TKW3sXouJK`j6ku7H?a9%kQQScpaA@lJP0p*%(i( zWqk{DsS$wgc3_V%Xawe7uj{RLiY~}?TLrM>8=InSUb5#M53AnW6eVXry?nptG7)vn zrzmtLwFNii0h%(T7;1yo$m|h+QTtA5ZP|(oP(LS(1Qx{ZOQHGY$lz*kD&I6RH778(+uFHTPLQ$;kyg zS*uX!+8k~2IxoD0! zQ6dvZrNc$0ocQ=1UgzbJ)phm6Czz0;y!@ zt}eBe`GD89a)YhdP{lKFsx13%)wo580x9tk$Uf?fQyuYMgNl&kb!5oXyirDxwOsD< zykn^yUgGjh^q)xM!&Y+%?6*1?$5`loukHPnNcEd`sao(@9tR+&tDiD%{2hC|&bP(? zR#_>G`P3*JjpqF?nTJ+AHUe}SR%IxT4!q^t)1|aXYKF@}VwnbhcwcAVXY*o~1 zZJ%Z6A+J-$D96aZ+T`2GfBq1K{VHU(p|tPA?bM23(3qaBm?Y0^NytBXdiH!+PI3gY zj`!^Lybswa$(N~(eHWzY)F@Yd+SXMsCUH7#jjCav#0!?~Bo>godqK|*&+Elpfy^4H zSqq4Qu`!}IK$MwQ&*>R>P}Oe3 zZeocy?Dm$;x&t~&l1LRSRh*+Dgh~rKOqSVYswzw>vDg%Or4&N$QU^+S4nj^9*DcZ& z+@+ISoczI&rDI3@FY3V& z{SuociO(?Cck=lAnd^-j(tZS!Bsi){fR8|&tgkIo5ou-fhH0LMZeHb(oO zsAm(n0x<*2vW=vI7}PDpFu(vXx(2TjliLqDFCwE)uoj*13gl|Iq$`=#dywOm(7AFw zh4CbKLbZk+kssLJD%VSpEvmn}n#%%XTf7&$gT`$*%5(T?k+3-A-9@#qRJLUyv``z%o5Xw@b}#s@1Py8%c+=rEQ&qIXe-z$WOHsg z{0?qdg0Wp^W+G%bK9zd->ch#huyj|GA&9#r-V`gD^*b2w1EAFe*I~nm?;y>tB0i;Y zGXyVMz>av`ZeNX7AaZw30ut?utX`_1_-FvBk!_ipIqlR^g=u?Op4DkW&rd_)Zty_c z6ZjQ`koGsyqdCd`P^lt)3o3r~NW4(0F1*@oX0nZ^Yj`=;`LwC#)PGIfPv93ImZRQs zE31whR-E=1qiW)d5PQu!`izxCkC=0q(HDVPfr03#vD;_J{)o3i9}31)n~(CXh9yN( zCJ$|DC^uDUfsFyRn`~}B>zitf zL4Rz!69Nnz@!Dr;i)XT3b}sCsnmt2aubz$+v7y4cu2{q_Nb~F$r5*>oarsy|MrpcXwjiX&TpX*wW>7I(myrkZY9 zL@CL{P(d`r?wU@R=(W)LgiK3;L5X+b(@65QH2ztpn@2ok6Z(RIwY)`|q;F(hR$}r5 zGm&bcM8Zk)c21m?ftEKTqy&W|Bh?-;DE9$`c+3(*4`vzC3x}b9fb(QFlbA;9rNq7+ zSlHc6Fnc8rUACSWZ1*WFSI0~y=yXa%92PNpJ$a7RqA=*NC8YUXM#f(%S!JhMtjrpD zwcp#$+*IQ*H$saXx3+7fgHz`15PREZ7SYwld`X_gIY=eKcSXvma~8v~r}Bo!Cc7YX z{dyo^bOrfPu)iC7>#hNS22-OX6P6Ce5ZDpET4l*)kogeReGCri7?j$Xp3Fhaxm)36`yhYYc6X|JLd&(x9_7%X5eecP9=KtX;;u%IC)6U9 zffitL*r))xaZ(I=)+f%CeK=R1`DR9c<3<1!N_-74VlG04mxId55{`J<(bL)%XNnX( zkCLox+3dDO9I3@wxiXNBS#9^&-VyR38&MTEH1N?UyhNVlyK2UccmuXQTmDcVF>HPd z;rLbjv*=#Hstm%PErRr2410ingDJi{%s)Ziu4drt*e0n@?e;h%9q)1vn>Qhv^R|&Q z$sx*^^tkr>X>A`J^0o{0ar9AF#f)_zzB4c{l(`&U#IP5$M-=S#)aR(0i0}0>=oLbd z^#C+AE;pF+>){b#lOsE$-JZH(YG!J(_*LfpadyUH zm!-`QV$dv6U>gSPJfF`|2-|T~i->oM6#5T|38TGFqmg0rM$_Y{ z{0TtJhWPQfP!k{^ZA;=gVlm_C^}O4cm-}hq#58UsYvUL5{1tlsV3g?J425%t*MCm$>g@-!u6p$G1lu_(0M$`r^LHE|1^b1*zcr-`*Z z8UD$WUg9%)Fh#Rqxk}>c)!(qkOa5-YZmVClmD%z>f^Cdjon%QQZE>w3W^J{Qm?F{*v>j$k@FN}v@l zOUpo-SyyGSlN9ugc66Oy!Dh)t?Ec|w_#QIO8?Xd>43rdLx7VYLv(DBA9i;SUc@Mv3 zpO<(&u3GMFl6~-0vjW;~1$QPXpt})hy`xoS5_++hh6NxyI2w;b!dG^^p7sS5K04Hf zPSCU`t+!y2PSJRSSL{b`jzZ63hwd_gMy95RYoOE%9%q}HQ7ATGkLyiLD3Pi z=6@LS&V9YUGehzecfk~dXbTHVd|;Q?`F^?pM2IqHx3_%5(di(n9^Gj^_5R=d&zGbQ zQL|v7y0Bv;ho2<3QwFD0GTv1~;wMC67px%dedFhoR1EF<5 zo;??{Rp@+(J*Wiep5ug`N-3?cUNFN5$I$N`wNN{#l*#p#A<%8(59hRYTPEK~z>%D?ia25(& z!`Vk_V!WC{2udqr=DqnMYKMt<2mthqWcvXNwrkr zW8&FIB+(KADwPqA7gW~zJ+gRiXJFt`uw8U`_Rx-FR0D$5-wmVd|d-ctsIw=PEuq@&O%JbT=e^yIQOb*B>A znp3gEsR%N7dLgNV-vXr%lY6#ghpz$!LH08UaLq1n`Q;?|n%5XTiQC?u{+8#x<>5g! z5{QxnB{)*Mm%g1++9d{KsA;mSHb)zA#|^naqCmhuqBv%sb^R+!dT?FLYYa{bauf1A z>W^rvZ+iFgM?LRN``Ev+5jdCe+vyrEAyV%5@B#aooEyxBTy_R+CnIyuQG!77dIo(R zahGKgzXTr~k0Q}DIJ(Z)q5Og%u2YIUt#7Q4+74g!#?%8|vip6*yHG^uwUnYx;c>Oa zDs{Bgp61$&Tr%xi7U2x47en<=fG}(lqu$H3iXTL!2IycmQ%rJdjPHF4$`=b#q34@8 zLknhQ@FQO2#D+%+3}?S+P>BI>=Fr?1VC$VPH@Ojc&x<7w&t!VJv#WA55N0O=XYjOYrU@Y z@(V6{(>p)%xxvXpvBFi=m&l-6C*4?spNnTX-P}%c+QGTiB9+S>;5yE4>YF;t>~_?h zK7vw8e{pM>Fl{jl_SL}0oW2$V^(ESB&GjYfA8IAf+p`)fc)oVG1`9DPL#c|SziI?~BIobBtW!iXjrx5~;>2=mqW;EpOFq|HVKZKi5Vi313YPbi41kMbONj`xMJLK{C= zVOE>eRB2)v^|9O0v@$}$S1V)82uO}z%@WYMT3Fg-owgJ;kl7X#G=A7A(6DZ*5agfb zEZ54gQ7%qS`89Aw+iGeP9b{tL8mBqKV>e4CadBO2Mo8No4or=PN~ztxh4BslZQmJVI;%p}h#YdRa4oL8^ue5B6a z(?Eh5+G`%zjL(pp;*kWe&_Tj@(!ZQ3RWvq}YpTc5zNFPhHJ6nT1-xzB zb|QFEHTg(pEHRsD#*vr~Fc<(VfHT2U=SITavu5$8IyD=R;c}OcD9fVkHo{Ev0b=8{ zxn`iRQ#ic@s?GaQr`B7tMj)Lw9bqZed3(b^Um|-1d&4cD+x8*Ld%St1J~!E;n!ocE zc~`W>ouI@L+tTw=^Y;EyWl!~bWD3|wD9V2X@z1)mUQe)X@JsT zy&Rbzg_X~Mqi{4sBx$YW&8-7{ZC~URnAL!wnTJ~PnQz6nv|7&==yF^3M8v_r@?)6;$; zJ(lQJVnnH2oN{Dx?d&WBsCZ&>pwGJ+$HIn#gZOk%#oolT#E2xbc&FVI6|@=wnxqDa zbpw6p;taNb?wT8JJ?yLnm1eZEx#d%9K6P{dqj2a2C~Eo!*pd0@39Y1V(A@Xs_)8?# z47GA!RNeWRHRcBMfSIkSQvo?1xFvIK>a$z3QS)kMK|+|gj^BCu0$mEOg;0nEUm8Ut z923^qmJV%(mBtS!QxW=Cil1B8*Lm$0%gi`J_a3-*LtpZ`gZM13*+0(-#II8i$ie*=H2A#V3?TGyAn<}gkM=Q&~?zHz>;11(S9L_|&PRDb3MwsEN zwujFJRI6#Nq~6qi$o>$E@afw&_H};M@-@{wvZc?vX%?13#L|8XVmr{^=Uv6DwIMw= zXg_AFrS)Lq5T97ru(8j(0nO+~+_J8(-Ry-yB!6`-3K|iw+OIue^0y+BTeK?S z7G514Wd3VPN?r?0#ja7VJ!)mhS8kp#x35{(m$((CDlm0bmCW4T z=A*7vDW=$o_q}tqr*RbP`cg-D{-`Ww9l{E5_1+-S%L^pcJ?OwTIJtHW!eFv&MUGt0 z`@{OG{Q|T!$TX?l@aJ%WpObLW+|<#?AK$r#?_Q5I7`|P(C1y_I(sak1Q=cXDejIT z5;rK-nlOj2WsIxfCV!)Xgp$u&k&Y~i74F@+xUq5wbF zDOnk|OK^xmH6e0#Ii$p4Dzw4%eOmY=H~9%q=X?S1HJE zXUX~%6SMlZ4Crot1AkdH$@**=t`|erJGm?V3|IW=JdpEOq~TtZ=-rVASVF@Y7jqf8 zc1K_0cH9c)*u4hfx{U8Sy~|qZfkYlhN0y=oKzesF1j3;S&ss2yRQ;9s2bOp*KuM;WC_&FewMWf;O%NX1sB(_fSXvrtF|x;1&=|!32XMt zNDDjXv1?h%9IV;k9KvnlRt6IL50KdDX0`&Ic@!yhI>;xf^CInwhASmvbX@_Qy9X)P zx%*Z7VfsS%+&(|66yTgFQHL$|}B{g#IMha(Sf!Ol{*bEDdbQszf9 z_**50*3M^8^EJA*3=YLS&Hg&S(qdf8NHYvS5;se#leGMH(X$1=shFyk~`WH6nd9$wz!SW%~o)YYed?%Mmb~!jw!Y-H8Y`{% zn0T1q53`uJa(HC6ncLQvILI2b9_sj_z@?Gf`C-xHHd=$y*(v)pxQ(x!RXvr*xt8Yd#Z$_#rF4DK@ z?xbk+tDErKM{o{{52tTYWjJDFJU782%5I&&L4If0w8iFM#bF#}bqUKk7H{%t^47ZA zE!uR0u0Zx7*qBfF>-ydlly`pi>g#Vk+z9+J*$`hlbo;G0UBBkqPk&a9lkS25?+8QW z=4fX7?c=lqqly0AU%CF8tJYlig#*M!x<(l6oYr9RmY5>v8rD_e$a(K-JEL`tw4~@1P{6dje8ThuLqsAJBlJmKHpXYm}j37XSK*g zD`la#o3H9@g}pd;1v;fefiz!OIMJtUyinat&Wy>1(K1!O@^uJIn-ki|@G)Yx=O(mQ zMi&|fT-rvCWh;AaCEQBV91@cg#)p2Lp+K!uxi~Xj&|a^U9S9ec__BsqT}Jo-?PbBL z_C_F)*G=&)yizy~clhe8_ZVd=HyrVfddwSsvE-}AT=&7tUAZe2;~62Iv?i<*gOt)O z&%`pdnkIFKk;`&PAw&}Bh7v2jFNIqe>c~zBhWea3Eea||*vHmLT8LOD)d-#D#Gq6N zKZ2}^fOYL!>8j4e#%>aY2IcrLO zjS08vN$n0MD`lLty$fYRH-B>8ESLjos1+s_J1R zmYpB7!^pst5~mHr$wmz_YQJQt)(Nycw{BMAD_qD~rAF@VM%=EwyAfARip5b$+_exq z-QDQ!_dAoH`?NuH4223a3(4r@9L{&)V07VvdEU$)f7(38&$H$^{`$jl`}&C!3+(tU z&;w}ZfbbTM|G<6a#7Xm{ef5|*X~P{S;FVV5Cxi^J-zXKU=KdddJi?3J9M?kMAEi`K z&9F7FeBs1X=IQ6+(N9I0`L39Q$6uJ=eFovVh4~j-gmrLSO9D370!ZkpqQqr_IOzDs zN8U*wPVES2NpbvSFxI3Ojzr$V$+af&_*&EUh}1-hb;nF*hKjoq-f_job!>g_q{$qlH7rbG=DR z(vD_prjNZauhm#*63D!B{y96%=jQOFC!f`~Ar4%GmjHBf5N#gm4X#f72h6DW0_~_y zn^gm=6F=G%wawr8$#-Q|ZCLF}AMK1Jz{R-wP=Dd#I4gW8T4&P7&EpKLsdvW$f5e<% zy~PC$(dMc3rqihcs3~>u& zO*OLjzu&ucb!q_Vt4$(VFREvkEfUV7jYIwE=jRSZ>rLv!3(uGrP_y2clgH4p9XRn$dw&m;z;|8DEB$m{JU5&NIdwHH^nqOzy7v{~}{F!?344)oX!pNtvHNV+v zg}le}*v-85Q2^cK6zK_9jl#W!e+o|@1WIodk&GGntAW*BzvUcEDf;{Et5fgQyQG30 zfpx+l?{rn*_4DX$GRU%5l?rPwJoEBAt%>M|x4=5k{<&x&b0?ogjK_J;sn4cpqiNqI zg~9vJzrc$pUYOs?QOeeSpB6%SKK;9b`M*D~+FLcJvX68*XYPf+Rbq*s>30pRZhzO; zeieOhVD*Yszu=uL$4?}0;Zi~y%T~}9)RjaFy7G2@ZvJko<^$2WEiTP`G;f~Z2XVI8 z++q?Z$$(EGIjQ0NmS~yV@ldu&^djnl7v*#dtkn8D7Ff?TlEEqd+4)b1Szpsf-(rpC zd6D{#gimL#+YD~Clu8ri65YXL+a9ujCN7xy0n>g`f7phGgN1q4c77ov<)0^jTh1RE zK;CmQ2iQI}z$crc%_eai4Oqy`cbs~GDZM7B6mU*#tmm2a zqO-8Ycz|EbVvbongKl<2Dlr@$>nmZ^sgo~3`ja$Vg?|Y@A5&KXN+5n-;tPMT4^@+K z;5hJc=!@GY#j3BnfVAp4%guHJFY_A_2O^mRT39#^J9kMwnCHR?z3aG2o{|lxP{T*e zadD+6nd6XZ;!eoD3tp;{&AW7hSXDf4-VZPK!)<45SOsy@s~hFOC0{V#SZ~^oFPt!sNn21^iDsTpo;-=} zq!&&oygp9{1kS<+ey%P}d>+?u&t{>|EJEi!7NHn6{}+nNvkyoC;7ySe5wW1H_p4ya`c#a>O{m9 zYk4zWA>H7PiTVstjE2=Y+3WXNE4|RcDXz}s(Z4`)-=`~6x$NS85~^y`Qp(Mp(JsX* zB%$@`mt^M5$+`K=0#bPvE}k}8KdZI<__*anW=@MZ0EKeQw%Rl^?Dr+!3j;fliX7Nq zjpa)0Y9$_Ax4QlJH-58hKcfM3Q6lI*e}ISdM_2u5f=VMia~#k64fDeB7m>|zl9QOO zR!z~(M&E~!1UlCC4f8T@?K0;pems97!kNe(un53SBK7Ct=WpPL-);ZBqU0~}F^T5e zgb;o97pfZEwwQWPXmVE0?|R8#NNxnRd^scKw6B-e%2}{Mqr7KMau(RM9XBTkVLc}V zD=6LN0uxx+51BuJ1^b(P`KMZ&zvU~lL->{7)){3~Mb1k*u3YV4Z>1MK=1N>?e@5nM z^%jntJP1a7coKmePd;OwAQC+Z3yFmxdD(>oSu)86avt+*6rH&mZ2X6u*em!tUbL@z z1!s#tUrRE!iqe=1DhF04SE=Pj@di`@m=#BA+e@Y>tu`QfO_V28c1VWd5X$X1g*BFG~9baP$xLW^hEa;EQ%`%6Y5skMVWl)g8 zm;zmMs;i_3kAu-padirPgybo$jbt$>r$K~|7b|wH@PuDmn5SZyg9WMFbVF4#41692 z{t*WF-SLkE)2xkhcaTt-iQi$Z)Vi%yffP%}`jpqOMX{hzBH}Vv{nteqsiQOD^*VM3 z)zqBA8RF4779}ACXbwU1W6SHQq|5&TRm-N@Mc%AqdM7avDsOm)JIs3LEs#%h; zol+V{Oo=il6A)id7MFv+Ae}ncaX!AR@NCgb`pcv;xyd!yR^Jm_)jFTEV&PP#j|GC?=AlxC-C%pkW>e}npv z_C`JPaU{UooM1+TPxI9AC^I(%z2xx|^Y=x|Pd;NF$xfJp?c>RE`^eJunmK#DCZG^o zD!q29%A7z$pt+Jpveuaoa4&3OhAIJor)xg0V?{A7ilWIzKeP3opFct}_Y6mB+@o7K!WRVz62Wl5OT5^VzoN>WWM$ z>HfY)`~_CtcA1B_Z+OJun0m48{cWS_uUZQm$%e{Y5zIGKycZgjz8$uh61mRS6i*$nOZiQUc+g?3 zE>hNk3RBQNzx89QO}eYqHYC%tJ*IX$0&hvBVaifWE(g$G2_byTb|$&O1Hfwmlmn_~ zE@tA?vk_f*^=3U`ucpK-+|`szfVe#Z1DM3M#`<$D{L!C4SIA@XYRL_X|V9=F0*md9iJqGdKS^p_^qS>@I( z=oJAc0jp&Iv4{nNaBGYEm#$96}0} ziQusbE{eLyN=*rgyaC7jv{?M7#!-D2lwG~BLA!+W|9y!iUs9{BLX8$d&#B(0y9?Z#KWoQy za88J$Bxs!bi1i({{=1_w0%t%1^(Og82U3)IkRUWlxYydDMrt9^I8Z%>z2atCfrUWnFx+ y^@NNKH@zYrTUUwe+DIR*(O=@#|E~Jez7MXs^1omCu|Ik1CqH!MM?ZK);{OH(b8>+I literal 0 HcmV?d00001 diff --git a/tests/Images/Input/Tga/indexed_rle_LL.tga b/tests/Images/Input/Tga/indexed_rle_LL.tga new file mode 100644 index 0000000000000000000000000000000000000000..414e151a3c02ddcc49de8e3ffe3e37a4b8fa4a9f GIT binary patch literal 30549 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

Xj6Jzb{ z;SDx`X@LqzCE*y9Oy+7GhP}eOK7>C|5TH^)j&xL|g0+OLW1+iVUEMmXfMD?2B8Ux{ z7Ur{rI{TcCl%6k=#_7r91(Eezs6bA?4i-r{7cZE?lCmcB#4rr)WEO9ptWMVYgNZGp=p zK;Q5eBs=)m>7Z-fiT?05_CmawDrvb|4+6?Yr6@a8IIo4}wt88#MGSxe@w*?wI8&X% z@~KYM5QxD;#3lT$w~8iUp_i2ro3TH&&meCAtbQf)V8K1ZTuY=X4;!Wh)S? zAx_>;RZyBuhU4~h6EMfkg_I?nKilg%FWgKt;hpp|n_rdC z1X_(T?R8P7*#NQhhlPdl%;<6SQYD_lr?2^(o6o=W!}o7u^`YtrHN5eBM4!CH_IxUU zZ}tDIa%+5SNDk~g7r7Te5zw+*Zz6a#tpSLI?B9$~s(q+8w#|oehe4+;)vLBqM)!`F z-R0(=_Qt)0h>}$~VtLJo=XugTT$XCwrpdw|mN=l8nqnn@-}VS~jZ_nmiHHO$EIv{T28@sCuIRDbc7 z+HQ{(Up4JUb?OXy#(Tcv(j+zRMyC3&L(~Vzsz-=cXMxFa$8TmvZ`!IESp*KIOh^k@ zpkujWVqi>J7$8>|UXGmS;!7r7>-}UPxj1+l zQtrY|(0`m5eJO-sWTO<30dpg3IwCe7m)f0Z@O2x{kKo_CLe zEkjxb5*iLt1tjNEp7^qDsX{wX%qk-h1^c}&EqBQn_Jmi5s#ezf7|82xaGzfKo!7kQ zq8+#2TLF&rDmU&!Loy+5An$Po&@TDXTe6PXhozsx?nQf^cS|$mZ5ev@C>YTH_ z3-jX6=Ih0*V{YQJ; z(jEBKg$JT;hLVf`XwD~?z-u|o0F_}2$mRrNEg(3$9(8HXGpAqhFzFXx>KZSRc$PD?lUZ6-c@n+d5j1wqI!oAVJ`YrhMwO zmtO6<$L-pN+E37XN`a``KB;Wn)^^6v_`2(yK&Q{jfc@bvo}(0&r~y_F+WQU*}9d`ku?_&EsYEq8h1nWv^SPcy%K}BZ4j{GOV4!$6q;twgf!0(^WQb zN9r8rajds*k&!{KIZ8e`rLE&uUQkX7jxQua`3l zpu#Al5hkRedd7|`U1!Jje4I|u)A;nMwV1^>1Z?<0b%h+QX4$8669AP;v*84Bvld4B zCPLt;`?Ck^<^dN+jNegt)Q34E)e|fNROkWhkr0m6LG=VG;gx6e`3JMlyUFnnxw+%C zU}(2KCZz);l8K}IS_2_mbl)nv;Z%|3z&7pRGXeH{cFyvIu3HgSB@sj6bZiH=Lv8^`@`5(Wf?VOz zqs!1|IDw70lKGYn%gu!iWjcWI4Ln*NOV2(Phxq%{yY(CjWiQ?1=Keqh;WgE;P5Fl^ zFyIdCFjUJ$cL-2tpO<%LncHBEiBf;fC|*>qtn~-UY^uWssSn0TW!U36h7?$1@2kbu8gV#AG`{)qB3o*U&xY zyb|y5XFe@wcBBBO7!rFAxQ0;mNOa&$yWQhnf)v_iFG|j6esU>OBXlC>M^By3|7)-&;sia!8IZ4AR+1zvpZ!pZa{kL zR9T{fmd~mYDW5buvg5cRS)xW-z84~Lg&L9mkb9iC*Il(MWNi?K+n9B=)68s&5jY{L zaU!a5A!Ex(7rIcK4-sH~JM(%0HUiec5b$}D1Wo1-rs{H7Zr5advKdY!)SrQ)KUj^d z>sr!SvH(q6*2MRo8B(w@J^UGpLO1##_^72wSj>1_dlghlWhL2G$h z1{$E5(urSL!xG<*^D~|i6(z^%m750`p?ZT5<*_^7(9+Q!yWAHNtK{is*;waT7Y!tr6~RfKs7ob z$;93R*!ev~$ki6A9x9^!Vz{80Sz8J}CT}c_8|rDCvCi_h1aJ0Y*4*dj$8;OXis~6< zv7vInEqGN=^du)QhLB#AJe^Bf&=(lsLm^K@tgsxdBS#5 zYo_ReLFJNW9{m_6aWe>;F+irpS|4!p^ma1md;-!hdu|W|qGExn8|AcOB;6|z#V>_v z+|;oH6Xb*vHRxD2i)-VQ7i*Sp69~#v6?ehbELbf-BW8y1b(n!yygp-l&2G1Fx(>l& zBWEy?*N|wT0KJu&EdEz^%Mr^shN_cOI+TimC#g@mcia`7{J@%FAoTJxJQ1&z#@I`o5cW zcr&AY_wv)#kLC=eF^GV77ro+~iiooik;vh3W|Ng32G-6M(}YwScS5kfBLA$33xpL& z=0H|CQS}WUM$U;~%;Y!(9VWTavJ^SY#Cb7l4l^KNZrmW(1P80x&r$yNN-}JD2o!0DPn3lqas46{-9TllE_k^P_E=-2s<5N%0n+wZmx_h95$`eMA-ov zK#YvxZDmCO(OA+*I7w5aPpiR7IQM~_t73xarcawz)=~~t#fKLe7xvoD?{eK|h_Ifl zR?O=#KkI0RqR>$gN=8EhR-aD+Heo*YAhC%j5Yg06`-r?y`v63xJ3Y`!EE1(Cp*>y2 z0_eMCY5>JMjrpl}X$SK=Z96qKTJ{B3Oer|#hxQw|mHIY`7rc@k-BxP$d=~U~w?eC6 zat!JyS!kGbcFFP5dp^jrQZ#Wl^4^5JS0ZmOhV0*D*O-atcYWk!hwiVH%bkM0D0=XGeY2x{ZvOaK1@XNsePp*Ak-K0sh84UeS<% zo}b43L5?%NcJLf7qSf!#ZAJ4la08Z>HP>B$*f(=Vh11i=Oi(9p(fC|28IiEo-!yiN1}5Y;MV+PIDNh;5=0sVd~^-s*#W7!$yX|y+FZI#KNE{OvE5) zR%Ec=!M?}_#&@!IhkD%8gh;-4tnN2Q07A+1eZ9>8>6i|UW9a$Tf@>b`?OzDl} zkH^U1!42AVGG(j*aS$PR7pE`S*swx#*39k~{d@W!NjzB(VdW?oyef#B~$&FoZ}v zE&YA)$zALLd@S9_!O)lp5;(zk3Cs_D(}syD@@tvbYcuB|n8cysuBBG;kMo!_5@^uy z^bHc)Ct=7aiLisvVmO|So47jXRFtw*MLAxErZpg}4Or(U558hEL`eau zIsm{9s69Lc_2I@R{nfWI&qH`tu(wWH^A0+gIC2wqDS)$|0f$Ie(aA~&?&KgW!t2cO z6NZXZE$N80X-Z2_w1PjG{q>qH;+T@2te~&%Z^_CLp=eck&27XL=H_!Ct}sdmc4CM4 zzXwUqXn(qD5)&5!|CI=O5j}mRs&TyjCAb+#g4 zt7g+?IRQ5_)jLS*t)Qb6hi4|!M70X=CnLGLbTt>{d;spIOgt$jy;if&r3nV%+>3u_ zyDJZ0XI+)Npw}F?IpG%9c@8TSsAO20#;rzMlm`HpxB?qU)UA^(DxHExkHV}a!94b# zUF+ta{!u|6D}KRU9y;?GSC|aJq`DM?zPqswe^*bx#Vx#hG#o3K(CUZr)~H}%Fscnd z3jIsqb7{5m&ZdgGNaEBX1oANZYJS{H@THQ}g3Q&(1)hj?n;cZ=&j04~mIbUCU~ zxLTo$;8BpGJL#4ixo&X6e)pNBm!A3A1GUy@K58Gn{n9gE4yN1A2}VeabQX)pm&U=C zC28`Ko7~*V*2k|^R?$REhVAjAOibAxDDbZQS6wm#Mm83*nlM|dh10!x6Xc8dRdE8V z>H(gM`v<_*3iqJ1N5%2%_$tX#j~;vr1 zU4;4cQIRYcKkVi)0Qq-2mlkpw)ywmU*W3UprQjn&F5%O3nwJmaRxV46N>O^VN5yZP zf5@ebB-P!O947VaD~Bm0{!%^9mo$xj}n=k&pxE&A%KD* zOJj7agEj%5+i9M8z3Vo~|>{hgnwg0N|1w@2LBz zOQ1dY{QKR)^Uvetq3VUO4`H#{PiEZ@@Q9CXA#{;tDMQFUUrhS#PK?U|lgu9lpXO$l zJo8@Hefegl>^G^u+5wh=Fw=cdsrfd`HE!d4v(K+mK|u!+#C1FhV$Y4wJ>m0i?u65C zr+c9qP}~iM*W;wAu)O?gFqV*nN`{N!8&+f{W&8MkM!fsfJ7CLRvjuNgZCVMJbdXFy zXPKOj^N8JyU}fQ@Sa{_;klmheQ<|?|`11VCEWw!v8!VH0K&auf=sj-fFl6W&(Rmy# zrrC4yyhS;Yp=8QGOnk(8U@)810gxPZvK4%>mXlK;G|G1ZCq=RVa@SknOy}uf#~Cli z-K~DwOV8Zfp;EWlB{OH!2i?)IOf###84CWE@ntGDqPo>4HK*=S16jpU`bfzWT5Gk_ zdIZyC$T~aW8uf9?^C|v!t!jZCvL71}C7!mrG$vMeZlLS)<6#oWw42F5)M4Z>66D~D{0PO}N% z=BQ?qC4GcFq|AwS2v(U((}^TQo~ve)LAOT%6Kmv!QmrN&kC@Ml zeyPVw*OeIR9;JzzIPA_E54Jxa`)09Sg>;2fj>Zny!~%oM&M@i97syWIwN%Y6(1X6r zz%ja*DV1vAOOjEJpL70OjUN<8&7t973BuuIKiy?fKu==aHR2?iVpZmw)QSOMvgDL% zo}jonW|YdtQ%JfTp;uF77d&M}fO^=sWQ--xXcY5(a?z9rjd%8196L110e(l<6cuM& zSfHHHplem;WXEHR^Vx(Or34J1D>JM%-IT%z%z3D~eUk+_G*dtNe1Low_IaaP{ zFF7SU8`01~?rP^*RZp>iLqTE}0lP_4bl3x!G`WgK8oeuh+MN5jDiIthKFDiwmsX3> ztr9`w2Q8{EtPb~$fhlfQk3%asb|^U#2bOByVReltV!Jko03vZgvuH(NZFEn`YSr$h z^O;V`Lq*Gsm=!CPsQEJ=_N*0-Yddfo`DxS6y}^y+VXXy(6LYum-sw^742*zfMu?Zv z7rh1yZI5WLRM@HRXFaeC0Ux%Letz7X-qjq8$it}Aj0Db1_3e5O@x3||%UrEmGv%nK zCntaBvQ8*nT%>-x>0c$%#~dk=1GQ_zYSAs3I5E(Knh{f9i}I<@N+zz>5cL{zm90WH^Cs_n}_1xhRFQTb8

Rkw9VaDNq>5@K;9oR72+1pX**)(j7^fz}WhPEc6( z&1Powt-0tVtg;lA1n8NePqdI`D<54$Z z{m%~cfV?R}E@n!ZS^Y(1kg}spu&y`x|GWXvu6DtLtVmQ`Q}Z1S6F`sdY(->&^Hp6H z2@|d>qK}e`6!Cr`)NU5yjD50&V|}uAKUAzOFbC*)%4{k8>h`~5nWx;Ki2 zU`JO(+MRzf5qI7pN8|*JJHgvIO?Gymh$_m4wn4bYnp)`89o;ncpYABl z+c9~3L>?)sjYti|OelS%XMvT%EEbEmT-<&NHNko0$a$!1XMLZ>Ix5)7g>TaD%&sm+?yHFTAdk8FhXklm6$R=q{HEe}EPWlmKeQf!l zY2{cizKNPqV!ajiM2(4c@rKJS4_qv`76Z1nGnB<5CR!#h%sEQK#C#C-)F{{BqEj8X zxBFU$4be=-LWyRAKWau?YO1bhY)6n-u3!!I@mEHEZbt)kcMVWfm##CptQ&+Uu+j;AI4s-1#S9Ki|kvL(*uB( z;r)`X?vVSos}heM%=8NNUTMB?+K{btM^_T1SNQ_Ot_4A84mE&h?^uSOJj(B1jl%q&pL|C2PXIuJOoHT<#4FeT^kV z^Ru=?aud7Kg)TlmOs1gS7pXi+z}j+s3+2#mMRYjm2$R&sP96C+P}+>FKjq6(R3V23 z9XRSgBvOw=AsbP-mYb525i3vJ0lg{nQU|_Um)a^}QqSKWS~q<>Fl^n5QWhb?C?C-6 zuq@f^QGACgy0f|Df&wsY`eD?xl2OQJzxWuIRhQv{fQ%>VOC{8GZ!W&%S#J(8)GW1t z?ATh0We)jhqC69Xb2$l%?nl(DRTYIu_%}&qwd~+4IW@S~%(SkXQF;2J*9xS~nI__j zvPEo=^YxTIgWj-LCneXtub?SpM7DX7x+a>6P3F!rE(j~yy@cv-4A*aEyo z#9FRV^KWyIT>y)IDzek{IO=fhp)063BGeh72gI@IEDllxc4jv-e3gu{pcJAzG~Cq! zlgcr%@nl7+mIkRdUg!)Q3;Y9>ICV7VlcpNOpgAXS^yX58M6$)m&PN!SvUAmTue9}e za$N)O3h9e=nj~(K-J_Duzb(wNSmGy-DN%nAE5UAhofymE>FWL!DFLB{Gm>Mn<5QTT zs4GLFGchAs!=!q`U;@xPGm)`+Xw`{PqJe^@h~%(v+R!%kNvS!q1*HK(WHsX)lr^uK zPvVX`=kP^VVD=e{{E*}}c~#k}b#ahSBT)mxD(xB=ySt48i8V5ypffqB8xBym1!1)sp8lAjRB zg6qb++@YHcx%wHun-TiQNTXH{o4!<8Mc-rsy^E2W;}k^ZObpkXEz@QD35iqAsd`2r z^$Dz4vCehL-72XMhCq_#OAGN;L-3%ErgANTmK|3+p%~&9xd9dn)Rk)y?i? zwp1tU_h7WX7L1pNuHoqlhH1vP7O^S25b=@wQlks4SRhTbsw}zU?S~#lFpgb<>M32snWGsV8&$2_i zh70By&*L`-07(*F!4S*R3lw+5gFDHDCv@BKo$mABa0Gl$ix6TppqG{{U;-N&l#+D9f z853LDZ22tgT^x&F1C{I#bpb6viesvN3()zL%LoA)djLtE>6!tfOms#w>d3!nZJnUT z6X21?I^Uu9Pr%s5nr2k3*D&FIbhDd$0pV-mma5G$yKC$t=UD)}Pu@Tn+YN+BR}%u= za_}$a-&n%z7*rp@+#iY3!FnuHuqIV5O}moz#We{iIrF=bNVBvOKX!NRHE9rb0wbCr7GcZdw0U4NW$ka$fl7Z5kn@q>a z{9txn;TBk0F1+tImQ;G>prG9%Xr~Nx*_0YzU`~e>a;!gGV;nGC4qCPW3smoom zuPop|D~C7q5$osmhX+yza#mIR90~w1twlW?<^m07biS{T`&2nJQKD9zdvjNTo+?#8 z8r*3tp{`bO6W-_elo9)!qZy=Or77p7I&6)u>V~{Y@+he<@{`IiUPJy!U(cb|UrtWnN;cJ^m3x76aJSN{ zU%L^I16C|Dq!Nqlb-1d8oXhJ!vJeCXHD6{~_v6V_IVy)bpn)4!V$|T`y}YhNMVHKz z+SPw#?O4OS6B1I2$)NS*)qgJcOV9Hv+_CIl|ErlCLREW`9k&uKJmPGY!(q%d^n}$q z6Se=!jSd5Ukg*8Pn61}Y)uM8|fIDsk|FZl2FvutG_fyzn=j?J-Z=h>b&C=U24S^+{ zh$B%ox@y|Pudmfnx@ZbTE}7gkh{W8qn~@eQ@RN7=aY}mmh!H?S_|47^aDN87p3#CM z1PV>(!-?*5K8pJk6G48;&C~!?JJ=RAMID(?GKURxdgDE(QJsVYV!9$#XED)1<;NlS ziLz@M$E3?FGr9smv?6OqN>)e_F)M8?SS~`9j1mfrU6L5Y7sidw5CBe(k)30=W?|8e%P}XE zeW+=Gag*7Y6^pJJsrQPj1F^oM&Iws9an?rbI8soQpLq&1>W+*?RXK($WtXW@u>hRt zSG6PPikO;!+}&}j974MSNNh&donlA9ImoVrqbV;|dv6C1+pw1m=AP!y>Ow#vbSJ1 zg?TwD+Ki;e<^Vl?gNSWXkrvutyUGshu)ew0L}{Y~M@lX+0+oV8GU!LJIiUHmB=XJb zgUh~ggpJWbbg*C_-jc}JqAe#s=|ur465<8vGRePwb)AeZySh$2HX4D*NOOkxj55__ z{;aHzCQt7Fx6I!gpfxMwShyJ7s#>DcgA$@jOo<$sPE0TqwUR2+LJa{k)=~T$?xLg7 zM{FnOv~9PH5bdi@D;|<)W8aZ54u!&3k>Gmmp4BM*(M{&$j~x&mIlyeyBF^Gi>oZEC zArKeQ3Vg@W7t1NRwRp#x>QF5`UR*B1n{_c>)2^kN31@03*%v1C?F$o_;7a|*t;su4 zND(I*#mB6VX|Xy}mH9^fp-KZ*Kno#!7|w6FIIe00$MVH-RzJ{ns@&7SXAP|dMvO-O z65pTDVr7ebofbBL?-VoJm8 ziBm^*Mv5-HGTgUuqJdNcrbwM1YC4o5R_R#fq4J$HmEqikfTa&Z>b<0;b_eS*!`Y{f zYN}$gn&rXp+YIl=CLD7`93)Y8MV##`ss=Zj=*lLG8N-0&zYaiLlhE2sSM^M-0>!Y~ zY;hbKwE^w=9N%IT29k~%g=#Z=#Y|IJ`N=WRTyNxBb|Kub&Dv)aajx1C3!_fuH6fmu z8%KaHM0Cz*%Hq{!jf=(_8Xbj2zK|!;ISh4G%0F4dSCX||FOLFl6cR^nZ+6tKsIP)7 z${*0Dc+BN*Tz7OudOVHE;Y45irTaR8u6}1Y(tX;nkt7i#oCn zZD~KN4RjDq+U`-hh+2yEy4YSl+ZnTnpM$GiRDENRRVJT{q=>s2hoHk`Us}i)D99%* zU0h8?U0G?zN>gI}NxItlb}mX<=zva$F=jR-7|x8W$PvcQFR;@{w07CR6wo%@5$o_GycapUFl5 z>ax<08mqA_CW_+VBdG#erYXWA@S?tY>BPbFoN1e;1;wHJjv7UdQSfxN1|4TNzzozV zYLIZmTOBc}Glh%nJHyZ=sfkDLn>&^lwJQd!ze|2u#Y5bOnxMcKF?>+gvMBi~JgGY% z7dHUC_W+*yPrbn{V`vGJ^7hm-`G2MWT=i8R0PrAJt+W(s!Yk|$uL{+a(9?7-a+N%3 zIV{i#W7ym-H`2)_rpi7K;oAo(@pSo$w%TvFqD>)BU7#g83TA!2wF-K+Jt94X`-Ayp zb=pvvO^CZh{Zjdu4k>m+H`Oa<+C3YD3ONcBuj-F*`Jx#MdZ=HC_>wvY&$nPgwOQip zIo7R})0DV@T#vvpgN2ON0BbE1W00;>xPC6r$4-qe%uO03i8SM3QRpvQwCD^nO$Pe~ zXua~h#dT~J4_D|wsmhh40STL?Kypxv@YT4x*t zKA!+UL?3Vj4NR zm1Wh1Y8ep$Ii>;fy@nGwTD%>b_G=VtHdmTK_+|+%dYgZVRTfHeYFFycft8!nj6I}~ z#CFJzY4m1^GUZI5HdTM)I~t)3_;fA)bw(9d9{aMpn)r^y#X-NiePu5H`}6mI@QlBB W=Lg>Tr|&)WKfe8)?>^&ycm5Z{lr;tb literal 0 HcmV?d00001 diff --git a/tests/Images/Input/Tga/indexed_rle_LR.tga b/tests/Images/Input/Tga/indexed_rle_LR.tga new file mode 100644 index 0000000000000000000000000000000000000000..0fafa56d3adb0ecae8bd60bf193a886ab5bdb28e GIT binary patch literal 30610 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 literal 0 HcmV?d00001 diff --git a/tests/Images/Input/Tga/indexed_rle_UL.tga b/tests/Images/Input/Tga/indexed_rle_UL.tga new file mode 100644 index 0000000000000000000000000000000000000000..da7153daf50501f171643a1e6a28291202acd6a3 GIT binary patch literal 30640 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 Kde15U*7-j#l60#8 literal 0 HcmV?d00001 diff --git a/tests/Images/Input/Tga/indexed_rle_UR.tga b/tests/Images/Input/Tga/indexed_rle_UR.tga new file mode 100644 index 0000000000000000000000000000000000000000..f1a52640fa81f5a478c41bf9ff00f010d1574427 GIT binary patch literal 30500 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 Date: Thu, 2 Apr 2020 12:05:51 +0200 Subject: [PATCH 17/25] 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 0000000000..fe694a3938 --- /dev/null +++ b/tests/Images/External @@ -0,0 +1 @@ +Subproject commit fe694a3938bea3565071a96cb1c90c4cbc586ff9 From 39d5a93d1439e28078b93c5e0ff43f0a497d3ff4 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 2 Apr 2020 12:31:35 +0200 Subject: [PATCH 18/25] 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; + } + } + } + } + } +} From 69639450979861eff73bc7cc17963f4e79a8f8fa Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 2 Apr 2020 15:18:53 +0200 Subject: [PATCH 19/25] 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 | Bin 0 -> 25266 bytes tests/Images/Input/Png/Bradley02.png | Bin 0 -> 26467 bytes 5 files changed, 81 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 0000000000..309716eb55 --- /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 272998a896..e475a7712f 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 fe694a3938..c04c8b73a9 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 0000000000000000000000000000000000000000..b8c3c0b6f61c2f8126cf87402abdee1aa90d1a96 GIT binary patch literal 25266 zcmV({K+?a7P)004R>004l5008;`004mK004C`008P>0026e000+ooVrmw00002 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 lUbL#erNrK`Tm;&!d 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 JPDHLkV1h5aoCyE` literal 0 HcmV?d00001 diff --git a/tests/Images/Input/Png/Bradley02.png b/tests/Images/Input/Png/Bradley02.png new file mode 100644 index 0000000000000000000000000000000000000000..8aea767ab89b1d4f8f32a7f8f834d70ac63136ff GIT binary patch literal 26467 zcmV(wKr00007bV*G`2h|4+ z6bJ!ooRY%;000SaNLh0L01F}j01F}k3E@wo00004XF*Lt006O%3;baP003t4Nkl)h@TJGrO(-h1}h zQ|+(T@U5@bfe{7>!G7@zf&cOcF#ynJ2>M@sbVH5=phIOB{RZ31uSnYw_0U*Au1OaQ zp~Rp&WD9{$rW0d`5n}{^9AiicQ>0f4DfTJngphKC4l?b|^uZ`VmUoeM<}?bE9K(ce z+9ilNMBYnhhnQ^KJl-ge1p1)z05)R%0EdS|2SUN%E!vBY5BdSRg#r?vT$()~Pe;3u zZwiJe9W8HA2c^ILFP&WkGr|FRv^E<>E=eC2o)Nw%gK6jTRplmpI&xHF~t&sUq&X7?KB$vba3I16ftm< zk9=c>HOkR~DWd^mLMibZx+xMg{6Wv7jv1gso{1EB_7fko5~rcl3B@k1I}TsB;#C1_ zaf8qxRQChGu)^C*(hV+tMNgJ0Ex1Lj8DeUqaMYcp6s4Ur-X21hl!(9*f{At@_xEW0UfA4 z@Ou1#7L$sgIpSa`(Co68onNu!pR8L&ATZOhnX)*6Lm;t{h>JosG_g94d-8cSv~0J8 zM+A#^sBv17-+H>qG^it9suTR_7^2I2K0H7$B(EHB`8!y zQxW<|=&6c@6Eg{kb3VwZ7#0o!$j35xh5n%gT!vaPL?mb;hk=;cZGi(L{pAD{88sRR z9Xz7T)eJH`KHLo=m`M(Ms+lzLvh`_u=z580sDcI6ItS)%2%gQ9->_ZG#kK0p47cAtWAPhwwS59FY2Zr5e;(nSI>F$ zw?JsFWvNiZsK!aUanjgmiIFUiBWhujW_#*0LCa}G(r@U*8jb1zGZrCJoN^_#(4lk^ zd-qXSZL&b)jO*s;LxlDq40pW%hrLlJ1(fk2`lTxFVl}8qOtZE_1;%@P_AHmzL!#Ln zfdrK!IaW~vqNG>Gl!!|536{9PcEv2KNKlDUl7-~rq68KA9rht~DUq#aHetR-A5vMl z53S)>Gi!^RKs!F#DdkV%^g9>qTRy$ObJxuu4OA9B{4eU z=&_}amwy*j-pKzwZ*oe~e`LbK_&Dj3!xqigOsLh;D3LJ7=D^pZdRWd~N67?^I8Yk} zt8uJH!~kmtg#oOV^LT&+u(Tb)Lc8r%?4sC-1w)H|sL947!EhIgY;mw$y(G9T35La( z4x;gBX4yI)AbgHSQY2S^dpU1%)DvKt1ooU^$P3uG8FZq`WP*}0x-2+G$zQ37ZcR`N zVLkq<9oJ*3GdVVJB-hMSf|_Zlrt7INq22VC*bJqRF<(Gwfu`hOi(znBxzO-)b%N2y#8NbzjbLs6ps=_Oc_|J zVeU*RCG~&()sN1<@a5U#k@o~va$1iaNEr;7w8=0{X%;-$wr;Fm?WQ}Rpr!#v;WV{I zGd8PLtAT(cUg>}@mlGX?z_o3qpmcEGDmTIzA#yIB*oDGUl(!xoWFBDnY z3jKI%RhW*;-TGcgWD@V{%fJ28&%OU=e_{T@f{x|tSW3&IVGq_foAPXU52iP?=UPfz z_u$5fLpE5H8wZmEA*=@kgdr%H9;5fGmSvTs3vaYMJp)bPXORE#G@hd-eo(i}=^->2OecpDXI_*HF z@Al#&>>0ecz!q=$9aZEDCib;t&@<_9rCvN~coWQNEya`1K`COv!K3Fa@kizT*C2zTKz zRAuY*`H0ZfY#hZGYOM!Wq=QpD$(v?4sG6@Z~ASj@4111i^P_0g-Ha$&yU$$)Oh%%STbvR{H6W zq|19)RQt)@RmfTyeB@G<@A$_VC1y#|(m8j{%7hiIe5-$=Rcyh{BbfDxO}Ri7j1xvr z-{6@@{5rb2E@?|yuH@>;bz1SgYNG1jSS3VkwAw+}aw#<` z&D6``YG-e`+`ly^PhIWYq1tTR?;eKL?(JoNt;6LZnc=riJpM?ya|gq<>$3~@U) z$Ocw)>7F$iN#ND&nm?`u`QsS~_#NS^nFa3pSb=NUTSHzf$;g6Qu+$Qa=xAP5WhP1_ zlf7C$OUw{OOY2)@lxPZ9==WEX9AY;qd%da;9s^?7nC9t(Zk5Ia&L}J}oO$ZeaBz7y zta75*hAUR7Ic{8@Z*+9-|;SoBdNLZ=k(DzCtinNcV+ zG?CKne4u?8nq&rk6o{x9f@v(iBVvu)sM!Rqxc3!wp_Vr*)~XWOs&(auRrkM zhHNAjJ@|0eV!DT6=k|&yVu8)A#Jc}N*OmF&r3}WQOQ$t-J8u5byF_n&MyjwFYmx7g z^q@1>Q%spM+9>q}@8yw;+&Fb2!3{f6i^ zg{uzvG_FcUxUhZrAKbWcYx(pE`n#emBP(?xK-N|RA5N$X6+>BKO!3`AIH-f}IT5j5 zj3bpCQR-1vlt5K-B0yEF0|fUvSBO|pERL_3A8pNju^x#03k7k`4B`f$Izh*~dE8s?IxDg?D2YFc01 z3cm~c8&IrZ^15M5Fm)L+LgmT>3#|pZDl=|lEnfwz@ah_yYeffrDvLDb(6=fbeYRN6 zsJNZ4N?Pt9#kQDtmar95>i2fz<_H#VE^#}Dn+I24EtB2D-AC?x;}-6BrxNGqlun(0 z<@qabJagugr^ewQ{`N+9^Bb>yV61v-icm*g=}IGoxE}H@GsxZWRgG+Hw&hb6TWdFt z;axCR@tQNLUSi%ldA*NmbZ-3CwfNq1tN)!nGQCl7l4oaTRPT;rPGNrI=Ir77N0X3OluWwZ zO^F7^l(VhCLxa@ttSyM&O$e;7VX+7WK&#?PUFvZ1EgdbkObzIAVF1^g>aYYj1P9I5 zI1S^~E+yY?BFL7-JU||c-NRxTC*3lPSb>Jr#R!>Kc*!_fDJ6dl^jX3*O1u=;<&;L5 zd)@dQk(?Yyyaw0Nq>tkW30QXFxDHoUyc@fKLCN}F0Svj;WZ>)LSP6@2E8t9~4BEyp z$pUwgYAw3@Af159yLqU2YsuQN+$c__PQYRq5vXBg?SV-|*(g*cGt*S9C=rNQvgkR& z61f=wH6OFu8hEucONSGS(W$krhWfd{5Gp(SUS2NvtH8dVhwCl87>P|spm()FJ;8Ii z_Qx2w+;pKtQCMF{laZ`!gj7{Hbm9oU#EjgjA=Bs$A$lRLH+h^fBcTN>28wo1TQpNN z!8vg!mmCtKU42+J@|C1xaNQ#)Ry}=ai2>^f45Ev>rqDD@@?pgu-@6V~2K6RMWfKaD zmvEq6fKndw%20GoY-k(R<`_LfN)uUa=au6kq&rE|QLiigJE#caA9 z>8fg%=bZ4qBdy^bzhaxv{aJLO+!lX|6B(m(VC$A{ep>j3A!;_N51QOpjNTb;+yZ2) z%Cfp1Y@t+|)0+5+RrKWuUD(T+tZ<0zds!&%xIaro1PYT91>Fp)nRq0u>$1+bzBlu8 zk`Zm)LhC5B)&Nf%swVKOX#W9vC4ypiv6)fxkK#o}P@z<(Ng6RqFmsni4ig)!KGVF7 z(#6632+~!p3#38X*pytQq8W@;1!mYQ7O1%qjtDxYq;dBi*wmie`t--~0$ZLmO~nNm z_f~LrEeWQk-Jvp8(IB{n=sRqQJXl{FR~$n-mI)H5W*I6^4;mQ6I79J%&!`m-`dztS zx-Xha=%}%j#76b#v|eh$7NVMY8|?5HF|;UUN=mbk8HTDQ)NE$UPU;D)CwOsda*Fe5 zKg8CDpfYW=MLWaa8bb7;F$G<0;Y^?eNJqzgyKI-7Un8!1@9rRGv(muLl!qHYSka1A z=vym!tNEep8prbzA46>OyRG#aXAX5$S=+$f{=}3#Vpo~1svG`22essU2ANCS%2hFsMzEux6#L2zt$WoA(4r?=or1E~RF#9^bVWtjvOgo9hg?ZSaG64& z%Z2s9a+B$H?inu(8y}>3i#_>n7QZbyxX*v@AwcHamkAN>`+J%MXL{+2vgT-_+=;B?8R}Qxx zGGL3Q)1HONU=+g zM>l)35lR-}EKW3?YgsJ~auzgXy}5ttd>6V=z&z^m6EEo(no37Of#_iBq%hH8jLu)) zwa05Z;nx$~@d0Qox~f(6##o!~)wjLp1dQ^f<$8SV@zRsCui*0k z9BlpI;~kH3hKD?zul?(*_dmM)XjdP04btNSP)n>3Owu!#F4i7iH7j9r(ZZP(8j=va zg|Zwjy0LZ7jQgR_4&7)um+Ci`xb4(*nSKsrtTZLdosDe?jo0?e^nufK^VeTz8vWrj zAZR9Dr0QAV;?8{X*5z0J{{0ue`Ii@d^AaVGJD+*`mrsB34?ps$lQgo8utTGG?)N_R ziK~C~!V_nk$30f;6hNi8o~t~0fqV}ZjR~NwtV_qsut37;Si6H6o3dQSu~i8e#~M^A zK&?8;02~^-5rNzvgsp>li5nArpu^o2rZXF1dGny$caoPu-o9{t@1B+6~ z>SZP1@+~mHt`pWkDWl|PKliIYKI*^r*-wA^Vp;y~%b$5Le&nzI{I~yLL>34X1IwSC zy_hci#_yC0n}+mbj)Bn|$gL&9$%i<$@nRgRjjF+@X_*kl8wD3dzIUMCzw0_Ii{0Dv z`Oe-huI?PBwAeY|9+*YhJz$agKsfaLVDrqVze6Uut?}~an-86yoZeWz-uJJKCb4^X z)}!bN(sV!}j~Nt`McDoP^ut}AKKY?*pIzd!&pvf7cBejm=7qnY5l#!;V*jmgUV-$U z-&$K}Zxn^&0?@v!;ju|7&0#Ulo5H<0NO2pa@i4p>xk)yAy)>C*zgl(Vd3*iFcrqz- zgxQ2DitSU|sqBlS6}KkiZCKH%(-8;3{z}>+w*27iej3>RYaw)(Iw9m7XPDV=2bL;? z|9yRXlW5@V7xCpQ%g?Vq#ESH}pE>*@F_=h8iDYb!BYU&Olr7` zwL60Kkm~|)3?`5SU+VI1UoKNo*CRqX*xlXPJBSnVx9RNUW)Ll2S}>Ru&pOOgC`(p9 zM;YoGDQjtmiz#>6pZ%c=G5;TJR~BR0byd%F?;C1px67^`;rMOabI#stueI*s=B39k!maBTi7!Yx2PVEW^lsq! z=!uzr(_nITdY;=j|A4)|XYIg_z=RLpb>;6q^r;=4e80AwIj{gK0l&or^lB+YMHLDs zpDWy4%fnu!UJ`eliyrIyZr<2F(k;-&z`E77d`!-i8-*e3*?ewty=+ijBx+VOrB(+I z3hVAT0^3jg#~BWPa=a-A;>X|{+Jy!7znIk1CcuHvcm-Pp!6ir!E`=V`mQ;H*JbaVH>YQ& zI{H9pz(wNGIZAD8-!Q4ot;Z+-G|$<(HT#tt`wy>O`AR;D-1GepQ)@8K8{+oojje8b z*U&4-xs(VxQ;0^|Dkz|=)D4lgQSV^e$%qaFlDs`1%p&qzblmvyl>nH<;EnL|X}Drt zOqHj0&bCk_{&;l=wqM-cJNwK=&(BWfNA#;<#ReXDfcSLL*sCn+JoJ&_i(kuy&_CSy z!Ja$%$MBDj$NBfZ{D~9!*`7>SujOce@$XYQedgmg8eP5zUPa?G*Q7)!q%UiP=4W&v zwz@{Eh`1@}GnC1tF^e%&Ly8;B_gr4GuoLqR@62c#)^p!AcZXs<9*09YnmhBuJ)5?g z8&<5JU9!i9#++HK-&XnA%X|1{Htrt4|L(r+?Y);?ecz_70>U(q)tm@>*+juqrosDee0Z53vfogz>0@JBo_crdvevW7^s7;-h;oSRS zy*IYiz)qUAzMYR}#t+taXA4`c^mZHT>*K37Ub>R%<45{AKGkRj8@I33sDVQXjSZ7o zsBEvdIvDgT#IVMr)y~0Ji`p>l z6R0gGfWb&%jBzM+pn}mV;uTa&|6dMhZ%Sxc-&%#Hfw{?_3&)Lh7|M~fO2x~%qb<<( z2Xg8J!%8Vf19db$@Z<}{#WiAt6dHEAaF&T60gZXQBmG1`=O=z^}WKGY^4r^mD zp6Nvf2O&PUZ4t3jnxZ2wq4Ip1=QD^7demNfF0y>97cE1a4vb(s|8tOEI!6UkskO8R z;f1p}6;uVQn4s9r*n&ovNv1PpGiZs*prbw#Dj1y-X!m2SG(JetN}{buj<}Kz=?45% z*gQKNo}-x&ddQG67-{8>*!k`J{dIwY&hG~vKvk6q;?{XpE8v3V4n);fGX5w?LdWl; zWdv|*V-?4dc&FIFqDfj2me5#%2R)DhmJ?M9D`UY3I|hY>o;Za3aF0YSPx3jj-D&C2 z{4Nf*YkT7W()PuREh+1P14rg~&}A&BuYwb_OOsXE8DhVXbb#oTBcwr|?S(--i~Oiq zco3pV)NTXFLCa(zGB%1e0-GeFB>k0IY&w*!~o$`y(KTFaQ_QFr<1)Cu(A5!F@a*uptBx z5)DS#7iSS;+R$}=*`@4)dUBPCaCuqHwW4*U*LNz-7Q05}RdV^zPP_z*1+N&k0*{1d zsn$wzmmV8Ik0;%99q=wdR2j>r07y;|1sD{e<7I^Q?EeP(Ifl?ktICQAD{t+bTXg2+U@ojwgJM6<{1)2N9tIVhPh2t89{nja`u&V*xEd z#9EXJ1NWL}!M?xj>rW2!D`@woHHFo%f>Y5Yoe7|hl%1NYY>-eqcz94JtCCK0GQ3J0 z)}oX^(sQnWlap6q^iX78IfR(7WRXRxLx=MKvQ{L!gxtGDny@BBPJ?Z#rnZrqsscN! zBwyZYJFeY_Ya#t>Vbc0r2y7^v{!;80JHZx9RDxV7`ou60 zN{eXOxrYyFI&=c|7(f7iLbJAo_0cD4bg{;Ewnm6HVY`xlB1}TAp7F|hoEM|1LX0u_ z)PQU9k3eWbMV85E6Y44xV#RxRl@Oh%2SZ=yPTCD(cnbj((8?;8s*J8sP|z*xp;P3@ zJgLmrL3l_ann6=3BPi-;U zA|g(X`AG7qa~Pv1(;V@hl{pJI(w}2Pg7r%vu*;EUWr#{i2UfTix0wA1mgyjwOFs4`?G zRlD2BU><)w8gX&b{@dsi1u+tEG-J?Z&vt@fLt7)B>D4!-*ko|9JO=T6pz|Xa!2BI6E*+0caF$MtEZq2H zyC5n));?GRAz3HU9W%N#HUVaoNueKU8Hb2szQ!T>+wmXX)gUTkV)A zxQ-8V;SOsnWsC^qScS}`l5~@fPE^F{RSY5a@bvMMBl=9r(aT*1rG7F@p}~>9wP4)q zKb*pq<~2+DKm%yoE2I)>IT|}N1`rh{f5SGEa-9?FzX)Nmr0+FCGZKtUj}-3s1zV+$ zL`Q{KdY~+rYdIDWGI>3jXhF{$&-GKPP;DKka>u$IE5ZDy?Vhu?9Az5G)`sbz1E?Rc zdx5l_D?78}Vod^3Y@jJy6~Ire4FweZq#W&r&nI4;Mwwue$KL7|3{X^178&|7Awh~N zN(p2Br(Di7`H3Peay)Uo0F>oJuu7~0Y%X&$;)=zum$Z&XcYD(-dfsbzdbJI_5}pYT>jzu<6I-@KE3GnUXCN?^n4^{% zTVW##CXdhKacU_GLf}&5E+F<7F`79{h|=H=(a^1L5(&=`fu&nw==$mYzN`+U4IsZj zS)uLL#Y72-k}?&bUF$NlQ*Ec-;7W!oJm@1A196h!0?-lG%9Ik0KKn+I!#S{bK9z)Q zn4TDkwsnm7lpYqjfPtD`rxE2q84`37?u|CQ<&-gsHj{y!7+MOUj=mfyiI0BJm6MsF ziw?w1EV~0z$c4x3)Q(y%X1^12wpZ3$MHl7A6g<~(?>!m782WE~?1AWGI`L`6)*-m+Xc(MYk0x#&e;DEFel zL@M_RD41xOt*KSJ!GnV7^>23D!leZH*pd#+G-<)gBn1hS#=o-6=z z*Dw5Qli2+YG5?0%cj7#c$-ce{)V_U51#~9^3x>n9nTlg5IE{ zWZM2VPA9@k+XH1{UOtwb7N*lfN_>n`%n|pWiG4q3sm4=S^CW z?3=C@{|Rbx8Elqea55dTF9le~MUtI~S^a7)WW^0d7q<`_$o5HCV(4KBXp8Eiz~i~f z33&^QIj9XDA+`dvvc)HIXhG)!9&7zVJipj}R5Fk1suxjD^VDR0~=rtn#W6be$WN+jvtQd170}Do> zi|7~5ylF>E%i9H=4k-%Ns_lPTcJ#hc0#J2F$QApScS;hYm!ZYSOeY=GCacs-iKq{} z$FWbok)bIuW=TqlDl(5L?C~a~_#^o0NDYPn4GVFb^5*s(8T*oMIYH$6v82|0N$`8| zC^iM@=QXKZwKf|;hLfe8wR~*SLbP-ctIFDnqqLgoi#EV<*UUF75Tmn19AA{9v&PGi zLIkG(5uo3zt&5WtiXR_0tf3Iz4;sp>oC<)3!s)+Ym+WV#E+a=OZG6wVDs= z2VgbFn4T{jbSD-ikkqnUTnBZ^!WSbc^rIC9KRl6$#5Q9A2;I%+pnrnxDTFMIcH4Li z#d`6zCx(BpQ9~v6%5nOM#h<(tj>(QwFtr2!lFdV^H-!}h zvTu?8Urs4vwaI8vM?E8_=Rj`qkM- z4n705Aq8Ep+{n`aT*s5L^2GMeDG?zRgaZVSw~NJ464a)VITScem>5lDtu$T1TYwn4 z0VM5&UaQGF)~Ch{*4LkR72j=>Zs+BquSuhhq!_Xd1XP4n#-~v{jB{&`u1}}(f5dd{ zBUUCo(uQa!Zh}R zXzDmHNmjiMowxIG&<{PdqC`KP9LAXoR5KkKP)2*W6L-k%%WEfKYuBFGK6U?1iyvIR z>AkDlkEPzqujex6smCV||EisR?zv-^9(gCqZQQj#I{%&@#0Hw*8~?>U&wl2npMCa~ z-}q>J^gDkU{_q!m?QTiJ<$O7@6cc1l@aA$mWzf4hlp>MwO__{nsh~5CCZ;)q2My`w zz%dW)5UU>fl*P@5%r4Nk0xik5~w?6g={P|TMU^f5abHDw`Pv+O# z*qrQy+ZXWB-<^IZo&CnfrjO^}p&xZQOldJ-OvSUQZO-IXr5i2%28bb!wPh3J8x!Wg z&8?YZw+f@(`52?RJFaFsv;6&N8fH5YE02}hE}Ae{q~OAh{>td)n$c(g#Y@{eUmE^Q-YR;(cuN?MM*n5kdpBSCdQReI`tRwTRnNz0 zYctMkd$5w1ZN7%N)UNC@H$Y?bHiOdW%23j>ks+{!BG-Y6qzaUvu&+@|Havf$@7uV@ z-_I8Pl@;U$E@D0)8?1(hgAgZNkKlQoKkmfD3MO_jGkXkq>xw`w8iTIJ-M)IVI-P#_ z&9s@`Wq$NIQ%Abv1=!uOt*bh|U@AM?+d&{hbTXR3Em>44>R0 zyeK&gZ}#LFBdwd2gcICKh1p^tW=tvba^PnJu5^sJG0obo+yLb8V+_Mtk``_|sq@5GV@*}W@O6XVPtaSj%}{^+t{ z*9Mpx^!Cj~K&*bLd2aQy8%yqq%ZZWnPFs8rA6I}F5L+R|3&bQ9w8GYSqTYHo%llvh zQ8ae>X{I1|icLakusj?`Y4vuEg<-wA)~9%JAL$e}GX&G8=*4n|hi7y4 znS!`>ce)POtM{k82d;h~eQEIwq>&q#Ez{UpZcOHj^r@$hZC+cnv*D$DUw`be ze?9c}rJ~bzpo}cRn%Vqa1jFsRW(1Y_qlVeJg+hb4Xikb|k)I33Gq)D@X7zA9cWZHE z2lJ*kOxt!CM)n3b-mCp^r>PDHJKs7|8#lk)xYZ*)0{F(4Mz(kCn47$`sE=(m;l%8! z?fuk^mD_uI`}TW>XUvb*-#a|NaBG89-*B{AT;6`ip}I-F?gf}5+tL{o0N~n6|NQNv zRr9^gPnUatE+v3N5h?i`+Ig&BT{Lsq_aSHv)GwI{i{;xQ7VdTQ5iF5#4 zDmkPb_4KA4S zOH%7V3~~B9Vo`^2?D)U*=%3+e{TH_y+;yzF+7D|%WV@}?UzE2d&Z=d=`^FEG_M*MJ#}+cVPe4m8nz_g? z7sA?{GDZpve2I7e4$N|C!js5&eRb;zVYtW_!EkxB%AFYR7K@i|n=;9R7MVp-qzwu4 zrj2XqxMBXl%3T=x7+XiB`UedN4^BN92+Jpv-q52;rHE_JRn4A?pj4}K_N&H_2YOu= zn)sS_?dnG$`W6Aos*F&|u4?aE+88O#telO{`#!GsY~m z23}kwp?W1zg`y^y%zNuw9>z@|nITd3BX^GATiejA`Zpu-`jwBs;-HnC z7EAH>ZunNv!lTEFEwWGxLSY1HOWOhC8lp9)82&NsqT-E0dni`M!kjY`{MjLt1r!8# zq%0lkh!;aV%bao$YuY->iN*h|6pm?+A>bu*Q~3Kdwqu)ISGN0WRmg#rD*AF;Ai>&Q zgG5(^8+AwjSd2K2&)K8M9pLNMx3@nsOSIi#S5sK*Ow?HasTVKsQEb}AuQKt}80 zr!r68KS-4l8|pd+58@V0a5)#C7tvFBUYl002u#b^xhSE;@M@J6I->p8)Hh0b28*zQ zJCexg;6-6;a76`~93qKHBjrlkcDb$`GJg`0C;7JVx?E>>gLyQ0ho*U6(EOGK@=5|& z4E&RlIapC!=92h>id+svTd!v&TQb!nMXPQyOtrm%e4}R?R)Q3noYE)QeBewHK~bT0 zHT3?8`xniI0)r4@oY_g#BeqjuauY3yp4|ID(8~3cv=|AXyl#P^9BrhPY;`j~trakK zZ{dn=@lL{Ae1vh+8gmqqW>M}3JS0PM@`*TPmh>p<^b^2TcEo8b($s|pAj51livx3I z*!8GU>a;$=P8Gd4;4u{*Z5@I6)trALm(E`?wBSl>wE7lX*bVm_9o`RKOOb*IZJoRt zmdJ^B2+mrNx2P2?xkr&*7Fe1jykU+oDH-UrOb29L0yBx|l5~hoTD2N2a$_#aIJcf? z1bSsd4YGvxDf318lw~yZ)M}VUsS$c#Hn<;5T_dA97_);VwQ;9#=5UlmwRW(q!k@8VTK+e%OQ4-C1tt@s^CcYMBeWg6x!!=oHVQ+ z>G%~Pkl+<9n->j&u9_my=W3}kxrnTwL*4vRGl^uF%q6wqphikT*_we&ib$@6ASAbj z3VDg>m2?@@#u>J$G%b#dIF&Fq>>NH*CJNw)v8N<0_wF^X)O1N4$e6%Lw~&d z*`hL`$je9Iu*sXx6UY)Srm{})Aks|Mx`yk~K#L^mpW-J1-YO+0vE(KQ=gNQ!9*=$y zlJZp!QqMwZ@yMN-v)1`(uO-S`GJTw=4hh8Z9)hp!6ysSt*(PFx%+7BkR0v0s7y#+S zGS&cL@lYyx2!>L705P2@=*W(~5mbu9ppjyJ3|qTV_bH}O7U@DoL5(b&tJ?QVF$eTR1q^ngxD&H!r?6Mrz&y=m z0nFocL)|%4V69a=wgW&VA`Qk4C0k~MiXvKXm!f(neQ0K~Q3$5t(X&tDUYa#cq12jM zUUMsgh8$7*M1F#S`lSJHFGRGIL>n9n_DSKg(+S6*%u+qVI(VP}yo<;B<GI-et}`JrzAP5pqr8!GIHa3e&wL1~pKh@6pol9J#r*04B3L(Z=IhGzi&iORaHfdC3PGzi zdOblrnJB8{tWczD5Ni>`IDR|W%XsWf6TuNX8x)&T+}^9~wzaAZy*MdTxkkg_&5ERo z-2{!oWc9i1hXY?P@W zFfeASEQYFbp*V&r7^xw5whJ&bQi;BvF({-OJl4hdGDh?=jE7P_G3GjqFMv34HvDr@$W;{{LI5@`JuW-E4&~#A}Qb%~>B|+pOj-UTnL)<87Dw7xA!OPRm&K(H_)QO2-Y)*dnz1f48?X3Ud?XI4E( zx(nU9{$|;PetQthftri%!k~0^y==v zQ^3NfHPc2)#X2>wy6uijsHEp z`SmC5n{)K-yUGLsG$9sP(o}p)E>;S$u*y{Tak_(mIP_t#HA}I^2(e_~mufFhBQlkx z>3}zj<#AIdHC3O8hK284oL}ct;-g2`PVSt(La7}S_Ok6uc=ykQ;I@DKU{)4yY_j;q zyqzeBji!3qY)g`29D9d};!QXma-0)4RNH{d;H6`jwG|153IYErtm)9YkWE5eJdPV? zxAkzNefIoTN11bG9dJFKv~0DV9^d{>7tV9(%Cnn*zayuF4sSl6cbNYD!*`pHe)iRk zgEw~CvV%iUiN>Jtxxsi-CiDd(A{49lx?&EsZ-;{(W!S|D0#oI>zKN{Qi(!*zz|QWt zz2Ji!^)IWhes)%0AvPzgB|`*=X|>sGzTO-Kb~u5{x=el(`tH`F^Uc}gdIk)AdH>aa zK70D}X&!l>Iq|Gep*mJ&UBRNU%wCNhyQqw-T2zk9ZZu8|~Guv+Vi2M?tEzeG$pY?aH!+LqU3GwmM zM=$QaSl)T(Zuw{@RZ3h&iT~R3&aOyjcCukKc_u9<+K{4)6}& zP4JAhx!F8bYayNf`!Zk)x^ZyecdyXx-?Q(ZP;d?3`#eNWx#!lBOss zn5sz@MN(%Y ziB5gDgDsdAzYH_jm?#WB7xA$`A*(Sv#Me^A!$A8rScN69E0LVvi1QM~VUdtN!V)k= zVi^TB5=dPQ9N~vdHVXJDG_%M(Z7h5^)Dk9@*yspU6ID>QkL@5zIzZ*S1)~5KsieCD zp0l<}MR&$N?_`KvCpF`LobGUa zxTfRDk}ieBcPFm>ZX!U{(M?U(hF;7W+_+krJ1`_Av(h~h?A(IP0um8~B|?1lH8II? zgt4OuXmxK2qH-4>C+2*x0#{6rGFMW$Iwu}6J;gc`q@Q__AUEVO0eY0S3`let_K~aq zh@CC4LH(~>zolndaJiRrXx$7!SH?1MpcP3SY#309ud!&W6=`3?|CJzKsob86XfY8L zfaXFmJwYTnHsn<*tGw>II^GJYZVGD?^V63{uMSTyo_&2cZC-wJ@$TJ2Z=$$UcF>>MNStm2 zmJylQ>(UAc6t5ro1H#gnGosL3_ISX;*@S5HE?{ORSjeDUG;zj(TNcpWeQ z<=#7AJ^o8|E`9R@zvb&I44@$N-Y7&BuL{Ab=Bo!3mF#|X7Y0Gvt3vjuned4GtV9i0q?Q*Z|9nZqalhi`E zb=*AeuFv529&(;vH|pho#8nFiKZwiIKVCrm;PG!in3G0!QJ<+}P~6DZxRcQsGCZML z(`0V$u2V*@&ymq>#uqus1i`fsT2Jw_Cndu;bhH}8rg=?UZzTN!gnA7vjWY=m> z%0+rbyGXYWX%*WN=G^)$q$V!DGsAZEo6mmK<6+%FX7DO^ZRT2gjBPTG@6L*cDN(Ms25}+e6Yqj_3gEJ8=BRN9--$#aSS~jYUY6(6vV-$z|ybXTF9ut zbmaj=bq7<)ubhCTN2bI5mg`XF3m}tvbU|7n9{9iv6lTe0v=AWd3vor>Z*{i;Cg&99 zah-48>%wX&`bV5E>u-}-KvCCX(zA!fY3kY+*GpM#u9dDX88hZ^r|l?8&P3c<4|igE zr5^!|WB}%#g=)21N*(|jp^7sWldW(B0-z8gCEOQJwy6q`c=oGHaLwr`uPiBvgGa#4 z+E&RD`Mzx0h;hY_#Z->)qQH_NFbX6mMby)wz4ztw^y;&Nle%`kMBKpE#LIp&jR-Sx zyq?g9mRvi~z*5cmg0jrbAi*;#nb1QsSW8%g8eoQ@ut}s9bt#jk&6x~&w%ew3Y**f| z=Pj(MA@-_fc(8&aTp^ro`)S1Ih$eHEb960A{>Y1s@*?0Pq*^LHA42!&-bpU=7<~X z^qJqBhJ5TQ`JYT5@H_MRX>nnX#NDwd_CK0)-Oh%@Xk z)|g>4FKw7Lv2CeCeTx)Tl0U>~%w!AJa1g7f{cteup+1Y<9VI?3pH!UX3~iF%10JPf$24ba0w>p{W;@!rNMKY6roxt4-3POd_K+S zBoLpHZBlJ!5;FiR4XJx8vXoN_z5XyCYB6_h$8oX-1&sZcbzoL{udY zzAM;0}zr8yL7yS06waPP4{8@P$rNs5}_b`cL%^dr?Wa zmsY+rCmhnEde>`?OfEM3Kn&yQkepeB#47+hz-KCY6ly*dlJsd3G9ndE3O8y7a!2VY zwM<*m5+~Rx%&Cj+?!`{?AwZg_E_oP)u}zW}NCcgC={1@~4<9XrOyq?;HBRNSW-!i; zG0$afvkDzWMq4o?TQGqs#%wOF#Z59%04C;ZEul{-Ya13Z>%%ZAZ2RK!+F4}604n0- z7_K`6!Vsp^sPXp$_BROF6x3l4L$wMmh$@l)oVo%mgfbL2j787iqrU}yA=YfJEEZg^z@gb|vh z3TpYv{A|z!IH0(RT#I4VdWB}%Ayj^;PVy)VXpwZzgGtp{lp<+$(ivS+qIHw#9a;Nq z5RG3N0U_xRsnvKdGdZAS)MG6$BPzA79~m~#pUs>_uiT5oNex-?EX?e z(iTNNQun<#cjheL`ObH=plZ<_C=repte^?_Jynt;ln(S45cQK8Bwm~|`D%_yNNm7L z)(SI0q!AkhdZ3jRs&kZKg5Lpu<3dhsEFzYuJmEp{NEtm7na=J!Hb{~L_+wZ@9(zK% ze4=#Bn1W*e+>Gm@H!cj6h*4t9!F4Ccq)VPU=Oa&n*3YqBH{?bVPa`~~dKFP2SUO9x zGtSbju)5N&qA4sc%g=2=(A@{o{u?G-2TTE4;mL)4_ZmvAX#j)5S4kz2x0 zuQ4oS@{yb)u3RY6f1@Ex=%$^c%ktl_xG>xu1Y`h+2rKF83j42pg_t>H(Wz(AB-Qwd zCX|j)@UhrHo*U=+1k3vpy_aY{Jf>G{Eu9=)MrLF=Bz*LeNO?9A29BYL{lxK;mwpHu zu#AWgcBN;8umR#25<6_|sZ$^yC7Lu(YHT3IOE_Cw)Rt*dOr-=)nqAT331gyDWf}rE zb5YB_iXyTyU9({Yk}#rBQ#}orVxUXv@8P1PsbNIk2mOPl;fG$v;*kC+FFLSFK1UT?m^W&csw5_RwF$VU9>m~8ciUi3C$JbMu-Ao zBu@n2Xnb4*Y_#WkvEjfH@dHKNe)w!9xP&Hj`k@ohk-{dDeaD;_)M9F!6=^WfbL52~ znj|>^Cpo-sj&Q^i;>YpCfwTZKtc{o}C^-jFQbrJ$Sg_I2;z?ndUn9nnh_O)c;^RMM zsT0!oRH!C_KMQMBLr{pc8w3*_X>&mD4Wii&W;Q5jz{VatydW$kOtTRO(U^w_5c-*V z#M~FKDd)$Hr6p*_UEpL$24Z!bs(5an;qA$i6BPBb<^&Qlj*i#6I;Kg|HS@p5m zUgm6S*_&Rndy-p(lS&&C*fCfuaUhinSFk>zX&Vrv!^(Mr4N?2aVxW^UmKZqzD#lCA z$<@cPf`+P+F@cnj248@dY8IiW9G2m4xmXRQt*hBPvoa9_gH%2vbB4Q09Exgm(P{OK zfQ(csBI#eWjX-1ttzMufglH%MwmD1;2l{$gTdZ=&@&?DL>lj!9S8`+rD{qAAr0{rR2A#)Vd_eZ~);|`Q}>802ANZO;ZY#2pKxda!L>qU4~G{|PMYNbSwjCL=+#|Bws z?@W*2BrwMe;jozy5^`8N0-hV?D)C=K2cJt~;{vR!kL8&om86)2d;_JK;{wtpwGx3- zmV@zewJ1Z7#}&));DKWqO716#x)n~EM~E^kGFqBBNED1aHVFbSqV^)}L~OW54xJ|< zY2251MME-Nl|p$fWkF*f$ge00<+OfkalWxq$B@gJC~4xv{OK41O76;_pp%)mO${Ul za1o4##Y#Ny31%qh8Cx!Tk4Tk^BA0~rqX__7mlJr0-f-e9=$MCumkP(Zx0TImK&0Vb z*_uR|XI;e+g!-xFoNGjFL~)P#mHg~Z<~B5I6?@wvgPch)f*W{9xTsV?r3=LnmUO&= z$!8dXbp$<&Co&2y`&~;l94uf$F3Lj@oyR&m&~~dd1=1eittdkHe{Vm33bT!zH* zTyVFVqHGXg#KU2MsEL8I#o`MPGy$TSQICre;gQ{VJaAU@9!d&oM5dDNO#UB{C0Ztq zX~Ej+5K3q?Xpjx+Q3Ck@@23rSpfZ(9f}sP_zPH*YSi3R~!5H|9Mjl8= z5VaK1ul?1Lx9zl#ee?%03JI?j0^d5o4~nG&YT zNX=J7Is|OR9b-gxOmm$xa#BsSL4@eZxDi5@Xvxu17|r}l?oQO69AL!2ey%%EF}a9CVLptJw?^@Atg#yq*=v!L!U@(nt1O^R+NA=D7nSq8t+kb|#!zNcU;{B_=a!WF^~kk?*qB-ceB?zyF6W5s6{Qs5lhGlw^R-P(2n2B=2254LLQCY`9Fgrfx+kBibIxN2 z&T7CYGUtkqr;+|8)LeJT*tefn*s96!s4aM7{tH5oc{2PvgL51uB?`hBTNfAh#YJW~CL_?3Lo2!74&1LT-Alk+4tjxlJXQ>1iMfzmpj?syf+aZG&6{mkBx98HOcfnv1Uld=$GU8Sol zPkn=M(rYoCk_%HDRF0=!#T*wdHsoEb2bj^i&C1nZS*%h{KP$t!^!LN3{TnVEP*?lM z|FQGcuZ9MRFko39^~1v^wbm0E%vHdb@x$L-d>tYkZ>I`c;9WIsDOY`6U)xcB*M4=o zp1U(i8-BiyI3E`%o95vepF;am`h|fX3M4Dyo>9dY%wqy14HJxi$h5-tsdLZ(fKecD zgwkL%@^Ip^iRr4Au3a8hX5zEbPj$DdTeUx2G-><5xV9bnRoKRhbvJRHjp=iD>q~pt zWnboB*3Yj^zL>u8+4PMMTRW#FZ{584H|x9CfAaa8|NQE~Tf=vMzO-?zy|n#;xpVdA zxvhf`YJa}+-rK+anGk8`wgYK-IN*%!{rn`-5282+<8o&#qd-$;h$O0VI;qOC_Pz=q z8a|h0HJuFM;gMC9SCi^+Tu(c+t>mpAX)oqLb2zni_Y``eM#hf<=wNlF8%KJ zj~Dj*^2HaFDqp^R^YHEP#xDKojfPwY@9t;$ROkE~f*t{L3igqwvZD(4jM79td zt6A$#!|zn%WNo>6yrguYvs0!X>~=x0I+*H97xXxN^=#*v{mXxz>)ib4`lepG{BOIi z?ei>HxAyeMt1kR*rV680Ki$fd{`&B(k<-)i@!tOOmiHaMq?YG=fEBQK#Hz<4r51LM zVw5G4oHUaKIlBXBA;oGLmkWo*q-yPZH&}hLrVs46I9MDk_iK}DGk9=%{OHmb{r29A=6C9x-?Urj zR#x|S)~{YSuWfZE>*JSiZogEFMyo|}qI33KueYiW?_527rqg<5>A909duv9W$j>L| zo7SN@edtcpj;VPcdO`Ig+k+c9Wm_7NL-CeUe(j6 zY%6j7R;x}@AZO^L?wCnl7j_|?)a@dC>ATMLs@!(-v2jHWgXa@aVsy5pR1X9bI%(N1 zGSEw233jbWaWG__f&?K>S~rpVe(%oPO>t+&`$jWAuLYdBq%D)GngjJ2GYyeT zVN9B(#$;*Yvpgxh$zjAeG>(MdOH<8}EWK3IEEqoUR8R3-K8yg}k+=_;j;bS;ym~LejlbBO8gp6urhy;3+lTakLSJxURFxODSM9fO- zDg=DWR=^G%(a2$24ky*9)QsPT6!4yaS~5F_47u%p)aUQ#wt9*wzO;rsU`E)M5tL+D zM)?_-Fg)TaV4E=zk8vRcU@$gCBkin0F^HJRRf?h6NJ#*>Hti1g#!=n^{@C&XPbQPa`C4?~i&dprEQgI`Q6LS&=AzrXi)hc$9QfZsn^jBq~v4IJu_ zD&O4xLEBba&JNr>7ctdJl6fVHb?hJ-j!eTi7d93cMGzBW#Kdxe3`Vdd1cbRs*O%0* z1z8x&2`Ctvd(K2d|S)AH^mRfL1Bmbx~*J?-t)7pn(vc6T?Jg6gSBk(%68|g zpZJzOxP5;6^v>S$hd(v{&(-?21>~@Wi{mqR=uEvRQg|I>r_@zl2Txl9dBqx6r4Y_k zA=z>^que;GZpa}LP;FB!Ulye)k`meyY-<>HRy&-_Wi?KdVYmUO`l>iF+9;C57Nb*J z#fgWfyY-hZe)6n-DXAYG{Ll?H23PN`-%_`iPUy|;qTE}oKD<)rA#h3U`>p=Lf4%l$ z`qoA@*}R-yJ9Yi1%5Ckee0lZSsp`VcGn+SleoE=gVBu1Pda|e)5fxS&3r`E#-=zA!`3gyGH=lciA_~FOB{Ng)*wJ;fUr<47aSAY4| z=#P{y)6KIh?!|w2-AvE?PycOipBe1_EOC?7i?3~7t$)TlJ0c)m-_ z;F-??>UJDKt&NMzBvcF$&Y@@I(E^k-V?i64QD_kp?O6K~1CZcR5||V0vhZIO0=H@E ze9KgINK8VOg43t!PD>4wa0c?K>&msFytlqQDq6#$PI9eQ%x-tuE!WL-+FIx@_FD_C zS|2WyE1hy0;=WeBXnT6qqz)9l)9^G2O3S3N;aSh3GCC#(YId8!7vU!<#D8IX zhD)WXCv9#JHVFYv5e{eu-CiMg%kz*%mQOa`P~%``SDa76 zK~J!Y1|lPXsuHReJCx&2;Z^`mrncAt2C}|WxIZDFnOFd;AA|Ycy#mO(w$+yds8PB9d}JrT*w?lS z#`4!2VfT-3S0RwPwZSiXc+HQt$r0jSSj6ki_XF?|A9uTNY6_z1tA&E0BvTg5A@bA7 ztXL_3_$ownRZ5Py%4rCs(p8mVOQNoZ?Manm@eQ<*p|c+{IaG{+n`cvJQ{x`D+8tm9 z+A2v^RTUu=Z}-Nde4|`2>B1l+&uZ7zOTWKzu(eWdT9Hv+8d4ICWV=n3~H1=AKo?_^Mhrc9$#aShFBwM)MpwUk+J9*jr^d zrWG(#(vWCP!+ZDE)`?9jn?OM*1Zt|(li;q2?FR!}^V%eni5^sT;tJa@2E&f-53BL$ zP(SPso)mkByG!=IvXkv68>@HsCd&u!9e%xcZ}3I$zsLPeb<$t!{;GcemGtq~w|xK1 z2ZQk^r{3#sX6N$5e|q6xU-@Y9#t#edIyCl^Ba2V79wAZ5lUCsX1`5$m z8;-AD7+Ia=PtYMHFoL;gp+bBGh`=PXP<87NtF12c9q+4AS(g)4Y(M#4xp|}9dG=pE z8h!8G+nHY*b%sfDb@SE-W7rt$<04-!@3@!EU*8K3{!H)0+Q~U{jC99|dzIPrF=`fo zaK>I}4igRWX-lB>kQ_fs2!Xp!U{;p`%311M9$aQ_Cc&;*T*V^9BO}|I>bwlGM|WH= zwrsys`?a)hdk`;> z$3E$t81#lky3}7aNt(7p_L!nFd~Cd+7pBuJ&AI1HEu<6||G0v-?;!%>bz39qCni9N17}K_t(hCSE-pBJ3W1zpQICx~>L^-2#7HpVr5&dhQxXdYs@=FMaAe z3W&liTAnmH-;Dx4+<;Mcub?Ki=Te}7G(cxG;8*sbh^LyX%8eAwd~NHA7^XLH2u~YE z7)cRO4a$hoI)Q2^ZVYq8NJDgx7f|g-BVaK~TzyxQ4HO;|^k*j3S>(Y+0==RGuj|#v zL<$P5=?m?D+di$;KgZYJX!DZC@W!Q?zMnS41+ZKce@lj;Dbx%E_EIp+3RWXJf_F_Q zfETrvXcfKgoXzdqVY-WfWzydM8RIxoK1P~4HxJK8kJH3CQgOAin|`iB9s{COQhrNwR0QBN8}G0LwjW zfR(rxFlP2d%UnOVuTdj(qwDw8P^&V+A)Ix~e38@L^I?5B9EU}>_2cTbyZij;UVgKF zv{>DP@HBl>S6%t~c(Zb+j~?9l=gHeAT9B33nPOCcP z&CRD5O5}BduvK`p$`%YhXrP`XKk;r06g&cc4Pfla3DFuJB@F`I+e5C@kcL4aJRd^O6pEt{rS~;{Pq4V*S&=P z%j4mvZ+k4()3`|e?0CvV`vZ%{tJ6PxMy0zxaL_>A@>*}2Dy0>oC8upFpfY)>f$dbg zN|h>jfobqS4V}`ODFWfdedaebRKCU>DK8Fiv{`i?ogd55t!nJG^Q*4JZpA05^NX4S z$AfhYVj_VaxrH+|*X0&f!zgxv+@d4fpw%k(u=qU^>}qr;Lz+(Yj+gxgIqocGIo z91a}!+}&b#SkC%!IeZ-QS^D(t`lRn}Kf9AZxmevgzc}12(&~%7d-XX^;|w$#C(xUr z3l0rl<|Y>g6tKYdCRIUAIBY>L!C0#@wA*TddhpVr=EccOn!!>&%yzJNK}CY%0KrfU zQZd8&0k)V(y&NllHDts5KH~iJ@#+0#4nB1bI_zQ8i{N@}635h!H421v={*j12?6`T zA;LifYzbr03)|{-LHk7ztuLpyAKbh4f_Kb%{ROB?utdqm1Y7HDx{8)roW##FIw^zd znZ3naEiYV?rua&)M!74mktVP@mM7`nz-2>hvUgY&#d!L&SS=SZpsqhJINlHefY>SS z{SvUb9y*+{USERp(u=LA|)4Q-r?*LoyR!SP0$-}ZEms0b!?~yp0VF84&>Qn z)G!h@5?E`xp|Z5;PTEh!c06|kW+ry3KcQ3}hWN6_@{PNyaEN-{9|ep?5+UD>=+5$_ zw8Cg$CsGlSb?A}$A87nv)-FH3WCZ2;fUOhlE-Fm5Eu!l|e$8PhP`n76MK&a`I)dY( zjIg{6Bf&T$9mS_wS$L5Jmf2jK03M2#DvCIJ(b!?u4U>?IhS?Cfo=Sd=gcER4p$r_$ zSTYAE6Ai3-orIjwamQ~ECuRbn^6Pv8d9^52Xca=ZMqEADmb`rPEXGLZEKT8Kxn!Pu=va}mAnT3H23{Jc zh(E*(Wh8!6K)^J@IYoLqr{K$gzj$OBQL;U;f%5#UzhWnr$<_Y~W+s~M)4HCF1D`iL+PUD29}>kGE*O1VD5yiLNgT#onB5Kvt`?i?%STW1?L1(?plG` ztmlkdQ;OoYELzCY$pfb%F5KVqUWB+X=F18&DoX7w80m#96_D8H5%4K@ zq#HlTCq!~rNqgKGxU2y?@|k`QV$KxId8{9Df O0000eB literal 0 HcmV?d00001 From 75ac0eeb170ea6cb7be598cea853b807e89dc857 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 2 Apr 2020 17:25:46 +0200 Subject: [PATCH 20/25] 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 130fc40f3f..109631ab8b 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 a468883110cf413ba6380c29e0bddb41c6356c74 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 2 Apr 2020 17:48:06 +0200 Subject: [PATCH 21/25] 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 109631ab8b..6daf3a8ed7 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 309716eb55..f992ac35b3 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 c04c8b73a9..6fdc6d19b1 160000 --- a/tests/Images/External +++ b/tests/Images/External @@ -1 +1 @@ -Subproject commit c04c8b73a99c1b198597ae640394d91ddd8e033b +Subproject commit 6fdc6d19b101dc1c00a297d3e92257df60c413d0 From 1c926703a87f93dc77b55fa1304074a1ae59e459 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 2 Apr 2020 19:59:24 +0200 Subject: [PATCH 22/25] 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 6daf3a8ed7..7b3e10b0d8 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 50aa77e3a002df178afd823d4b391e9b5ea10db8 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 2 Apr 2020 23:15:48 +0200 Subject: [PATCH 23/25] 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 7b3e10b0d8..dd8833ad96 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 ee016f61157986fe50c50ff13390d6c485972050 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 3 Apr 2020 11:48:15 +0200 Subject: [PATCH 24/25] 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 4fc78a9588..3279d96e3a 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 . From 5754f9493fdebdf9cb32dc3f54bd57a14c9a227c Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 5 Apr 2020 13:30:01 +0200 Subject: [PATCH 25/25] For paletted Bgra5551 alpha bits are not ignored --- src/ImageSharp/Formats/Tga/TgaDecoderCore.cs | 27 ++++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs b/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs index 7753b916d3..816a472fb2 100644 --- a/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs +++ b/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs @@ -252,14 +252,14 @@ namespace SixLabors.ImageSharp.Formats.Tga { for (int x = width - 1; x >= 0; x--) { - this.ReadPalettedBgr16Pixel(palette, colorMapPixelSizeInBytes, x, color, pixelRow); + this.ReadPalettedBgra16Pixel(palette, colorMapPixelSizeInBytes, x, color, pixelRow); } } else { for (int x = 0; x < width; x++) { - this.ReadPalettedBgr16Pixel(palette, colorMapPixelSizeInBytes, x, color, pixelRow); + this.ReadPalettedBgra16Pixel(palette, colorMapPixelSizeInBytes, x, color, pixelRow); } } @@ -338,11 +338,7 @@ namespace SixLabors.ImageSharp.Formats.Tga color.FromL8(Unsafe.As(ref palette[bufferSpan[idx] * colorMapPixelSizeInBytes])); break; case 2: - Bgra5551 bgra = Unsafe.As(ref palette[bufferSpan[idx] * colorMapPixelSizeInBytes]); - - // Set alpha value to 1, to treat it as opaque for Bgra5551. - bgra.PackedValue = (ushort)(bgra.PackedValue | 0x8000); - color.FromBgra5551(bgra); + this.ReadPalettedBgra16Pixel(palette, bufferSpan[idx], colorMapPixelSizeInBytes, ref color); break; case 3: color.FromBgr24(Unsafe.As(ref palette[bufferSpan[idx] * colorMapPixelSizeInBytes])); @@ -723,20 +719,29 @@ namespace SixLabors.ImageSharp.Formats.Tga PixelOperations.Instance.FromBgra32Bytes(this.configuration, row.GetSpan(), pixelSpan, width); } - private void ReadPalettedBgr16Pixel(byte[] palette, int colorMapPixelSizeInBytes, int x, TPixel color, Span pixelRow) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ReadPalettedBgra16Pixel(byte[] palette, int colorMapPixelSizeInBytes, int x, TPixel color, Span pixelRow) where TPixel : unmanaged, IPixel { int colorIndex = this.currentStream.ReadByte(); + this.ReadPalettedBgra16Pixel(palette, colorIndex, colorMapPixelSizeInBytes, ref color); + pixelRow[x] = color; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ReadPalettedBgra16Pixel(byte[] palette, int index, int colorMapPixelSizeInBytes, ref TPixel color) + where TPixel : unmanaged, IPixel + { Bgra5551 bgra = default; - bgra.FromBgra5551(Unsafe.As(ref palette[colorIndex * colorMapPixelSizeInBytes])); + bgra.FromBgra5551(Unsafe.As(ref palette[index * colorMapPixelSizeInBytes])); + if (!this.hasAlpha) { - // Set alpha value to 1, to treat it as opaque for Bgra5551. + // Set alpha value to 1, to treat it as opaque. bgra.PackedValue = (ushort)(bgra.PackedValue | 0x8000); } color.FromBgra5551(bgra); - pixelRow[x] = color; } [MethodImpl(MethodImplOptions.AggressiveInlining)]