Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Defines extensions that allow the computation of image integrals on an <see cref="Image"/>
/// </summary>
public static partial class ProcessingExtensions
{
/// <summary>
/// Apply an image integral. <See href="https://en.wikipedia.org/wiki/Summed-area_table"/>
/// </summary>
/// <param name="source">The image on which to apply the integral.</param>
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <returns>The <see cref="Buffer2D{T}"/> containing all the sums.</returns>
public static Buffer2D<ulong> CalculateIntegralImage<TPixel>(this Image<TPixel> source)
where TPixel : unmanaged, IPixel<TPixel>
{
Configuration configuration = source.GetConfiguration();

int endY = source.Height;
int endX = source.Width;

Buffer2D<ulong> intImage = configuration.MemoryAllocator.Allocate2D<ulong>(source.Width, source.Height);
ulong sumX0 = 0;

using (IMemoryOwner<L8> tempRow = configuration.MemoryAllocator.Allocate<L8>(source.Width))
{
Span<L8> tempSpan = tempRow.GetSpan();
Span<TPixel> sourceRow = source.GetPixelRowSpan(0);
Span<ulong> destRow = intImage.GetRowSpan(0);

PixelOperations<TPixel>.Instance.ToL8(configuration, sourceRow, tempSpan);

// First row
for (int x = 0; x < endX; x++)
{
sumX0 += tempSpan[x].PackedValue;
destRow[x] = sumX0;
}

Span<ulong> previousDestRow = destRow;

// All other rows
for (int y = 1; y < endY; y++)
{
sourceRow = source.GetPixelRowSpan(y);
destRow = intImage.GetRowSpan(y);

PixelOperations<TPixel>.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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.Processing
/// <summary>
/// Adds extensions that allow the processing of images to the <see cref="Image{TPixel}"/> type.
/// </summary>
public static class ProcessingExtensions
public static partial class ProcessingExtensions
{
/// <summary>
/// Mutates the source image by applying the image operation to it.
Expand Down
110 changes: 110 additions & 0 deletions tests/ImageSharp.Tests/Processing/IntegralImageTests.cs
Original file line number Diff line number Diff line change
@@ -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<Rgba32> provider)
{
using Image<Rgba32> image = provider.GetImage();

// Act:
Buffer2D<ulong> 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<L8> provider)
{
using Image<L8> image = provider.GetImage();

// Act:
Buffer2D<ulong> integralBuffer = image.CalculateIntegralImage();

// Assert:
VerifySumValues(provider, integralBuffer, (L8 pixel) => { return pixel.PackedValue; });
}

private static void VerifySumValues<TPixel>(
TestImageProvider<TPixel> provider,
Buffer2D<ulong> integralBuffer,
System.Func<TPixel, ulong> getPixel)
where TPixel : unmanaged, IPixel<TPixel>
{
Image<TPixel> 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]);
}
}
}