diff --git a/src/ImageSharp/Processing/Extensions/ProcessingExtensions.IntegralImage.cs b/src/ImageSharp/Processing/Extensions/ProcessingExtensions.IntegralImage.cs new file mode 100644 index 0000000000..af6d32b216 --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/ProcessingExtensions.IntegralImage.cs @@ -0,0 +1,77 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Defines extensions that allow the computation of image integrals on an + /// + public static partial class ProcessingExtensions + { + /// + /// Apply an image integral. + /// + /// The image on which to apply the integral. + /// The type of the pixel. + /// The containing all the sums. + public static Buffer2D CalculateIntegralImage(this Image source) + where TPixel : unmanaged, IPixel + { + Configuration configuration = source.GetConfiguration(); + + int endY = source.Height; + int endX = source.Width; + + Buffer2D intImage = configuration.MemoryAllocator.Allocate2D(source.Width, source.Height); + ulong sumX0 = 0; + + using (IMemoryOwner tempRow = configuration.MemoryAllocator.Allocate(source.Width)) + { + Span tempSpan = tempRow.GetSpan(); + Span sourceRow = source.GetPixelRowSpan(0); + Span destRow = intImage.GetRowSpan(0); + + PixelOperations.Instance.ToL8(configuration, sourceRow, tempSpan); + + // First row + for (int x = 0; x < endX; x++) + { + sumX0 += tempSpan[x].PackedValue; + destRow[x] = sumX0; + } + + Span previousDestRow = destRow; + + // All other rows + for (int y = 1; y < endY; y++) + { + sourceRow = source.GetPixelRowSpan(y); + destRow = intImage.GetRowSpan(y); + + PixelOperations.Instance.ToL8(configuration, sourceRow, tempSpan); + + // Process first column + sumX0 = tempSpan[0].PackedValue; + destRow[0] = sumX0 + previousDestRow[0]; + + // Process all other colmns + for (int x = 1; x < endX; x++) + { + sumX0 += tempSpan[x].PackedValue; + destRow[x] = sumX0 + previousDestRow[x]; + } + + previousDestRow = destRow; + } + } + + return intImage; + } + } +} diff --git a/src/ImageSharp/Processing/Extensions/ProcessingExtensions.cs b/src/ImageSharp/Processing/Extensions/ProcessingExtensions.cs index 0bf83812d0..ac14d84231 100644 --- a/src/ImageSharp/Processing/Extensions/ProcessingExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/ProcessingExtensions.cs @@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.Processing /// /// Adds extensions that allow the processing of images to the type. /// - public static class ProcessingExtensions + public static partial class ProcessingExtensions { /// /// Mutates the source image by applying the image operation to it. diff --git a/tests/ImageSharp.Tests/Processing/IntegralImageTests.cs b/tests/ImageSharp.Tests/Processing/IntegralImageTests.cs new file mode 100644 index 0000000000..481463f47e --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/IntegralImageTests.cs @@ -0,0 +1,110 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Processing.Transforms +{ + public class IntegralImageTests : BaseImageOperationsExtensionTest + { + [Theory] + [WithFile(TestImages.Png.Bradley01, PixelTypes.Rgba32)] + [WithFile(TestImages.Png.Bradley02, PixelTypes.Rgba32)] + [WithFile(TestImages.Png.Ducky, PixelTypes.Rgba32)] + public void CalculateIntegralImage_Rgba32Works(TestImageProvider provider) + { + using Image image = provider.GetImage(); + + // Act: + Buffer2D integralBuffer = image.CalculateIntegralImage(); + + // Assert: + VerifySumValues(provider, integralBuffer, (Rgba32 pixel) => + { + L8 outputPixel = default; + + outputPixel.FromRgba32(pixel); + + return outputPixel.PackedValue; + }); + } + + [Theory] + [WithFile(TestImages.Png.Bradley01, PixelTypes.L8)] + [WithFile(TestImages.Png.Bradley02, PixelTypes.L8)] + public void CalculateIntegralImage_L8Works(TestImageProvider provider) + { + using Image image = provider.GetImage(); + + // Act: + Buffer2D integralBuffer = image.CalculateIntegralImage(); + + // Assert: + VerifySumValues(provider, integralBuffer, (L8 pixel) => { return pixel.PackedValue; }); + } + + private static void VerifySumValues( + TestImageProvider provider, + Buffer2D integralBuffer, + System.Func getPixel) + where TPixel : unmanaged, IPixel + { + Image image = provider.GetImage(); + + // Check top-left corner + Assert.Equal(getPixel(image[0, 0]), integralBuffer[0, 0]); + + ulong pixelValues = 0; + + pixelValues += getPixel(image[0, 0]); + pixelValues += getPixel(image[1, 0]); + pixelValues += getPixel(image[0, 1]); + pixelValues += getPixel(image[1, 1]); + + // Check top-left 2x2 pixels + Assert.Equal(pixelValues, integralBuffer[1, 1]); + + pixelValues = 0; + + pixelValues += getPixel(image[image.Width - 3, 0]); + pixelValues += getPixel(image[image.Width - 2, 0]); + pixelValues += getPixel(image[image.Width - 1, 0]); + pixelValues += getPixel(image[image.Width - 3, 1]); + pixelValues += getPixel(image[image.Width - 2, 1]); + pixelValues += getPixel(image[image.Width - 1, 1]); + + // Check top-right 3x2 pixels + Assert.Equal(pixelValues, integralBuffer[image.Width - 1, 1] + 0 - 0 - integralBuffer[image.Width - 4, 1]); + + pixelValues = 0; + + pixelValues += getPixel(image[0, image.Height - 3]); + pixelValues += getPixel(image[0, image.Height - 2]); + pixelValues += getPixel(image[0, image.Height - 1]); + pixelValues += getPixel(image[1, image.Height - 3]); + pixelValues += getPixel(image[1, image.Height - 2]); + pixelValues += getPixel(image[1, image.Height - 1]); + + // Check bottom-left 2x3 pixels + Assert.Equal(pixelValues, integralBuffer[1, image.Height - 1] + 0 - integralBuffer[1, image.Height - 4] - 0); + + pixelValues = 0; + + pixelValues += getPixel(image[image.Width - 3, image.Height - 3]); + pixelValues += getPixel(image[image.Width - 2, image.Height - 3]); + pixelValues += getPixel(image[image.Width - 1, image.Height - 3]); + pixelValues += getPixel(image[image.Width - 3, image.Height - 2]); + pixelValues += getPixel(image[image.Width - 2, image.Height - 2]); + pixelValues += getPixel(image[image.Width - 1, image.Height - 2]); + pixelValues += getPixel(image[image.Width - 3, image.Height - 1]); + pixelValues += getPixel(image[image.Width - 2, image.Height - 1]); + pixelValues += getPixel(image[image.Width - 1, image.Height - 1]); + + // Check bottom-right 3x3 pixels + Assert.Equal(pixelValues, integralBuffer[image.Width - 1, image.Height - 1] + integralBuffer[image.Width - 4, image.Height - 4] - integralBuffer[image.Width - 1, image.Height - 4] - integralBuffer[image.Width - 4, image.Height - 1]); + } + } +}