Browse Source

Expand pixel blender API to support single color bulk operations

pull/3089/head
James Jackson-South 2 months ago
parent
commit
4b30c4ea10
  1. 2
      .gitignore
  2. 14946
      src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs
  3. 84
      src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt
  4. 98
      src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs
  5. 9
      src/ImageSharp/Primitives/Point.cs
  6. 12
      src/ImageSharp/Primitives/Rectangle.cs
  7. 24
      src/ImageSharp/Primitives/RectangleF.cs
  8. 30
      tests/ImageSharp.Tests/Primitives/PointTests.cs
  9. 13
      tests/ImageSharp.Tests/Primitives/RectangleFTests.cs
  10. 13
      tests/ImageSharp.Tests/Primitives/RectangleTests.cs

2
.gitignore

@ -227,3 +227,5 @@ artifacts/
#lfs
hooks/**
lfs/**
.dotnet

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

File diff suppressed because it is too large

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

@ -123,6 +123,44 @@ var blenders = new []{
}
}
/// <inheritdoc />
protected override void BlendFunction(Span<Vector4> destination, ReadOnlySpan<Vector4> background, Vector4 source, float amount)
{
amount = Numerics.Clamp(amount, 0, 1);
if (Avx2.IsSupported && destination.Length >= 2)
{
// Divide by 2 as 4 elements per Vector4 and 8 per Vector256<float>
ref Vector256<float> destinationBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(destination));
ref Vector256<float> destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u);
ref Vector256<float> backgroundBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(background));
Vector256<float> sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W);
Vector256<float> opacity = Vector256.Create(amount);
while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast))
{
destinationBase = PorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity);
destinationBase = ref Unsafe.Add(ref destinationBase, 1);
backgroundBase = ref Unsafe.Add(ref backgroundBase, 1);
}
if (Numerics.Modulo2(destination.Length) != 0)
{
// Vector4 fits neatly in pairs. Any overlap has to be equal to 1.
int i = destination.Length - 1;
destination[i] = PorterDuffFunctions.<#=blender_composer#>(background[i], source, amount);
}
}
else
{
for (int i = 0; i < destination.Length; i++)
{
destination[i] = PorterDuffFunctions.<#=blender_composer#>(background[i], source, amount);
}
}
}
/// <inheritdoc />
protected override void BlendFunction(Span<Vector4> destination, ReadOnlySpan<Vector4> background, ReadOnlySpan<Vector4> source, ReadOnlySpan<float> amount)
{
@ -169,6 +207,52 @@ var blenders = new []{
}
}
}
/// <inheritdoc />
protected override void BlendFunction(Span<Vector4> destination, ReadOnlySpan<Vector4> background, Vector4 source, ReadOnlySpan<float> amount)
{
if (Avx2.IsSupported && destination.Length >= 2)
{
// Divide by 2 as 4 elements per Vector4 and 8 per Vector256<float>
ref Vector256<float> destinationBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(destination));
ref Vector256<float> destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u);
ref Vector256<float> backgroundBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(background));
ref float amountBase = ref MemoryMarshal.GetReference(amount);
Vector256<float> sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W);
Vector256<float> vOne = Vector256.Create(1F);
while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast))
{
// We need to create a Vector256<float> containing the current and next amount values
// taking up each half of the Vector256<float> and then clamp them.
Vector256<float> opacity = Vector256.Create(
Vector128.Create(amountBase),
Vector128.Create(Unsafe.Add(ref amountBase, 1)));
opacity = Avx.Min(Avx.Max(Vector256<float>.Zero, opacity), vOne);
destinationBase = PorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity);
destinationBase = ref Unsafe.Add(ref destinationBase, 1);
backgroundBase = ref Unsafe.Add(ref backgroundBase, 1);
amountBase = ref Unsafe.Add(ref amountBase, 2);
}
if (Numerics.Modulo2(destination.Length) != 0)
{
// Vector4 fits neatly in pairs. Any overlap has to be equal to 1.
int i = destination.Length - 1;
destination[i] = PorterDuffFunctions.<#=blender_composer#>(background[i], source, Numerics.Clamp(amount[i], 0, 1F));
}
}
else
{
for (int i = 0; i < destination.Length; i++)
{
destination[i] = PorterDuffFunctions.<#=blender_composer#>(background[i], source, Numerics.Clamp(amount[i], 0, 1F));
}
}
}
}
<#

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

@ -64,6 +64,39 @@ public abstract class PixelBlender<TPixel>
PixelOperations<TPixel>.Instance.FromVector4Destructive(configuration, destinationVectors[..maxLength], destination, PixelConversionModifiers.Scale);
}
/// <summary>
/// Blends a row against a constant source color.
/// </summary>
/// <param name="configuration"><see cref="Configuration"/> to use internally</param>
/// <param name="destination">the destination span</param>
/// <param name="background">the background span</param>
/// <param name="source">the source color</param>
/// <param name="amount">
/// A value between 0 and 1 indicating the weight of the second source vector.
/// At amount = 0, "background" is returned, at amount = 1, "source" is returned.
/// </param>
public void Blend(
Configuration configuration,
Span<TPixel> destination,
ReadOnlySpan<TPixel> background,
TPixel source,
float amount)
{
int maxLength = destination.Length;
Guard.MustBeGreaterThanOrEqualTo(background.Length, maxLength, nameof(background.Length));
Guard.MustBeBetweenOrEqualTo(amount, 0, 1, nameof(amount));
using IMemoryOwner<Vector4> buffer = configuration.MemoryAllocator.Allocate<Vector4>(maxLength * 2);
Span<Vector4> destinationVectors = buffer.Slice(0, maxLength);
Span<Vector4> backgroundVectors = buffer.Slice(maxLength, maxLength);
PixelOperations<TPixel>.Instance.ToVector4(configuration, background[..maxLength], backgroundVectors, PixelConversionModifiers.Scale);
this.BlendFunction(destinationVectors, backgroundVectors, source.ToScaledVector4(), amount);
PixelOperations<TPixel>.Instance.FromVector4Destructive(configuration, destinationVectors[..maxLength], destination, PixelConversionModifiers.Scale);
}
/// <summary>
/// Blends 2 rows together
/// </summary>
@ -121,6 +154,39 @@ public abstract class PixelBlender<TPixel>
PixelOperations<TPixel>.Instance.FromVector4Destructive(configuration, destinationVectors[..maxLength], destination, PixelConversionModifiers.Scale);
}
/// <summary>
/// Blends a row against a constant source color.
/// </summary>
/// <param name="configuration"><see cref="Configuration"/> to use internally</param>
/// <param name="destination">the destination span</param>
/// <param name="background">the background span</param>
/// <param name="source">the source color</param>
/// <param name="amount">
/// A span with values between 0 and 1 indicating the weight of the second source vector.
/// At amount = 0, "background" is returned, at amount = 1, "source" is returned.
/// </param>
public void Blend(
Configuration configuration,
Span<TPixel> destination,
ReadOnlySpan<TPixel> background,
TPixel source,
ReadOnlySpan<float> amount)
{
int maxLength = destination.Length;
Guard.MustBeGreaterThanOrEqualTo(background.Length, maxLength, nameof(background.Length));
Guard.MustBeGreaterThanOrEqualTo(amount.Length, maxLength, nameof(amount.Length));
using IMemoryOwner<Vector4> buffer = configuration.MemoryAllocator.Allocate<Vector4>(maxLength * 2);
Span<Vector4> destinationVectors = buffer.Slice(0, maxLength);
Span<Vector4> backgroundVectors = buffer.Slice(maxLength, maxLength);
PixelOperations<TPixel>.Instance.ToVector4(configuration, background[..maxLength], backgroundVectors, PixelConversionModifiers.Scale);
this.BlendFunction(destinationVectors, backgroundVectors, source.ToScaledVector4(), amount);
PixelOperations<TPixel>.Instance.FromVector4Destructive(configuration, destinationVectors[..maxLength], destination, PixelConversionModifiers.Scale);
}
/// <summary>
/// Blend 2 rows together.
/// </summary>
@ -137,6 +203,22 @@ public abstract class PixelBlender<TPixel>
ReadOnlySpan<Vector4> source,
float amount);
/// <summary>
/// Blend a row against a constant source color.
/// </summary>
/// <param name="destination">destination span</param>
/// <param name="background">the background span</param>
/// <param name="source">the source color vector</param>
/// <param name="amount">
/// A value between 0 and 1 indicating the weight of the second source vector.
/// At amount = 0, "background" is returned, at amount = 1, "source" is returned.
/// </param>
protected abstract void BlendFunction(
Span<Vector4> destination,
ReadOnlySpan<Vector4> background,
Vector4 source,
float amount);
/// <summary>
/// Blend 2 rows together.
/// </summary>
@ -152,4 +234,20 @@ public abstract class PixelBlender<TPixel>
ReadOnlySpan<Vector4> background,
ReadOnlySpan<Vector4> source,
ReadOnlySpan<float> amount);
/// <summary>
/// Blend a row against a constant source color.
/// </summary>
/// <param name="destination">destination span</param>
/// <param name="background">the background span</param>
/// <param name="source">the source color vector</param>
/// <param name="amount">
/// A span with values between 0 and 1 indicating the weight of the second source vector.
/// At amount = 0, "background" is returned, at amount = 1, "source" is returned.
/// </param>
protected abstract void BlendFunction(
Span<Vector4> destination,
ReadOnlySpan<Vector4> background,
Vector4 source,
ReadOnlySpan<float> amount);
}

9
src/ImageSharp/Primitives/Point.cs

@ -233,7 +233,8 @@ public struct Point : IEquatable<Point>
/// <param name="matrix">The transformation matrix used.</param>
/// <returns>The transformed <see cref="PointF"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Point Transform(Point point, Matrix3x2 matrix) => Round(Vector2.Transform(new Vector2(point.X, point.Y), matrix));
public static PointF Transform(Point point, Matrix3x2 matrix)
=> Vector2.Transform(new Vector2(point.X, point.Y), matrix);
/// <summary>
/// Transforms a point by a specified 4x4 matrix, applying a projective transform
@ -241,10 +242,10 @@ public struct Point : IEquatable<Point>
/// </summary>
/// <param name="point">The point to transform.</param>
/// <param name="matrix">The transformation matrix used.</param>
/// <returns>The transformed <see cref="Point"/>.</returns>
/// <returns>The transformed <see cref="PointF"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Point Transform(Point point, Matrix4x4 matrix)
=> Round(TransformUtilities.ProjectiveTransform2D(point.X, point.Y, matrix));
public static PointF Transform(Point point, Matrix4x4 matrix)
=> TransformUtilities.ProjectiveTransform2D(point.X, point.Y, matrix);
/// <summary>
/// Deconstructs this point into two integers.

12
src/ImageSharp/Primitives/Rectangle.cs

@ -260,11 +260,7 @@ public struct Rectangle : IEquatable<Rectangle>
/// <param name="matrix">The transformation matrix.</param>
/// <returns>A transformed rectangle.</returns>
public static RectangleF Transform(Rectangle rectangle, Matrix3x2 matrix)
{
PointF bottomRight = Point.Transform(new Point(rectangle.Right, rectangle.Bottom), matrix);
PointF topLeft = Point.Transform(rectangle.Location, matrix);
return new RectangleF(topLeft, new SizeF(bottomRight - topLeft));
}
=> RectangleF.Transform(rectangle, matrix);
/// <summary>
/// Transforms a rectangle by the given 4x4 matrix, applying a projective transform
@ -274,11 +270,7 @@ public struct Rectangle : IEquatable<Rectangle>
/// <param name="matrix">The transformation matrix.</param>
/// <returns>A transformed rectangle.</returns>
public static RectangleF Transform(Rectangle rectangle, Matrix4x4 matrix)
{
PointF bottomRight = PointF.Transform(new PointF(rectangle.Right, rectangle.Bottom), matrix);
PointF topLeft = PointF.Transform(new PointF(rectangle.Location.X, rectangle.Location.Y), matrix);
return new RectangleF(topLeft, new SizeF(bottomRight - topLeft));
}
=> RectangleF.Transform(rectangle, matrix);
/// <summary>
/// Converts a <see cref="RectangleF"/> to a <see cref="Rectangle"/> by performing a truncate operation on all the coordinates.

24
src/ImageSharp/Primitives/RectangleF.cs

@ -236,9 +236,17 @@ public struct RectangleF : IEquatable<RectangleF>
/// <returns>A transformed <see cref="RectangleF"/>.</returns>
public static RectangleF Transform(RectangleF rectangle, Matrix3x2 matrix)
{
PointF bottomRight = PointF.Transform(new PointF(rectangle.Right, rectangle.Bottom), matrix);
PointF topLeft = PointF.Transform(rectangle.Location, matrix);
return new RectangleF(topLeft, new SizeF(bottomRight - topLeft));
PointF topRight = PointF.Transform(new PointF(rectangle.Right, rectangle.Top), matrix);
PointF bottomLeft = PointF.Transform(new PointF(rectangle.Left, rectangle.Bottom), matrix);
PointF bottomRight = PointF.Transform(new PointF(rectangle.Right, rectangle.Bottom), matrix);
float left = MathF.Min(MathF.Min(topLeft.X, topRight.X), MathF.Min(bottomLeft.X, bottomRight.X));
float top = MathF.Min(MathF.Min(topLeft.Y, topRight.Y), MathF.Min(bottomLeft.Y, bottomRight.Y));
float right = MathF.Max(MathF.Max(topLeft.X, topRight.X), MathF.Max(bottomLeft.X, bottomRight.X));
float bottom = MathF.Max(MathF.Max(topLeft.Y, topRight.Y), MathF.Max(bottomLeft.Y, bottomRight.Y));
return FromLTRB(left, top, right, bottom);
}
/// <summary>
@ -250,9 +258,17 @@ public struct RectangleF : IEquatable<RectangleF>
/// <returns>A transformed <see cref="RectangleF"/>.</returns>
public static RectangleF Transform(RectangleF rectangle, Matrix4x4 matrix)
{
PointF bottomRight = PointF.Transform(new PointF(rectangle.Right, rectangle.Bottom), matrix);
PointF topLeft = PointF.Transform(rectangle.Location, matrix);
return new RectangleF(topLeft, new SizeF(bottomRight - topLeft));
PointF topRight = PointF.Transform(new PointF(rectangle.Right, rectangle.Top), matrix);
PointF bottomLeft = PointF.Transform(new PointF(rectangle.Left, rectangle.Bottom), matrix);
PointF bottomRight = PointF.Transform(new PointF(rectangle.Right, rectangle.Bottom), matrix);
float left = MathF.Min(MathF.Min(topLeft.X, topRight.X), MathF.Min(bottomLeft.X, bottomRight.X));
float top = MathF.Min(MathF.Min(topLeft.Y, topRight.Y), MathF.Min(bottomLeft.Y, bottomRight.Y));
float right = MathF.Max(MathF.Max(topLeft.X, topRight.X), MathF.Max(bottomLeft.X, bottomRight.X));
float bottom = MathF.Max(MathF.Max(topLeft.Y, topRight.Y), MathF.Max(bottomLeft.Y, bottomRight.Y));
return FromLTRB(left, top, right, bottom);
}
/// <summary>

30
tests/ImageSharp.Tests/Primitives/PointTests.cs

@ -159,9 +159,10 @@ public class PointTests
Point p = new(13, 17);
Matrix3x2 matrix = Matrix3x2Extensions.CreateRotationDegrees(45, Point.Empty);
Point pout = Point.Transform(p, matrix);
PointF pout = Point.Transform(p, matrix);
Assert.Equal(new Point(-3, 21), pout);
Assert.Equal(-2.828427F, pout.X, 4);
Assert.Equal(21.213203F, pout.Y, 4);
}
[Fact]
@ -170,8 +171,9 @@ public class PointTests
Point p = new(13, 17);
Matrix3x2 matrix = Matrix3x2Extensions.CreateSkewDegrees(45, 45, Point.Empty);
Point pout = Point.Transform(p, matrix);
Assert.Equal(new Point(30, 30), pout);
PointF pout = Point.Transform(p, matrix);
Assert.Equal(30F, pout.X, 4);
Assert.Equal(30F, pout.Y, 4);
}
[Fact]
@ -181,8 +183,8 @@ public class PointTests
Matrix3x2 m3 = Matrix3x2Extensions.CreateRotationDegrees(45, Point.Empty);
Matrix4x4 m4 = new(m3);
Point r3 = Point.Transform(p, m3);
Point r4 = Point.Transform(p, m4);
PointF r3 = Point.Transform(p, m3);
PointF r4 = Point.Transform(p, m4);
Assert.Equal(r3, r4);
}
@ -191,9 +193,9 @@ public class PointTests
public void TransformMatrix4x4_Identity()
{
Point p = new(42, -17);
Point result = Point.Transform(p, Matrix4x4.Identity);
PointF result = Point.Transform(p, Matrix4x4.Identity);
Assert.Equal(p, result);
Assert.Equal((PointF)p, result);
}
[Fact]
@ -201,9 +203,10 @@ public class PointTests
{
Point p = new(10, 20);
Matrix4x4 m = Matrix4x4.CreateTranslation(5, -3, 0);
Point result = Point.Transform(p, m);
PointF result = Point.Transform(p, m);
Assert.Equal(new Point(15, 17), result);
Assert.Equal(15F, result.X, 4);
Assert.Equal(17F, result.Y, 4);
}
[Fact]
@ -213,10 +216,11 @@ public class PointTests
Matrix4x4 m = Matrix4x4.Identity;
m.M14 = 0.005F;
Point result = Point.Transform(p, m);
PointF result = Point.Transform(p, m);
// W = 100*0.005 + 1 = 1.5 => (100/1.5, 50/1.5) => rounded
Assert.Equal(Point.Round(new PointF(100F / 1.5F, 50F / 1.5F)), result);
// W = 100*0.005 + 1 = 1.5 => (100/1.5, 50/1.5)
Assert.Equal(100F / 1.5F, result.X, 4);
Assert.Equal(50F / 1.5F, result.Y, 4);
}
[Theory]

13
tests/ImageSharp.Tests/Primitives/RectangleFTests.cs

@ -286,6 +286,19 @@ public class RectangleFTests
Assert.Equal(new RectangleF(20, 60, 200, 150), result);
}
[Fact]
public void TransformMatrix4x4_RotationReturnsBoundingBox()
{
RectangleF rect = new(10, 20, 100, 50);
Matrix4x4 m = Matrix4x4.CreateRotationZ(MathF.PI / 2F);
RectangleF result = RectangleF.Transform(rect, m);
Assert.Equal(-70F, result.X, 4);
Assert.Equal(10F, result.Y, 4);
Assert.Equal(50F, result.Width, 4);
Assert.Equal(100F, result.Height, 4);
}
[Fact]
public void ToStringTest()
{

13
tests/ImageSharp.Tests/Primitives/RectangleTests.cs

@ -327,6 +327,19 @@ public class RectangleTests
Assert.Equal(new RectangleF(15, 17, 100, 50), result);
}
[Fact]
public void TransformMatrix4x4_RotationReturnsBoundingBox()
{
Rectangle rect = new(10, 20, 100, 50);
Matrix4x4 m = Matrix4x4.CreateRotationZ(MathF.PI / 2F);
RectangleF result = Rectangle.Transform(rect, m);
Assert.Equal(-70F, result.X, 4);
Assert.Equal(10F, result.Y, 4);
Assert.Equal(50F, result.Width, 4);
Assert.Equal(100F, result.Height, 4);
}
[Fact]
public void ToStringTest()
{

Loading…
Cancel
Save