Skip to content

Commit 7859e71

Browse files
Merge pull request #1068 from Sergio0694/improvement/oil-processor-memory-usage
OilPaintingProcessor memory usage improvements
2 parents 842d7dd + c89e0b1 commit 7859e71

File tree

1 file changed

+90
-57
lines changed

1 file changed

+90
-57
lines changed

src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs

Lines changed: 90 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
// Licensed under the Apache License, Version 2.0.
33

44
using System;
5+
using System.Buffers;
56
using System.Numerics;
7+
using System.Runtime.CompilerServices;
68

79
using SixLabors.ImageSharp.Advanced;
810
using SixLabors.ImageSharp.Advanced.ParallelUtils;
@@ -52,82 +54,113 @@ protected override void OnFrameApply(ImageFrame<TPixel> source)
5254

5355
int radius = brushSize >> 1;
5456
int levels = this.definition.Levels;
57+
int rowWidth = source.Width;
58+
int rectangleWidth = this.SourceRectangle.Width;
59+
60+
Configuration configuration = this.Configuration;
61+
62+
using Buffer2D<TPixel> targetPixels = this.Configuration.MemoryAllocator.Allocate2D<TPixel>(source.Size());
63+
source.CopyTo(targetPixels);
64+
65+
var workingRect = Rectangle.FromLTRB(startX, startY, endX, endY);
66+
ParallelHelper.IterateRows(
67+
workingRect,
68+
this.Configuration,
69+
(rows) =>
70+
{
71+
/* Allocate the two temporary Vector4 buffers, one for the source row and one for the target row.
72+
* The ParallelHelper.IterateRowsWithTempBuffers overload is not used in this case because
73+
* the two allocated buffers have a length equal to the width of the source image,
74+
* and not just equal to the width of the target rectangle to process.
75+
* Furthermore, there are two buffers being allocated in this case, so using that overload would
76+
* have still required the explicit allocation of the secondary buffer.
77+
* Similarly, one temporary float buffer is also allocated from the pool, and that is used
78+
* to create the target bins for all the color channels being processed.
79+
* This buffer is only rented once outside of the main processing loop, and its contents
80+
* are cleared for each loop iteration, to avoid the repeated allocation for each processed pixel. */
81+
using (IMemoryOwner<Vector4> sourceRowBuffer = configuration.MemoryAllocator.Allocate<Vector4>(rowWidth))
82+
using (IMemoryOwner<Vector4> targetRowBuffer = configuration.MemoryAllocator.Allocate<Vector4>(rowWidth))
83+
using (IMemoryOwner<float> bins = configuration.MemoryAllocator.Allocate<float>(levels * 4))
84+
{
85+
Span<Vector4> sourceRowVector4Span = sourceRowBuffer.Memory.Span;
86+
Span<Vector4> sourceRowAreaVector4Span = sourceRowVector4Span.Slice(startX, rectangleWidth);
87+
88+
Span<Vector4> targetRowVector4Span = targetRowBuffer.Memory.Span;
89+
Span<Vector4> targetRowAreaVector4Span = targetRowVector4Span.Slice(startX, rectangleWidth);
90+
91+
ref float binsRef = ref bins.GetReference();
92+
ref int intensityBinRef = ref Unsafe.As<float, int>(ref binsRef);
93+
ref float redBinRef = ref Unsafe.Add(ref binsRef, levels);
94+
ref float blueBinRef = ref Unsafe.Add(ref redBinRef, levels);
95+
ref float greenBinRef = ref Unsafe.Add(ref blueBinRef, levels);
96+
97+
for (int y = rows.Min; y < rows.Max; y++)
98+
{
99+
Span<TPixel> sourceRowPixelSpan = source.GetPixelRowSpan(y);
100+
Span<TPixel> sourceRowAreaPixelSpan = sourceRowPixelSpan.Slice(startX, rectangleWidth);
55101

56-
using (Buffer2D<TPixel> targetPixels = this.Configuration.MemoryAllocator.Allocate2D<TPixel>(source.Size()))
57-
{
58-
source.CopyTo(targetPixels);
102+
PixelOperations<TPixel>.Instance.ToVector4(configuration, sourceRowAreaPixelSpan, sourceRowAreaVector4Span);
59103

60-
var workingRect = Rectangle.FromLTRB(startX, startY, endX, endY);
61-
ParallelHelper.IterateRows(
62-
workingRect,
63-
this.Configuration,
64-
rows =>
65-
{
66-
for (int y = rows.Min; y < rows.Max; y++)
104+
for (int x = startX; x < endX; x++)
67105
{
68-
Span<TPixel> sourceRow = source.GetPixelRowSpan(y);
69-
Span<TPixel> targetRow = targetPixels.GetRowSpan(y);
70-
71-
for (int x = startX; x < endX; x++)
72-
{
73-
int maxIntensity = 0;
74-
int maxIndex = 0;
106+
int maxIntensity = 0;
107+
int maxIndex = 0;
75108

76-
var intensityBin = new int[levels];
77-
var redBin = new float[levels];
78-
var blueBin = new float[levels];
79-
var greenBin = new float[levels];
109+
// Clear the current shared buffer before processing each target pixel
110+
bins.Memory.Span.Clear();
80111

81-
for (int fy = 0; fy <= radius; fy++)
82-
{
83-
int fyr = fy - radius;
84-
int offsetY = y + fyr;
112+
for (int fy = 0; fy <= radius; fy++)
113+
{
114+
int fyr = fy - radius;
115+
int offsetY = y + fyr;
85116

86-
offsetY = offsetY.Clamp(0, maxY);
117+
offsetY = offsetY.Clamp(0, maxY);
87118

88-
Span<TPixel> sourceOffsetRow = source.GetPixelRowSpan(offsetY);
119+
Span<TPixel> sourceOffsetRow = source.GetPixelRowSpan(offsetY);
89120

90-
for (int fx = 0; fx <= radius; fx++)
91-
{
92-
int fxr = fx - radius;
93-
int offsetX = x + fxr;
94-
offsetX = offsetX.Clamp(0, maxX);
121+
for (int fx = 0; fx <= radius; fx++)
122+
{
123+
int fxr = fx - radius;
124+
int offsetX = x + fxr;
125+
offsetX = offsetX.Clamp(0, maxX);
95126

96-
var vector = sourceOffsetRow[offsetX].ToVector4();
127+
var vector = sourceOffsetRow[offsetX].ToVector4();
97128

98-
float sourceRed = vector.X;
99-
float sourceBlue = vector.Z;
100-
float sourceGreen = vector.Y;
129+
float sourceRed = vector.X;
130+
float sourceBlue = vector.Z;
131+
float sourceGreen = vector.Y;
101132

102-
int currentIntensity = (int)MathF.Round(
103-
(sourceBlue + sourceGreen + sourceRed) / 3F * (levels - 1));
133+
int currentIntensity = (int)MathF.Round((sourceBlue + sourceGreen + sourceRed) / 3F * (levels - 1));
104134

105-
intensityBin[currentIntensity]++;
106-
blueBin[currentIntensity] += sourceBlue;
107-
greenBin[currentIntensity] += sourceGreen;
108-
redBin[currentIntensity] += sourceRed;
135+
Unsafe.Add(ref intensityBinRef, currentIntensity)++;
136+
Unsafe.Add(ref redBinRef, currentIntensity) += sourceRed;
137+
Unsafe.Add(ref blueBinRef, currentIntensity) += sourceBlue;
138+
Unsafe.Add(ref greenBinRef, currentIntensity) += sourceGreen;
109139

110-
if (intensityBin[currentIntensity] > maxIntensity)
111-
{
112-
maxIntensity = intensityBin[currentIntensity];
113-
maxIndex = currentIntensity;
114-
}
140+
if (Unsafe.Add(ref intensityBinRef, currentIntensity) > maxIntensity)
141+
{
142+
maxIntensity = Unsafe.Add(ref intensityBinRef, currentIntensity);
143+
maxIndex = currentIntensity;
115144
}
145+
}
116146

117-
float red = MathF.Abs(redBin[maxIndex] / maxIntensity);
118-
float green = MathF.Abs(greenBin[maxIndex] / maxIntensity);
119-
float blue = MathF.Abs(blueBin[maxIndex] / maxIntensity);
147+
float red = MathF.Abs(Unsafe.Add(ref redBinRef, maxIndex) / maxIntensity);
148+
float blue = MathF.Abs(Unsafe.Add(ref blueBinRef, maxIndex) / maxIntensity);
149+
float green = MathF.Abs(Unsafe.Add(ref greenBinRef, maxIndex) / maxIntensity);
150+
float alpha = sourceRowVector4Span[x].W;
120151

121-
ref TPixel pixel = ref targetRow[x];
122-
pixel.FromVector4(
123-
new Vector4(red, green, blue, sourceRow[x].ToVector4().W));
124-
}
152+
targetRowVector4Span[x] = new Vector4(red, green, blue, alpha);
125153
}
126154
}
127-
});
128155

129-
Buffer2D<TPixel>.SwapOrCopyContent(source.PixelBuffer, targetPixels);
130-
}
156+
Span<TPixel> targetRowAreaPixelSpan = targetPixels.GetRowSpan(y).Slice(startX, rectangleWidth);
157+
158+
PixelOperations<TPixel>.Instance.FromVector4Destructive(configuration, targetRowAreaVector4Span, targetRowAreaPixelSpan);
159+
}
160+
}
161+
});
162+
163+
Buffer2D<TPixel>.SwapOrCopyContent(source.PixelBuffer, targetPixels);
131164
}
132165
}
133166
}

0 commit comments

Comments
 (0)