Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -227,3 +227,5 @@ artifacts/
#lfs
hooks/**
lfs/**

.dotnet
14,946 changes: 12,009 additions & 2,937 deletions src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down Expand Up @@ -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));
}
}
}
}

<#
Expand Down
98 changes: 98 additions & 0 deletions src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,39 @@ public void Blend<TPixelSrc>(
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>
Expand Down Expand Up @@ -121,6 +154,39 @@ public void Blend<TPixelSrc>(
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>
Expand All @@ -137,6 +203,22 @@ protected abstract void BlendFunction(
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>
Expand All @@ -152,4 +234,20 @@ protected abstract void BlendFunction(
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 changes: 5 additions & 4 deletions src/ImageSharp/Primitives/Point.cs
Original file line number Diff line number Diff line change
Expand Up @@ -233,18 +233,19 @@ public Point(Size size)
/// <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
/// flattened into 2D space.
/// </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.
Expand Down
12 changes: 2 additions & 10 deletions src/ImageSharp/Primitives/Rectangle.cs
Original file line number Diff line number Diff line change
Expand Up @@ -260,11 +260,7 @@ public static Rectangle Ceiling(RectangleF 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
Expand All @@ -274,11 +270,7 @@ public static RectangleF Transform(Rectangle rectangle, Matrix3x2 matrix)
/// <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.
Expand Down
24 changes: 20 additions & 4 deletions src/ImageSharp/Primitives/RectangleF.cs
Original file line number Diff line number Diff line change
Expand Up @@ -236,9 +236,17 @@ public static RectangleF Inflate(RectangleF rectangle, float x, float y)
/// <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>
Expand All @@ -250,9 +258,17 @@ public static RectangleF Transform(RectangleF rectangle, Matrix3x2 matrix)
/// <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>
Expand Down
30 changes: 17 additions & 13 deletions tests/ImageSharp.Tests/Primitives/PointTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -159,9 +159,10 @@ public void RotateTest()
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]
Expand All @@ -170,8 +171,9 @@ public void SkewTest()
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]
Expand All @@ -181,8 +183,8 @@ public void TransformMatrix4x4_AffineMatchesMatrix3x2()
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);
}
Expand All @@ -191,19 +193,20 @@ public void TransformMatrix4x4_AffineMatchesMatrix3x2()
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]
public void TransformMatrix4x4_Translation()
{
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]
Expand All @@ -213,10 +216,11 @@ public void TransformMatrix4x4_Projective()
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]
Expand Down
Loading
Loading