Skip to content

Commit 9959d51

Browse files
Merge pull request #1561 from rold2007/ImageIntegral
Added image integral algorithm.
2 parents aca46ac + 39ed542 commit 9959d51

File tree

3 files changed

+188
-1
lines changed

3 files changed

+188
-1
lines changed
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Apache License, Version 2.0.
3+
4+
using System;
5+
using System.Buffers;
6+
using SixLabors.ImageSharp.Advanced;
7+
using SixLabors.ImageSharp.Memory;
8+
using SixLabors.ImageSharp.PixelFormats;
9+
10+
namespace SixLabors.ImageSharp.Processing
11+
{
12+
/// <summary>
13+
/// Defines extensions that allow the computation of image integrals on an <see cref="Image"/>
14+
/// </summary>
15+
public static partial class ProcessingExtensions
16+
{
17+
/// <summary>
18+
/// Apply an image integral. <See href="https://en.wikipedia.org/wiki/Summed-area_table"/>
19+
/// </summary>
20+
/// <param name="source">The image on which to apply the integral.</param>
21+
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
22+
/// <returns>The <see cref="Buffer2D{T}"/> containing all the sums.</returns>
23+
public static Buffer2D<ulong> CalculateIntegralImage<TPixel>(this Image<TPixel> source)
24+
where TPixel : unmanaged, IPixel<TPixel>
25+
{
26+
Configuration configuration = source.GetConfiguration();
27+
28+
int endY = source.Height;
29+
int endX = source.Width;
30+
31+
Buffer2D<ulong> intImage = configuration.MemoryAllocator.Allocate2D<ulong>(source.Width, source.Height);
32+
ulong sumX0 = 0;
33+
34+
using (IMemoryOwner<L8> tempRow = configuration.MemoryAllocator.Allocate<L8>(source.Width))
35+
{
36+
Span<L8> tempSpan = tempRow.GetSpan();
37+
Span<TPixel> sourceRow = source.GetPixelRowSpan(0);
38+
Span<ulong> destRow = intImage.GetRowSpan(0);
39+
40+
PixelOperations<TPixel>.Instance.ToL8(configuration, sourceRow, tempSpan);
41+
42+
// First row
43+
for (int x = 0; x < endX; x++)
44+
{
45+
sumX0 += tempSpan[x].PackedValue;
46+
destRow[x] = sumX0;
47+
}
48+
49+
Span<ulong> previousDestRow = destRow;
50+
51+
// All other rows
52+
for (int y = 1; y < endY; y++)
53+
{
54+
sourceRow = source.GetPixelRowSpan(y);
55+
destRow = intImage.GetRowSpan(y);
56+
57+
PixelOperations<TPixel>.Instance.ToL8(configuration, sourceRow, tempSpan);
58+
59+
// Process first column
60+
sumX0 = tempSpan[0].PackedValue;
61+
destRow[0] = sumX0 + previousDestRow[0];
62+
63+
// Process all other colmns
64+
for (int x = 1; x < endX; x++)
65+
{
66+
sumX0 += tempSpan[x].PackedValue;
67+
destRow[x] = sumX0 + previousDestRow[x];
68+
}
69+
70+
previousDestRow = destRow;
71+
}
72+
}
73+
74+
return intImage;
75+
}
76+
}
77+
}

src/ImageSharp/Processing/Extensions/ProcessingExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.Processing
1212
/// <summary>
1313
/// Adds extensions that allow the processing of images to the <see cref="Image{TPixel}"/> type.
1414
/// </summary>
15-
public static class ProcessingExtensions
15+
public static partial class ProcessingExtensions
1616
{
1717
/// <summary>
1818
/// Mutates the source image by applying the image operation to it.
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Apache License, Version 2.0.
3+
4+
using SixLabors.ImageSharp.Memory;
5+
using SixLabors.ImageSharp.PixelFormats;
6+
using SixLabors.ImageSharp.Processing;
7+
using Xunit;
8+
9+
namespace SixLabors.ImageSharp.Tests.Processing.Transforms
10+
{
11+
public class IntegralImageTests : BaseImageOperationsExtensionTest
12+
{
13+
[Theory]
14+
[WithFile(TestImages.Png.Bradley01, PixelTypes.Rgba32)]
15+
[WithFile(TestImages.Png.Bradley02, PixelTypes.Rgba32)]
16+
[WithFile(TestImages.Png.Ducky, PixelTypes.Rgba32)]
17+
public void CalculateIntegralImage_Rgba32Works(TestImageProvider<Rgba32> provider)
18+
{
19+
using Image<Rgba32> image = provider.GetImage();
20+
21+
// Act:
22+
Buffer2D<ulong> integralBuffer = image.CalculateIntegralImage();
23+
24+
// Assert:
25+
VerifySumValues(provider, integralBuffer, (Rgba32 pixel) =>
26+
{
27+
L8 outputPixel = default;
28+
29+
outputPixel.FromRgba32(pixel);
30+
31+
return outputPixel.PackedValue;
32+
});
33+
}
34+
35+
[Theory]
36+
[WithFile(TestImages.Png.Bradley01, PixelTypes.L8)]
37+
[WithFile(TestImages.Png.Bradley02, PixelTypes.L8)]
38+
public void CalculateIntegralImage_L8Works(TestImageProvider<L8> provider)
39+
{
40+
using Image<L8> image = provider.GetImage();
41+
42+
// Act:
43+
Buffer2D<ulong> integralBuffer = image.CalculateIntegralImage();
44+
45+
// Assert:
46+
VerifySumValues(provider, integralBuffer, (L8 pixel) => { return pixel.PackedValue; });
47+
}
48+
49+
private static void VerifySumValues<TPixel>(
50+
TestImageProvider<TPixel> provider,
51+
Buffer2D<ulong> integralBuffer,
52+
System.Func<TPixel, ulong> getPixel)
53+
where TPixel : unmanaged, IPixel<TPixel>
54+
{
55+
Image<TPixel> image = provider.GetImage();
56+
57+
// Check top-left corner
58+
Assert.Equal(getPixel(image[0, 0]), integralBuffer[0, 0]);
59+
60+
ulong pixelValues = 0;
61+
62+
pixelValues += getPixel(image[0, 0]);
63+
pixelValues += getPixel(image[1, 0]);
64+
pixelValues += getPixel(image[0, 1]);
65+
pixelValues += getPixel(image[1, 1]);
66+
67+
// Check top-left 2x2 pixels
68+
Assert.Equal(pixelValues, integralBuffer[1, 1]);
69+
70+
pixelValues = 0;
71+
72+
pixelValues += getPixel(image[image.Width - 3, 0]);
73+
pixelValues += getPixel(image[image.Width - 2, 0]);
74+
pixelValues += getPixel(image[image.Width - 1, 0]);
75+
pixelValues += getPixel(image[image.Width - 3, 1]);
76+
pixelValues += getPixel(image[image.Width - 2, 1]);
77+
pixelValues += getPixel(image[image.Width - 1, 1]);
78+
79+
// Check top-right 3x2 pixels
80+
Assert.Equal(pixelValues, integralBuffer[image.Width - 1, 1] + 0 - 0 - integralBuffer[image.Width - 4, 1]);
81+
82+
pixelValues = 0;
83+
84+
pixelValues += getPixel(image[0, image.Height - 3]);
85+
pixelValues += getPixel(image[0, image.Height - 2]);
86+
pixelValues += getPixel(image[0, image.Height - 1]);
87+
pixelValues += getPixel(image[1, image.Height - 3]);
88+
pixelValues += getPixel(image[1, image.Height - 2]);
89+
pixelValues += getPixel(image[1, image.Height - 1]);
90+
91+
// Check bottom-left 2x3 pixels
92+
Assert.Equal(pixelValues, integralBuffer[1, image.Height - 1] + 0 - integralBuffer[1, image.Height - 4] - 0);
93+
94+
pixelValues = 0;
95+
96+
pixelValues += getPixel(image[image.Width - 3, image.Height - 3]);
97+
pixelValues += getPixel(image[image.Width - 2, image.Height - 3]);
98+
pixelValues += getPixel(image[image.Width - 1, image.Height - 3]);
99+
pixelValues += getPixel(image[image.Width - 3, image.Height - 2]);
100+
pixelValues += getPixel(image[image.Width - 2, image.Height - 2]);
101+
pixelValues += getPixel(image[image.Width - 1, image.Height - 2]);
102+
pixelValues += getPixel(image[image.Width - 3, image.Height - 1]);
103+
pixelValues += getPixel(image[image.Width - 2, image.Height - 1]);
104+
pixelValues += getPixel(image[image.Width - 1, image.Height - 1]);
105+
106+
// Check bottom-right 3x3 pixels
107+
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]);
108+
}
109+
}
110+
}

0 commit comments

Comments
 (0)