Skip to content

Commit 4b30c4e

Browse files
Expand pixel blender API to support single color bulk operations
1 parent fcd8087 commit 4b30c4e

File tree

10 files changed

+12263
-2968
lines changed

10 files changed

+12263
-2968
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,3 +227,5 @@ artifacts/
227227
#lfs
228228
hooks/**
229229
lfs/**
230+
231+
.dotnet

src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs

Lines changed: 12009 additions & 2937 deletions
Large diffs are not rendered by default.

src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,44 @@ var blenders = new []{
123123
}
124124
}
125125

126+
/// <inheritdoc />
127+
protected override void BlendFunction(Span<Vector4> destination, ReadOnlySpan<Vector4> background, Vector4 source, float amount)
128+
{
129+
amount = Numerics.Clamp(amount, 0, 1);
130+
131+
if (Avx2.IsSupported && destination.Length >= 2)
132+
{
133+
// Divide by 2 as 4 elements per Vector4 and 8 per Vector256<float>
134+
ref Vector256<float> destinationBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(destination));
135+
ref Vector256<float> destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u);
136+
137+
ref Vector256<float> backgroundBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(background));
138+
Vector256<float> sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W);
139+
Vector256<float> opacity = Vector256.Create(amount);
140+
141+
while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast))
142+
{
143+
destinationBase = PorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity);
144+
destinationBase = ref Unsafe.Add(ref destinationBase, 1);
145+
backgroundBase = ref Unsafe.Add(ref backgroundBase, 1);
146+
}
147+
148+
if (Numerics.Modulo2(destination.Length) != 0)
149+
{
150+
// Vector4 fits neatly in pairs. Any overlap has to be equal to 1.
151+
int i = destination.Length - 1;
152+
destination[i] = PorterDuffFunctions.<#=blender_composer#>(background[i], source, amount);
153+
}
154+
}
155+
else
156+
{
157+
for (int i = 0; i < destination.Length; i++)
158+
{
159+
destination[i] = PorterDuffFunctions.<#=blender_composer#>(background[i], source, amount);
160+
}
161+
}
162+
}
163+
126164
/// <inheritdoc />
127165
protected override void BlendFunction(Span<Vector4> destination, ReadOnlySpan<Vector4> background, ReadOnlySpan<Vector4> source, ReadOnlySpan<float> amount)
128166
{
@@ -169,6 +207,52 @@ var blenders = new []{
169207
}
170208
}
171209
}
210+
211+
/// <inheritdoc />
212+
protected override void BlendFunction(Span<Vector4> destination, ReadOnlySpan<Vector4> background, Vector4 source, ReadOnlySpan<float> amount)
213+
{
214+
if (Avx2.IsSupported && destination.Length >= 2)
215+
{
216+
// Divide by 2 as 4 elements per Vector4 and 8 per Vector256<float>
217+
ref Vector256<float> destinationBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(destination));
218+
ref Vector256<float> destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u);
219+
220+
ref Vector256<float> backgroundBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(background));
221+
ref float amountBase = ref MemoryMarshal.GetReference(amount);
222+
223+
Vector256<float> sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W);
224+
Vector256<float> vOne = Vector256.Create(1F);
225+
226+
while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast))
227+
{
228+
// We need to create a Vector256<float> containing the current and next amount values
229+
// taking up each half of the Vector256<float> and then clamp them.
230+
Vector256<float> opacity = Vector256.Create(
231+
Vector128.Create(amountBase),
232+
Vector128.Create(Unsafe.Add(ref amountBase, 1)));
233+
opacity = Avx.Min(Avx.Max(Vector256<float>.Zero, opacity), vOne);
234+
235+
destinationBase = PorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity);
236+
destinationBase = ref Unsafe.Add(ref destinationBase, 1);
237+
backgroundBase = ref Unsafe.Add(ref backgroundBase, 1);
238+
amountBase = ref Unsafe.Add(ref amountBase, 2);
239+
}
240+
241+
if (Numerics.Modulo2(destination.Length) != 0)
242+
{
243+
// Vector4 fits neatly in pairs. Any overlap has to be equal to 1.
244+
int i = destination.Length - 1;
245+
destination[i] = PorterDuffFunctions.<#=blender_composer#>(background[i], source, Numerics.Clamp(amount[i], 0, 1F));
246+
}
247+
}
248+
else
249+
{
250+
for (int i = 0; i < destination.Length; i++)
251+
{
252+
destination[i] = PorterDuffFunctions.<#=blender_composer#>(background[i], source, Numerics.Clamp(amount[i], 0, 1F));
253+
}
254+
}
255+
}
172256
}
173257

174258
<#

src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,39 @@ public void Blend<TPixelSrc>(
6464
PixelOperations<TPixel>.Instance.FromVector4Destructive(configuration, destinationVectors[..maxLength], destination, PixelConversionModifiers.Scale);
6565
}
6666

67+
/// <summary>
68+
/// Blends a row against a constant source color.
69+
/// </summary>
70+
/// <param name="configuration"><see cref="Configuration"/> to use internally</param>
71+
/// <param name="destination">the destination span</param>
72+
/// <param name="background">the background span</param>
73+
/// <param name="source">the source color</param>
74+
/// <param name="amount">
75+
/// A value between 0 and 1 indicating the weight of the second source vector.
76+
/// At amount = 0, "background" is returned, at amount = 1, "source" is returned.
77+
/// </param>
78+
public void Blend(
79+
Configuration configuration,
80+
Span<TPixel> destination,
81+
ReadOnlySpan<TPixel> background,
82+
TPixel source,
83+
float amount)
84+
{
85+
int maxLength = destination.Length;
86+
Guard.MustBeGreaterThanOrEqualTo(background.Length, maxLength, nameof(background.Length));
87+
Guard.MustBeBetweenOrEqualTo(amount, 0, 1, nameof(amount));
88+
89+
using IMemoryOwner<Vector4> buffer = configuration.MemoryAllocator.Allocate<Vector4>(maxLength * 2);
90+
Span<Vector4> destinationVectors = buffer.Slice(0, maxLength);
91+
Span<Vector4> backgroundVectors = buffer.Slice(maxLength, maxLength);
92+
93+
PixelOperations<TPixel>.Instance.ToVector4(configuration, background[..maxLength], backgroundVectors, PixelConversionModifiers.Scale);
94+
95+
this.BlendFunction(destinationVectors, backgroundVectors, source.ToScaledVector4(), amount);
96+
97+
PixelOperations<TPixel>.Instance.FromVector4Destructive(configuration, destinationVectors[..maxLength], destination, PixelConversionModifiers.Scale);
98+
}
99+
67100
/// <summary>
68101
/// Blends 2 rows together
69102
/// </summary>
@@ -121,6 +154,39 @@ public void Blend<TPixelSrc>(
121154
PixelOperations<TPixel>.Instance.FromVector4Destructive(configuration, destinationVectors[..maxLength], destination, PixelConversionModifiers.Scale);
122155
}
123156

157+
/// <summary>
158+
/// Blends a row against a constant source color.
159+
/// </summary>
160+
/// <param name="configuration"><see cref="Configuration"/> to use internally</param>
161+
/// <param name="destination">the destination span</param>
162+
/// <param name="background">the background span</param>
163+
/// <param name="source">the source color</param>
164+
/// <param name="amount">
165+
/// A span with values between 0 and 1 indicating the weight of the second source vector.
166+
/// At amount = 0, "background" is returned, at amount = 1, "source" is returned.
167+
/// </param>
168+
public void Blend(
169+
Configuration configuration,
170+
Span<TPixel> destination,
171+
ReadOnlySpan<TPixel> background,
172+
TPixel source,
173+
ReadOnlySpan<float> amount)
174+
{
175+
int maxLength = destination.Length;
176+
Guard.MustBeGreaterThanOrEqualTo(background.Length, maxLength, nameof(background.Length));
177+
Guard.MustBeGreaterThanOrEqualTo(amount.Length, maxLength, nameof(amount.Length));
178+
179+
using IMemoryOwner<Vector4> buffer = configuration.MemoryAllocator.Allocate<Vector4>(maxLength * 2);
180+
Span<Vector4> destinationVectors = buffer.Slice(0, maxLength);
181+
Span<Vector4> backgroundVectors = buffer.Slice(maxLength, maxLength);
182+
183+
PixelOperations<TPixel>.Instance.ToVector4(configuration, background[..maxLength], backgroundVectors, PixelConversionModifiers.Scale);
184+
185+
this.BlendFunction(destinationVectors, backgroundVectors, source.ToScaledVector4(), amount);
186+
187+
PixelOperations<TPixel>.Instance.FromVector4Destructive(configuration, destinationVectors[..maxLength], destination, PixelConversionModifiers.Scale);
188+
}
189+
124190
/// <summary>
125191
/// Blend 2 rows together.
126192
/// </summary>
@@ -137,6 +203,22 @@ protected abstract void BlendFunction(
137203
ReadOnlySpan<Vector4> source,
138204
float amount);
139205

206+
/// <summary>
207+
/// Blend a row against a constant source color.
208+
/// </summary>
209+
/// <param name="destination">destination span</param>
210+
/// <param name="background">the background span</param>
211+
/// <param name="source">the source color vector</param>
212+
/// <param name="amount">
213+
/// A value between 0 and 1 indicating the weight of the second source vector.
214+
/// At amount = 0, "background" is returned, at amount = 1, "source" is returned.
215+
/// </param>
216+
protected abstract void BlendFunction(
217+
Span<Vector4> destination,
218+
ReadOnlySpan<Vector4> background,
219+
Vector4 source,
220+
float amount);
221+
140222
/// <summary>
141223
/// Blend 2 rows together.
142224
/// </summary>
@@ -152,4 +234,20 @@ protected abstract void BlendFunction(
152234
ReadOnlySpan<Vector4> background,
153235
ReadOnlySpan<Vector4> source,
154236
ReadOnlySpan<float> amount);
237+
238+
/// <summary>
239+
/// Blend a row against a constant source color.
240+
/// </summary>
241+
/// <param name="destination">destination span</param>
242+
/// <param name="background">the background span</param>
243+
/// <param name="source">the source color vector</param>
244+
/// <param name="amount">
245+
/// A span with values between 0 and 1 indicating the weight of the second source vector.
246+
/// At amount = 0, "background" is returned, at amount = 1, "source" is returned.
247+
/// </param>
248+
protected abstract void BlendFunction(
249+
Span<Vector4> destination,
250+
ReadOnlySpan<Vector4> background,
251+
Vector4 source,
252+
ReadOnlySpan<float> amount);
155253
}

src/ImageSharp/Primitives/Point.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -233,18 +233,19 @@ public Point(Size size)
233233
/// <param name="matrix">The transformation matrix used.</param>
234234
/// <returns>The transformed <see cref="PointF"/>.</returns>
235235
[MethodImpl(MethodImplOptions.AggressiveInlining)]
236-
public static Point Transform(Point point, Matrix3x2 matrix) => Round(Vector2.Transform(new Vector2(point.X, point.Y), matrix));
236+
public static PointF Transform(Point point, Matrix3x2 matrix)
237+
=> Vector2.Transform(new Vector2(point.X, point.Y), matrix);
237238

238239
/// <summary>
239240
/// Transforms a point by a specified 4x4 matrix, applying a projective transform
240241
/// flattened into 2D space.
241242
/// </summary>
242243
/// <param name="point">The point to transform.</param>
243244
/// <param name="matrix">The transformation matrix used.</param>
244-
/// <returns>The transformed <see cref="Point"/>.</returns>
245+
/// <returns>The transformed <see cref="PointF"/>.</returns>
245246
[MethodImpl(MethodImplOptions.AggressiveInlining)]
246-
public static Point Transform(Point point, Matrix4x4 matrix)
247-
=> Round(TransformUtilities.ProjectiveTransform2D(point.X, point.Y, matrix));
247+
public static PointF Transform(Point point, Matrix4x4 matrix)
248+
=> TransformUtilities.ProjectiveTransform2D(point.X, point.Y, matrix);
248249

249250
/// <summary>
250251
/// Deconstructs this point into two integers.

src/ImageSharp/Primitives/Rectangle.cs

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -260,11 +260,7 @@ public static Rectangle Ceiling(RectangleF rectangle)
260260
/// <param name="matrix">The transformation matrix.</param>
261261
/// <returns>A transformed rectangle.</returns>
262262
public static RectangleF Transform(Rectangle rectangle, Matrix3x2 matrix)
263-
{
264-
PointF bottomRight = Point.Transform(new Point(rectangle.Right, rectangle.Bottom), matrix);
265-
PointF topLeft = Point.Transform(rectangle.Location, matrix);
266-
return new RectangleF(topLeft, new SizeF(bottomRight - topLeft));
267-
}
263+
=> RectangleF.Transform(rectangle, matrix);
268264

269265
/// <summary>
270266
/// Transforms a rectangle by the given 4x4 matrix, applying a projective transform
@@ -274,11 +270,7 @@ public static RectangleF Transform(Rectangle rectangle, Matrix3x2 matrix)
274270
/// <param name="matrix">The transformation matrix.</param>
275271
/// <returns>A transformed rectangle.</returns>
276272
public static RectangleF Transform(Rectangle rectangle, Matrix4x4 matrix)
277-
{
278-
PointF bottomRight = PointF.Transform(new PointF(rectangle.Right, rectangle.Bottom), matrix);
279-
PointF topLeft = PointF.Transform(new PointF(rectangle.Location.X, rectangle.Location.Y), matrix);
280-
return new RectangleF(topLeft, new SizeF(bottomRight - topLeft));
281-
}
273+
=> RectangleF.Transform(rectangle, matrix);
282274

283275
/// <summary>
284276
/// Converts a <see cref="RectangleF"/> to a <see cref="Rectangle"/> by performing a truncate operation on all the coordinates.

src/ImageSharp/Primitives/RectangleF.cs

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -236,9 +236,17 @@ public static RectangleF Inflate(RectangleF rectangle, float x, float y)
236236
/// <returns>A transformed <see cref="RectangleF"/>.</returns>
237237
public static RectangleF Transform(RectangleF rectangle, Matrix3x2 matrix)
238238
{
239-
PointF bottomRight = PointF.Transform(new PointF(rectangle.Right, rectangle.Bottom), matrix);
240239
PointF topLeft = PointF.Transform(rectangle.Location, matrix);
241-
return new RectangleF(topLeft, new SizeF(bottomRight - topLeft));
240+
PointF topRight = PointF.Transform(new PointF(rectangle.Right, rectangle.Top), matrix);
241+
PointF bottomLeft = PointF.Transform(new PointF(rectangle.Left, rectangle.Bottom), matrix);
242+
PointF bottomRight = PointF.Transform(new PointF(rectangle.Right, rectangle.Bottom), matrix);
243+
244+
float left = MathF.Min(MathF.Min(topLeft.X, topRight.X), MathF.Min(bottomLeft.X, bottomRight.X));
245+
float top = MathF.Min(MathF.Min(topLeft.Y, topRight.Y), MathF.Min(bottomLeft.Y, bottomRight.Y));
246+
float right = MathF.Max(MathF.Max(topLeft.X, topRight.X), MathF.Max(bottomLeft.X, bottomRight.X));
247+
float bottom = MathF.Max(MathF.Max(topLeft.Y, topRight.Y), MathF.Max(bottomLeft.Y, bottomRight.Y));
248+
249+
return FromLTRB(left, top, right, bottom);
242250
}
243251

244252
/// <summary>
@@ -250,9 +258,17 @@ public static RectangleF Transform(RectangleF rectangle, Matrix3x2 matrix)
250258
/// <returns>A transformed <see cref="RectangleF"/>.</returns>
251259
public static RectangleF Transform(RectangleF rectangle, Matrix4x4 matrix)
252260
{
253-
PointF bottomRight = PointF.Transform(new PointF(rectangle.Right, rectangle.Bottom), matrix);
254261
PointF topLeft = PointF.Transform(rectangle.Location, matrix);
255-
return new RectangleF(topLeft, new SizeF(bottomRight - topLeft));
262+
PointF topRight = PointF.Transform(new PointF(rectangle.Right, rectangle.Top), matrix);
263+
PointF bottomLeft = PointF.Transform(new PointF(rectangle.Left, rectangle.Bottom), matrix);
264+
PointF bottomRight = PointF.Transform(new PointF(rectangle.Right, rectangle.Bottom), matrix);
265+
266+
float left = MathF.Min(MathF.Min(topLeft.X, topRight.X), MathF.Min(bottomLeft.X, bottomRight.X));
267+
float top = MathF.Min(MathF.Min(topLeft.Y, topRight.Y), MathF.Min(bottomLeft.Y, bottomRight.Y));
268+
float right = MathF.Max(MathF.Max(topLeft.X, topRight.X), MathF.Max(bottomLeft.X, bottomRight.X));
269+
float bottom = MathF.Max(MathF.Max(topLeft.Y, topRight.Y), MathF.Max(bottomLeft.Y, bottomRight.Y));
270+
271+
return FromLTRB(left, top, right, bottom);
256272
}
257273

258274
/// <summary>

tests/ImageSharp.Tests/Primitives/PointTests.cs

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -159,9 +159,10 @@ public void RotateTest()
159159
Point p = new(13, 17);
160160
Matrix3x2 matrix = Matrix3x2Extensions.CreateRotationDegrees(45, Point.Empty);
161161

162-
Point pout = Point.Transform(p, matrix);
162+
PointF pout = Point.Transform(p, matrix);
163163

164-
Assert.Equal(new Point(-3, 21), pout);
164+
Assert.Equal(-2.828427F, pout.X, 4);
165+
Assert.Equal(21.213203F, pout.Y, 4);
165166
}
166167

167168
[Fact]
@@ -170,8 +171,9 @@ public void SkewTest()
170171
Point p = new(13, 17);
171172
Matrix3x2 matrix = Matrix3x2Extensions.CreateSkewDegrees(45, 45, Point.Empty);
172173

173-
Point pout = Point.Transform(p, matrix);
174-
Assert.Equal(new Point(30, 30), pout);
174+
PointF pout = Point.Transform(p, matrix);
175+
Assert.Equal(30F, pout.X, 4);
176+
Assert.Equal(30F, pout.Y, 4);
175177
}
176178

177179
[Fact]
@@ -181,8 +183,8 @@ public void TransformMatrix4x4_AffineMatchesMatrix3x2()
181183
Matrix3x2 m3 = Matrix3x2Extensions.CreateRotationDegrees(45, Point.Empty);
182184
Matrix4x4 m4 = new(m3);
183185

184-
Point r3 = Point.Transform(p, m3);
185-
Point r4 = Point.Transform(p, m4);
186+
PointF r3 = Point.Transform(p, m3);
187+
PointF r4 = Point.Transform(p, m4);
186188

187189
Assert.Equal(r3, r4);
188190
}
@@ -191,19 +193,20 @@ public void TransformMatrix4x4_AffineMatchesMatrix3x2()
191193
public void TransformMatrix4x4_Identity()
192194
{
193195
Point p = new(42, -17);
194-
Point result = Point.Transform(p, Matrix4x4.Identity);
196+
PointF result = Point.Transform(p, Matrix4x4.Identity);
195197

196-
Assert.Equal(p, result);
198+
Assert.Equal((PointF)p, result);
197199
}
198200

199201
[Fact]
200202
public void TransformMatrix4x4_Translation()
201203
{
202204
Point p = new(10, 20);
203205
Matrix4x4 m = Matrix4x4.CreateTranslation(5, -3, 0);
204-
Point result = Point.Transform(p, m);
206+
PointF result = Point.Transform(p, m);
205207

206-
Assert.Equal(new Point(15, 17), result);
208+
Assert.Equal(15F, result.X, 4);
209+
Assert.Equal(17F, result.Y, 4);
207210
}
208211

209212
[Fact]
@@ -213,10 +216,11 @@ public void TransformMatrix4x4_Projective()
213216
Matrix4x4 m = Matrix4x4.Identity;
214217
m.M14 = 0.005F;
215218

216-
Point result = Point.Transform(p, m);
219+
PointF result = Point.Transform(p, m);
217220

218-
// W = 100*0.005 + 1 = 1.5 => (100/1.5, 50/1.5) => rounded
219-
Assert.Equal(Point.Round(new PointF(100F / 1.5F, 50F / 1.5F)), result);
221+
// W = 100*0.005 + 1 = 1.5 => (100/1.5, 50/1.5)
222+
Assert.Equal(100F / 1.5F, result.X, 4);
223+
Assert.Equal(50F / 1.5F, result.Y, 4);
220224
}
221225

222226
[Theory]

0 commit comments

Comments
 (0)