Skip to content

Commit f77801e

Browse files
authored
Merge pull request #1431 from SixLabors/bp/fix1429
Use GetPixelRowSpan to access pixel data in Histogram equalization
2 parents dea0737 + 9bed14f commit f77801e

File tree

3 files changed

+83
-40
lines changed

3 files changed

+83
-40
lines changed

src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor{TPixel}.cs

Lines changed: 26 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -86,42 +86,39 @@ protected override void OnFrameApply(ImageFrame<TPixel> source)
8686
new Rectangle(0, 0, sourceWidth, tileYStartPositions.Count),
8787
in operation);
8888

89-
ref TPixel pixelsBase = ref source.GetPixelReference(0, 0);
90-
9189
// Fix left column
92-
ProcessBorderColumn(ref pixelsBase, cdfData, 0, sourceWidth, sourceHeight, this.Tiles, tileHeight, xStart: 0, xEnd: halfTileWidth, luminanceLevels);
90+
ProcessBorderColumn(source, cdfData, 0, sourceHeight, this.Tiles, tileHeight, xStart: 0, xEnd: halfTileWidth, luminanceLevels);
9391

9492
// Fix right column
9593
int rightBorderStartX = ((this.Tiles - 1) * tileWidth) + halfTileWidth;
96-
ProcessBorderColumn(ref pixelsBase, cdfData, this.Tiles - 1, sourceWidth, sourceHeight, this.Tiles, tileHeight, xStart: rightBorderStartX, xEnd: sourceWidth, luminanceLevels);
94+
ProcessBorderColumn(source, cdfData, this.Tiles - 1, sourceHeight, this.Tiles, tileHeight, xStart: rightBorderStartX, xEnd: sourceWidth, luminanceLevels);
9795

9896
// Fix top row
99-
ProcessBorderRow(ref pixelsBase, cdfData, 0, sourceWidth, this.Tiles, tileWidth, yStart: 0, yEnd: halfTileHeight, luminanceLevels);
97+
ProcessBorderRow(source, cdfData, 0, sourceWidth, this.Tiles, tileWidth, yStart: 0, yEnd: halfTileHeight, luminanceLevels);
10098

10199
// Fix bottom row
102100
int bottomBorderStartY = ((this.Tiles - 1) * tileHeight) + halfTileHeight;
103-
ProcessBorderRow(ref pixelsBase, cdfData, this.Tiles - 1, sourceWidth, this.Tiles, tileWidth, yStart: bottomBorderStartY, yEnd: sourceHeight, luminanceLevels);
101+
ProcessBorderRow(source, cdfData, this.Tiles - 1, sourceWidth, this.Tiles, tileWidth, yStart: bottomBorderStartY, yEnd: sourceHeight, luminanceLevels);
104102

105103
// Left top corner
106-
ProcessCornerTile(ref pixelsBase, cdfData, sourceWidth, 0, 0, xStart: 0, xEnd: halfTileWidth, yStart: 0, yEnd: halfTileHeight, luminanceLevels);
104+
ProcessCornerTile(source, cdfData, 0, 0, xStart: 0, xEnd: halfTileWidth, yStart: 0, yEnd: halfTileHeight, luminanceLevels);
107105

108106
// Left bottom corner
109-
ProcessCornerTile(ref pixelsBase, cdfData, sourceWidth, 0, this.Tiles - 1, xStart: 0, xEnd: halfTileWidth, yStart: bottomBorderStartY, yEnd: sourceHeight, luminanceLevels);
107+
ProcessCornerTile(source, cdfData, 0, this.Tiles - 1, xStart: 0, xEnd: halfTileWidth, yStart: bottomBorderStartY, yEnd: sourceHeight, luminanceLevels);
110108

111109
// Right top corner
112-
ProcessCornerTile(ref pixelsBase, cdfData, sourceWidth, this.Tiles - 1, 0, xStart: rightBorderStartX, xEnd: sourceWidth, yStart: 0, yEnd: halfTileHeight, luminanceLevels);
110+
ProcessCornerTile(source, cdfData, this.Tiles - 1, 0, xStart: rightBorderStartX, xEnd: sourceWidth, yStart: 0, yEnd: halfTileHeight, luminanceLevels);
113111

114112
// Right bottom corner
115-
ProcessCornerTile(ref pixelsBase, cdfData, sourceWidth, this.Tiles - 1, this.Tiles - 1, xStart: rightBorderStartX, xEnd: sourceWidth, yStart: bottomBorderStartY, yEnd: sourceHeight, luminanceLevels);
113+
ProcessCornerTile(source, cdfData, this.Tiles - 1, this.Tiles - 1, xStart: rightBorderStartX, xEnd: sourceWidth, yStart: bottomBorderStartY, yEnd: sourceHeight, luminanceLevels);
116114
}
117115
}
118116

119117
/// <summary>
120118
/// Processes the part of a corner tile which was previously left out. It consists of 1 / 4 of a tile and does not need interpolation.
121119
/// </summary>
122-
/// <param name="pixelsBase">The output pixels base reference.</param>
120+
/// <param name="source">The source image.</param>
123121
/// <param name="cdfData">The lookup table to remap the grey values.</param>
124-
/// <param name="sourceWidth">The source image width.</param>
125122
/// <param name="cdfX">The x-position in the CDF lookup map.</param>
126123
/// <param name="cdfY">The y-position in the CDF lookup map.</param>
127124
/// <param name="xStart">X start position.</param>
@@ -133,9 +130,8 @@ protected override void OnFrameApply(ImageFrame<TPixel> source)
133130
/// or 65536 for 16-bit grayscale images.
134131
/// </param>
135132
private static void ProcessCornerTile(
136-
ref TPixel pixelsBase,
133+
ImageFrame<TPixel> source,
137134
CdfTileData cdfData,
138-
int sourceWidth,
139135
int cdfX,
140136
int cdfY,
141137
int xStart,
@@ -146,10 +142,10 @@ private static void ProcessCornerTile(
146142
{
147143
for (int dy = yStart; dy < yEnd; dy++)
148144
{
149-
int dyOffSet = dy * sourceWidth;
145+
Span<TPixel> rowSpan = source.GetPixelRowSpan(dy);
150146
for (int dx = xStart; dx < xEnd; dx++)
151147
{
152-
ref TPixel pixel = ref Unsafe.Add(ref pixelsBase, dyOffSet + dx);
148+
ref TPixel pixel = ref rowSpan[dx];
153149
float luminanceEqualized = cdfData.RemapGreyValue(cdfX, cdfY, GetLuminance(pixel, luminanceLevels));
154150
pixel.FromVector4(new Vector4(luminanceEqualized, luminanceEqualized, luminanceEqualized, pixel.ToVector4().W));
155151
}
@@ -159,10 +155,9 @@ private static void ProcessCornerTile(
159155
/// <summary>
160156
/// Processes a border column of the image which is half the size of the tile width.
161157
/// </summary>
162-
/// <param name="pixelBase">The output pixels reference.</param>
158+
/// <param name="source">The source image.</param>
163159
/// <param name="cdfData">The pre-computed lookup tables to remap the grey values for each tiles.</param>
164160
/// <param name="cdfX">The X index of the lookup table to use.</param>
165-
/// <param name="sourceWidth">The source image width.</param>
166161
/// <param name="sourceHeight">The source image height.</param>
167162
/// <param name="tileCount">The number of vertical tiles.</param>
168163
/// <param name="tileHeight">The height of a tile.</param>
@@ -173,10 +168,9 @@ private static void ProcessCornerTile(
173168
/// or 65536 for 16-bit grayscale images.
174169
/// </param>
175170
private static void ProcessBorderColumn(
176-
ref TPixel pixelBase,
171+
ImageFrame<TPixel> source,
177172
CdfTileData cdfData,
178173
int cdfX,
179-
int sourceWidth,
180174
int sourceHeight,
181175
int tileCount,
182176
int tileHeight,
@@ -194,10 +188,10 @@ private static void ProcessBorderColumn(
194188
int tileY = 0;
195189
for (int dy = y; dy < yLimit; dy++)
196190
{
197-
int dyOffSet = dy * sourceWidth;
191+
Span<TPixel> rowSpan = source.GetPixelRowSpan(dy);
198192
for (int dx = xStart; dx < xEnd; dx++)
199193
{
200-
ref TPixel pixel = ref Unsafe.Add(ref pixelBase, dyOffSet + dx);
194+
ref TPixel pixel = ref rowSpan[dx];
201195
float luminanceEqualized = InterpolateBetweenTwoTiles(pixel, cdfData, cdfX, cdfY, cdfX, cdfY + 1, tileY, tileHeight, luminanceLevels);
202196
pixel.FromVector4(new Vector4(luminanceEqualized, luminanceEqualized, luminanceEqualized, pixel.ToVector4().W));
203197
}
@@ -213,7 +207,7 @@ private static void ProcessBorderColumn(
213207
/// <summary>
214208
/// Processes a border row of the image which is half of the size of the tile height.
215209
/// </summary>
216-
/// <param name="pixelBase">The output pixels base reference.</param>
210+
/// <param name="source">The source image.</param>
217211
/// <param name="cdfData">The pre-computed lookup tables to remap the grey values for each tiles.</param>
218212
/// <param name="cdfY">The Y index of the lookup table to use.</param>
219213
/// <param name="sourceWidth">The source image width.</param>
@@ -226,7 +220,7 @@ private static void ProcessBorderColumn(
226220
/// or 65536 for 16-bit grayscale images.
227221
/// </param>
228222
private static void ProcessBorderRow(
229-
ref TPixel pixelBase,
223+
ImageFrame<TPixel> source,
230224
CdfTileData cdfData,
231225
int cdfY,
232226
int sourceWidth,
@@ -244,12 +238,12 @@ private static void ProcessBorderRow(
244238
{
245239
for (int dy = yStart; dy < yEnd; dy++)
246240
{
247-
int dyOffSet = dy * sourceWidth;
241+
Span<TPixel> rowSpan = source.GetPixelRowSpan(dy);
248242
int tileX = 0;
249243
int xLimit = Math.Min(x + tileWidth, sourceWidth - 1);
250244
for (int dx = x; dx < xLimit; dx++)
251245
{
252-
ref TPixel pixel = ref Unsafe.Add(ref pixelBase, dyOffSet + dx);
246+
ref TPixel pixel = ref rowSpan[dx];
253247
float luminanceEqualized = InterpolateBetweenTwoTiles(pixel, cdfData, cdfX, cdfY, cdfX + 1, cdfY, tileX, tileWidth, luminanceLevels);
254248
pixel.FromVector4(new Vector4(luminanceEqualized, luminanceEqualized, luminanceEqualized, pixel.ToVector4().W));
255249
tileX++;
@@ -410,8 +404,6 @@ public RowIntervalOperation(
410404
[MethodImpl(InliningOptions.ShortMethod)]
411405
public void Invoke(in RowInterval rows)
412406
{
413-
ref TPixel sourceBase = ref this.source.GetPixelReference(0, 0);
414-
415407
for (int index = rows.Min; index < rows.Max; index++)
416408
{
417409
(int y, int cdfY) tileYStartPosition = this.tileYStartPositions[index];
@@ -427,11 +419,11 @@ public void Invoke(in RowInterval rows)
427419
int xEnd = Math.Min(x + this.tileWidth, this.sourceWidth);
428420
for (int dy = y; dy < yEnd; dy++)
429421
{
430-
int dyOffSet = dy * this.sourceWidth;
422+
Span<TPixel> rowSpan = this.source.GetPixelRowSpan(dy);
431423
int tileX = 0;
432424
for (int dx = x; dx < xEnd; dx++)
433425
{
434-
ref TPixel pixel = ref Unsafe.Add(ref sourceBase, dyOffSet + dx);
426+
ref TPixel pixel = ref rowSpan[dx];
435427
float luminanceEqualized = InterpolateBetweenFourTiles(
436428
pixel,
437429
this.cdfData,
@@ -597,15 +589,13 @@ public RowIntervalOperation(
597589
[MethodImpl(InliningOptions.ShortMethod)]
598590
public void Invoke(in RowInterval rows)
599591
{
600-
ref TPixel sourceBase = ref this.source.GetPixelReference(0, 0);
601-
602592
for (int index = rows.Min; index < rows.Max; index++)
603593
{
604594
int cdfX = 0;
605595
int cdfY = this.tileYStartPositions[index].cdfY;
606596
int y = this.tileYStartPositions[index].y;
607597
int endY = Math.Min(y + this.tileHeight, this.sourceHeight);
608-
ref int cdfMinBase = ref MemoryMarshal.GetReference(this.cdfMinBuffer2D.GetRowSpan(cdfY));
598+
Span<int> cdfMinSpan = this.cdfMinBuffer2D.GetRowSpan(cdfY);
609599

610600
using IMemoryOwner<int> histogramBuffer = this.allocator.Allocate<int>(this.luminanceLevels);
611601
Span<int> histogram = histogramBuffer.GetSpan();
@@ -620,10 +610,10 @@ public void Invoke(in RowInterval rows)
620610
int xlimit = Math.Min(x + this.tileWidth, this.sourceWidth);
621611
for (int dy = y; dy < endY; dy++)
622612
{
623-
int dyOffset = dy * this.sourceWidth;
613+
Span<TPixel> rowSpan = this.source.GetPixelRowSpan(dy);
624614
for (int dx = x; dx < xlimit; dx++)
625615
{
626-
int luminance = GetLuminance(Unsafe.Add(ref sourceBase, dyOffset + dx), this.luminanceLevels);
616+
int luminance = GetLuminance(rowSpan[dx], this.luminanceLevels);
627617
histogram[luminance]++;
628618
}
629619
}
@@ -633,7 +623,7 @@ public void Invoke(in RowInterval rows)
633623
this.processor.ClipHistogram(histogram, this.processor.ClipLimit);
634624
}
635625

636-
Unsafe.Add(ref cdfMinBase, cdfX) = this.processor.CalculateCdf(ref cdfBase, ref histogramBase, histogram.Length - 1);
626+
cdfMinSpan[cdfX] += this.processor.CalculateCdf(ref cdfBase, ref histogramBase, histogram.Length - 1);
637627

638628
cdfX++;
639629
}

src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -116,13 +116,13 @@ public GrayscaleLevelsRowOperation(
116116
public void Invoke(int y)
117117
{
118118
ref int histogramBase = ref MemoryMarshal.GetReference(this.histogramBuffer.GetSpan());
119-
ref TPixel pixelBase = ref MemoryMarshal.GetReference(this.source.GetPixelRowSpan(y));
119+
Span<TPixel> pixelRow = this.source.GetPixelRowSpan(y);
120120
int levels = this.luminanceLevels;
121121

122122
for (int x = 0; x < this.bounds.Width; x++)
123123
{
124124
// TODO: We should bulk convert here.
125-
var vector = Unsafe.Add(ref pixelBase, x).ToVector4();
125+
var vector = pixelRow[x].ToVector4();
126126
int luminance = ImageMaths.GetBT709Luminance(ref vector, levels);
127127
Interlocked.Increment(ref Unsafe.Add(ref histogramBase, luminance));
128128
}
@@ -165,14 +165,14 @@ public CdfApplicationRowOperation(
165165
public void Invoke(int y)
166166
{
167167
ref int cdfBase = ref MemoryMarshal.GetReference(this.cdfBuffer.GetSpan());
168-
ref TPixel pixelBase = ref MemoryMarshal.GetReference(this.source.GetPixelRowSpan(y));
168+
Span<TPixel> pixelRow = this.source.GetPixelRowSpan(y);
169169
int levels = this.luminanceLevels;
170170
float noOfPixelsMinusCdfMin = this.numberOfPixelsMinusCdfMin;
171171

172172
for (int x = 0; x < this.bounds.Width; x++)
173173
{
174174
// TODO: We should bulk convert here.
175-
ref TPixel pixel = ref Unsafe.Add(ref pixelBase, x);
175+
ref TPixel pixel = ref pixelRow[x];
176176
var vector = pixel.ToVector4();
177177
int luminance = ImageMaths.GetBT709Luminance(ref vector, levels);
178178
float luminanceEqualized = Unsafe.Add(ref cdfBase, luminance) / noOfPixelsMinusCdfMin;
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Apache License, Version 2.0.
3+
4+
using System.IO;
5+
using BenchmarkDotNet.Attributes;
6+
using SixLabors.ImageSharp.PixelFormats;
7+
using SixLabors.ImageSharp.Processing;
8+
using SixLabors.ImageSharp.Processing.Processors.Normalization;
9+
using SixLabors.ImageSharp.Tests;
10+
11+
namespace SixLabors.ImageSharp.Benchmarks.Processing
12+
{
13+
[Config(typeof(Config.ShortClr))]
14+
public class HistogramEqualization : BenchmarkBase
15+
{
16+
private Image<Rgba32> image;
17+
18+
[GlobalSetup]
19+
public void ReadImages()
20+
{
21+
if (this.image == null)
22+
{
23+
this.image = Image.Load<Rgba32>(File.OpenRead(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Jpeg.Baseline.HistogramEqImage)));
24+
}
25+
}
26+
27+
[GlobalCleanup]
28+
public void Cleanup()
29+
{
30+
this.image.Dispose();
31+
}
32+
33+
[Benchmark(Description = "Global Histogram Equalization")]
34+
public void GlobalHistogramEqualization()
35+
{
36+
this.image.Mutate(img => img.HistogramEqualization(new HistogramEqualizationOptions()
37+
{
38+
LuminanceLevels = 256,
39+
Method = HistogramEqualizationMethod.Global
40+
}));
41+
}
42+
43+
[Benchmark(Description = "AdaptiveHistogramEqualization (Tile interpolation)")]
44+
public void AdaptiveHistogramEqualization()
45+
{
46+
this.image.Mutate(img => img.HistogramEqualization(new HistogramEqualizationOptions()
47+
{
48+
LuminanceLevels = 256,
49+
Method = HistogramEqualizationMethod.AdaptiveTileInterpolation
50+
}));
51+
}
52+
}
53+
}

0 commit comments

Comments
 (0)