diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultAddPixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultAddPixelBlender{TPixel}.cs
index 6a77034f2..441877f5f 100644
--- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultAddPixelBlender{TPixel}.cs
+++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultAddPixelBlender{TPixel}.cs
@@ -30,13 +30,25 @@ namespace ImageSharp.PixelFormats.PixelBlenders
///
public override void Compose(BufferSpan destination, BufferSpan background, BufferSpan source, BufferSpan amount)
{
- Guard.MustBeGreaterThanOrEqualTo(destination.Length, background.Length, nameof(destination));
- Guard.MustBeGreaterThanOrEqualTo(source.Length, background.Length, nameof(destination));
- Guard.MustBeGreaterThanOrEqualTo(amount.Length, background.Length, nameof(destination));
+ Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length));
+ Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length));
+ Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length));
- for (int i = 0; i < destination.Length; i++)
+ using (Buffer buffer = new Buffer(destination.Length * 3))
{
- destination[i] = PorterDuffFunctions.AddFunction(destination[i], source[i], amount[i]);
+ BufferSpan destinationSpan = buffer.Slice(0, destination.Length);
+ BufferSpan backgroundSpan = buffer.Slice(destination.Length, destination.Length);
+ BufferSpan sourceSpan = buffer.Slice(destination.Length * 2, destination.Length);
+
+ PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length);
+ PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length);
+
+ for (int i = 0; i < destination.Length; i++)
+ {
+ destinationSpan[i] = PorterDuffFunctions.AddFunction(backgroundSpan[i], sourceSpan[i], amount[i]);
+ }
+
+ PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length);
}
}
}
diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultDarkenPixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultDarkenPixelBlender{TPixel}.cs
index 62da4d934..c391aabe5 100644
--- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultDarkenPixelBlender{TPixel}.cs
+++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultDarkenPixelBlender{TPixel}.cs
@@ -30,13 +30,25 @@ namespace ImageSharp.PixelFormats.PixelBlenders
///
public override void Compose(BufferSpan destination, BufferSpan background, BufferSpan source, BufferSpan amount)
{
- Guard.MustBeGreaterThanOrEqualTo(destination.Length, background.Length, nameof(destination));
- Guard.MustBeGreaterThanOrEqualTo(source.Length, background.Length, nameof(destination));
- Guard.MustBeGreaterThanOrEqualTo(amount.Length, background.Length, nameof(destination));
+ Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length));
+ Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length));
+ Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length));
- for (int i = 0; i < destination.Length; i++)
+ using (Buffer buffer = new Buffer(destination.Length * 3))
{
- destination[i] = PorterDuffFunctions.DarkenFunction(destination[i], source[i], amount[i]);
+ BufferSpan destinationSpan = buffer.Slice(0, destination.Length);
+ BufferSpan backgroundSpan = buffer.Slice(destination.Length, destination.Length);
+ BufferSpan sourceSpan = buffer.Slice(destination.Length * 2, destination.Length);
+
+ PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length);
+ PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length);
+
+ for (int i = 0; i < destination.Length; i++)
+ {
+ destinationSpan[i] = PorterDuffFunctions.DarkenFunction(backgroundSpan[i], sourceSpan[i], amount[i]);
+ }
+
+ PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length);
}
}
}
diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultHardLightPixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultHardLightPixelBlender{TPixel}.cs
index 46e3023a7..7d98a05c9 100644
--- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultHardLightPixelBlender{TPixel}.cs
+++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultHardLightPixelBlender{TPixel}.cs
@@ -30,13 +30,25 @@ namespace ImageSharp.PixelFormats.PixelBlenders
///
public override void Compose(BufferSpan destination, BufferSpan background, BufferSpan source, BufferSpan amount)
{
- Guard.MustBeGreaterThanOrEqualTo(destination.Length, background.Length, nameof(destination));
- Guard.MustBeGreaterThanOrEqualTo(source.Length, background.Length, nameof(destination));
- Guard.MustBeGreaterThanOrEqualTo(amount.Length, background.Length, nameof(destination));
+ Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length));
+ Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length));
+ Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length));
- for (int i = 0; i < destination.Length; i++)
+ using (Buffer buffer = new Buffer(destination.Length * 3))
{
- destination[i] = PorterDuffFunctions.HardLightFunction(destination[i], source[i], amount[i]);
+ BufferSpan destinationSpan = buffer.Slice(0, destination.Length);
+ BufferSpan backgroundSpan = buffer.Slice(destination.Length, destination.Length);
+ BufferSpan sourceSpan = buffer.Slice(destination.Length * 2, destination.Length);
+
+ PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length);
+ PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length);
+
+ for (int i = 0; i < destination.Length; i++)
+ {
+ destinationSpan[i] = PorterDuffFunctions.HardLightFunction(backgroundSpan[i], sourceSpan[i], amount[i]);
+ }
+
+ PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length);
}
}
}
diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultLightenPixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultLightenPixelBlender{TPixel}.cs
index 0ee6f151d..e97c52edd 100644
--- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultLightenPixelBlender{TPixel}.cs
+++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultLightenPixelBlender{TPixel}.cs
@@ -30,13 +30,25 @@ namespace ImageSharp.PixelFormats.PixelBlenders
///
public override void Compose(BufferSpan destination, BufferSpan background, BufferSpan source, BufferSpan amount)
{
- Guard.MustBeGreaterThanOrEqualTo(destination.Length, background.Length, nameof(destination));
- Guard.MustBeGreaterThanOrEqualTo(source.Length, background.Length, nameof(destination));
- Guard.MustBeGreaterThanOrEqualTo(amount.Length, background.Length, nameof(destination));
+ Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length));
+ Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length));
+ Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length));
- for (int i = 0; i < destination.Length; i++)
+ using (Buffer buffer = new Buffer(destination.Length * 3))
{
- destination[i] = PorterDuffFunctions.LightenFunction(destination[i], source[i], amount[i]);
+ BufferSpan destinationSpan = buffer.Slice(0, destination.Length);
+ BufferSpan backgroundSpan = buffer.Slice(destination.Length, destination.Length);
+ BufferSpan sourceSpan = buffer.Slice(destination.Length * 2, destination.Length);
+
+ PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length);
+ PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length);
+
+ for (int i = 0; i < destination.Length; i++)
+ {
+ destinationSpan[i] = PorterDuffFunctions.LightenFunction(backgroundSpan[i], sourceSpan[i], amount[i]);
+ }
+
+ PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length);
}
}
}
diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultMultiplyPixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultMultiplyPixelBlender{TPixel}.cs
index f9f98d7ea..53748ad64 100644
--- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultMultiplyPixelBlender{TPixel}.cs
+++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultMultiplyPixelBlender{TPixel}.cs
@@ -30,13 +30,25 @@ namespace ImageSharp.PixelFormats.PixelBlenders
///
public override void Compose(BufferSpan destination, BufferSpan background, BufferSpan source, BufferSpan amount)
{
- Guard.MustBeGreaterThanOrEqualTo(destination.Length, background.Length, nameof(destination));
- Guard.MustBeGreaterThanOrEqualTo(source.Length, background.Length, nameof(destination));
- Guard.MustBeGreaterThanOrEqualTo(amount.Length, background.Length, nameof(destination));
+ Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length));
+ Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length));
+ Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length));
- for (int i = 0; i < destination.Length; i++)
+ using (Buffer buffer = new Buffer(destination.Length * 3))
{
- destination[i] = PorterDuffFunctions.MultiplyFunction(destination[i], source[i], amount[i]);
+ BufferSpan destinationSpan = buffer.Slice(0, destination.Length);
+ BufferSpan backgroundSpan = buffer.Slice(destination.Length, destination.Length);
+ BufferSpan sourceSpan = buffer.Slice(destination.Length * 2, destination.Length);
+
+ PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length);
+ PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length);
+
+ for (int i = 0; i < destination.Length; i++)
+ {
+ destinationSpan[i] = PorterDuffFunctions.MultiplyFunction(backgroundSpan[i], sourceSpan[i], amount[i]);
+ }
+
+ PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length);
}
}
}
diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultNormalPixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultNormalPixelBlender{TPixel}.cs
index e55be7202..823fd1c2f 100644
--- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultNormalPixelBlender{TPixel}.cs
+++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultNormalPixelBlender{TPixel}.cs
@@ -30,13 +30,25 @@ namespace ImageSharp.PixelFormats.PixelBlenders
///
public override void Compose(BufferSpan destination, BufferSpan background, BufferSpan source, BufferSpan amount)
{
- Guard.MustBeGreaterThanOrEqualTo(destination.Length, background.Length, nameof(destination));
- Guard.MustBeGreaterThanOrEqualTo(source.Length, background.Length, nameof(destination));
- Guard.MustBeGreaterThanOrEqualTo(amount.Length, background.Length, nameof(destination));
+ Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length));
+ Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length));
+ Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length));
- for (int i = 0; i < destination.Length; i++)
+ using (Buffer buffer = new Buffer(destination.Length * 3))
{
- destination[i] = PorterDuffFunctions.NormalBlendFunction(destination[i], source[i], amount[i]);
+ BufferSpan destinationSpan = buffer.Slice(0, destination.Length);
+ BufferSpan backgroundSpan = buffer.Slice(destination.Length, destination.Length);
+ BufferSpan sourceSpan = buffer.Slice(destination.Length * 2, destination.Length);
+
+ PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length);
+ PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length);
+
+ for (int i = 0; i < destination.Length; i++)
+ {
+ destinationSpan[i] = PorterDuffFunctions.NormalBlendFunction(backgroundSpan[i], sourceSpan[i], amount[i]);
+ }
+
+ PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length);
}
}
}
diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultOverlayPixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultOverlayPixelBlender{TPixel}.cs
index c6d2cfbcd..64393a66d 100644
--- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultOverlayPixelBlender{TPixel}.cs
+++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultOverlayPixelBlender{TPixel}.cs
@@ -30,13 +30,25 @@ namespace ImageSharp.PixelFormats.PixelBlenders
///
public override void Compose(BufferSpan destination, BufferSpan background, BufferSpan source, BufferSpan amount)
{
- Guard.MustBeGreaterThanOrEqualTo(destination.Length, background.Length, nameof(destination));
- Guard.MustBeGreaterThanOrEqualTo(source.Length, background.Length, nameof(destination));
- Guard.MustBeGreaterThanOrEqualTo(amount.Length, background.Length, nameof(destination));
+ Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length));
+ Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length));
+ Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length));
- for (int i = 0; i < destination.Length; i++)
+ using (Buffer buffer = new Buffer(destination.Length * 3))
{
- destination[i] = PorterDuffFunctions.OverlayFunction(destination[i], source[i], amount[i]);
+ BufferSpan destinationSpan = buffer.Slice(0, destination.Length);
+ BufferSpan backgroundSpan = buffer.Slice(destination.Length, destination.Length);
+ BufferSpan sourceSpan = buffer.Slice(destination.Length * 2, destination.Length);
+
+ PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length);
+ PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length);
+
+ for (int i = 0; i < destination.Length; i++)
+ {
+ destinationSpan[i] = PorterDuffFunctions.OverlayFunction(backgroundSpan[i], sourceSpan[i], amount[i]);
+ }
+
+ PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length);
}
}
}
diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultScreenPixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultScreenPixelBlender{TPixel}.cs
index cc8f77495..8538fda57 100644
--- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultScreenPixelBlender{TPixel}.cs
+++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultScreenPixelBlender{TPixel}.cs
@@ -30,13 +30,25 @@ namespace ImageSharp.PixelFormats.PixelBlenders
///
public override void Compose(BufferSpan destination, BufferSpan background, BufferSpan source, BufferSpan amount)
{
- Guard.MustBeGreaterThanOrEqualTo(destination.Length, background.Length, nameof(destination));
- Guard.MustBeGreaterThanOrEqualTo(source.Length, background.Length, nameof(destination));
- Guard.MustBeGreaterThanOrEqualTo(amount.Length, background.Length, nameof(destination));
+ Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length));
+ Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length));
+ Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length));
- for (int i = 0; i < destination.Length; i++)
+ using (Buffer buffer = new Buffer(destination.Length * 3))
{
- destination[i] = PorterDuffFunctions.ScreenFunction(destination[i], source[i], amount[i]);
+ BufferSpan destinationSpan = buffer.Slice(0, destination.Length);
+ BufferSpan backgroundSpan = buffer.Slice(destination.Length, destination.Length);
+ BufferSpan sourceSpan = buffer.Slice(destination.Length * 2, destination.Length);
+
+ PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length);
+ PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length);
+
+ for (int i = 0; i < destination.Length; i++)
+ {
+ destinationSpan[i] = PorterDuffFunctions.ScreenFunction(backgroundSpan[i], sourceSpan[i], amount[i]);
+ }
+
+ PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length);
}
}
}
diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultSubstractPixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultSubstractPixelBlender{TPixel}.cs
index 0d6428073..48b7196fa 100644
--- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultSubstractPixelBlender{TPixel}.cs
+++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultSubstractPixelBlender{TPixel}.cs
@@ -30,13 +30,25 @@ namespace ImageSharp.PixelFormats.PixelBlenders
///
public override void Compose(BufferSpan destination, BufferSpan background, BufferSpan source, BufferSpan amount)
{
- Guard.MustBeGreaterThanOrEqualTo(destination.Length, background.Length, nameof(destination));
- Guard.MustBeGreaterThanOrEqualTo(source.Length, background.Length, nameof(destination));
- Guard.MustBeGreaterThanOrEqualTo(amount.Length, background.Length, nameof(destination));
+ Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length));
+ Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length));
+ Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length));
- for (int i = 0; i < destination.Length; i++)
+ using (Buffer buffer = new Buffer(destination.Length * 3))
{
- destination[i] = PorterDuffFunctions.SubstractFunction(destination[i], source[i], amount[i]);
+ BufferSpan destinationSpan = buffer.Slice(0, destination.Length);
+ BufferSpan backgroundSpan = buffer.Slice(destination.Length, destination.Length);
+ BufferSpan sourceSpan = buffer.Slice(destination.Length * 2, destination.Length);
+
+ PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length);
+ PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length);
+
+ for (int i = 0; i < destination.Length; i++)
+ {
+ destinationSpan[i] = PorterDuffFunctions.SubstractFunction(backgroundSpan[i], sourceSpan[i], amount[i]);
+ }
+
+ PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length);
}
}
}
diff --git a/src/ImageSharp/PixelFormats/PorterDuffFunctions.cs b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs
similarity index 63%
rename from src/ImageSharp/PixelFormats/PorterDuffFunctions.cs
rename to src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs
index bbf1811af..6b5126f72 100644
--- a/src/ImageSharp/PixelFormats/PorterDuffFunctions.cs
+++ b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs
@@ -1,274 +1,242 @@
-//
-// Copyright (c) James Jackson-South and contributors.
-// Licensed under the Apache License, Version 2.0.
-//
-
-namespace ImageSharp.PixelFormats
-{
- using System.Numerics;
- using System.Runtime.CompilerServices;
-
- ///
- /// Collection of Porter Duff alpha blending functions
- ///
- /// Pixel Format
- ///
- /// These functions are designed to be a general solution for all color cases,
- /// that is, they take in account the alpha value of both the backdrop
- /// and source, and there's no need to alpha-premultiply neither the backdrop
- /// nor the source.
- /// Note there are faster functions for when the backdrop color is known
- /// to be opaque
- ///
- internal static class PorterDuffFunctions
- where TPixel : IPixel
- {
- ///
- /// Source over backdrop
- ///
- /// Backgrop color
- /// Source color
- /// Opacity applied to Source Alpha
- /// Output color
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static TPixel NormalBlendFunction(TPixel backdrop, TPixel source, float opacity)
- {
- Vector4 l = source.ToVector4();
- l.W *= opacity;
- if (l.W == 0)
- {
- return backdrop;
- }
-
- Vector4 b = backdrop.ToVector4();
-
- return Compose(b, l, l);
- }
-
- ///
- /// Source multiplied by backdrop
- ///
- /// Backgrop color
- /// Source color
- /// Opacity applied to Source Alpha
- /// Output color
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static TPixel MultiplyFunction(TPixel backdrop, TPixel source, float opacity)
- {
- Vector4 l = source.ToVector4();
- l.W *= opacity;
- if (l.W == 0)
- {
- return backdrop;
- }
-
- Vector4 b = backdrop.ToVector4();
-
- return Compose(b, l, b * l);
- }
-
- ///
- /// Source added to backdrop
- ///
- /// Backgrop color
- /// Source color
- /// Opacity applied to Source Alpha
- /// Output color
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static TPixel AddFunction(TPixel backdrop, TPixel source, float opacity)
- {
- Vector4 l = source.ToVector4();
- l.W *= opacity;
- if (l.W == 0)
- {
- return backdrop;
- }
-
- Vector4 b = backdrop.ToVector4();
-
- return Compose(b, l, Vector4.Min(Vector4.One, b + l));
- }
-
- ///
- /// Source substracted from backdrop
- ///
- /// Backgrop color
- /// Source color
- /// Opacity applied to Source Alpha
- /// Output color
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static TPixel SubstractFunction(TPixel backdrop, TPixel source, float opacity)
- {
- Vector4 l = source.ToVector4();
- l.W *= opacity;
- if (l.W == 0)
- {
- return backdrop;
- }
-
- Vector4 b = backdrop.ToVector4();
-
- return Compose(b, l, Vector4.Max(Vector4.Zero, b - l));
- }
-
- ///
- /// Complement of source multiplied by the complement of backdrop
- ///
- /// Backgrop color
- /// Source color
- /// Opacity applied to Source Alpha
- /// Output color
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static TPixel ScreenFunction(TPixel backdrop, TPixel source, float opacity)
- {
- Vector4 l = source.ToVector4();
- l.W *= opacity;
- if (l.W == 0)
- {
- return backdrop;
- }
-
- Vector4 b = backdrop.ToVector4();
-
- return Compose(b, l, Vector4.One - ((Vector4.One - b) * (Vector4.One - l)));
- }
-
- ///
- /// Per element, chooses the smallest value of source and backdrop
- ///
- /// Backgrop color
- /// Source color
- /// Opacity applied to Source Alpha
- /// Output color
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static TPixel DarkenFunction(TPixel backdrop, TPixel source, float opacity)
- {
- Vector4 l = source.ToVector4();
- l.W *= opacity;
- if (l.W == 0)
- {
- return backdrop;
- }
-
- Vector4 b = backdrop.ToVector4();
-
- return Compose(b, l, Vector4.Min(b, l));
- }
-
- ///
- /// Per element, chooses the largest value of source and backdrop
- ///
- /// Backgrop color
- /// Source color
- /// Opacity applied to Source Alpha
- /// Output color
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static TPixel LightenFunction(TPixel backdrop, TPixel source, float opacity)
- {
- Vector4 l = source.ToVector4();
- l.W *= opacity;
- if (l.W == 0)
- {
- return backdrop;
- }
-
- Vector4 b = backdrop.ToVector4();
-
- return Compose(b, l, Vector4.Max(b, l));
- }
-
- ///
- /// Overlays source over backdrop
- ///
- /// Backgrop color
- /// Source color
- /// Opacity applied to Source Alpha
- /// Output color
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static TPixel OverlayFunction(TPixel backdrop, TPixel source, float opacity)
- {
- Vector4 l = source.ToVector4();
- l.W *= opacity;
- if (l.W == 0)
- {
- return backdrop;
- }
-
- Vector4 b = backdrop.ToVector4();
-
- float cr = OverlayValueFunction(b.X, l.X);
- float cg = OverlayValueFunction(b.Y, l.Y);
- float cb = OverlayValueFunction(b.Z, l.Z);
-
- return Compose(b, l, Vector4.Min(Vector4.One, new Vector4(cr, cg, cb, 0)));
- }
-
- ///
- /// Hard light effect
- ///
- /// Backgrop color
- /// Source color
- /// Opacity applied to Source Alpha
- /// Output color
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static TPixel HardLightFunction(TPixel backdrop, TPixel source, float opacity)
- {
- Vector4 l = source.ToVector4();
- l.W *= opacity;
- if (l.W == 0)
- {
- return backdrop;
- }
-
- Vector4 b = backdrop.ToVector4();
-
- float cr = OverlayValueFunction(l.X, b.X);
- float cg = OverlayValueFunction(l.Y, b.Y);
- float cb = OverlayValueFunction(l.Z, b.Z);
-
- return Compose(b, l, Vector4.Min(Vector4.One, new Vector4(cr, cg, cb, 0)));
- }
-
- ///
- /// Helper function for Overlay andHardLight modes
- ///
- /// Backdrop color element
- /// Source color element
- /// Overlay value
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static float OverlayValueFunction(float backdrop, float source)
- {
- return backdrop <= 0.5f ? (2 * backdrop * source) : 1 - ((2 * (1 - source)) * (1 - backdrop));
- }
-
- ///
- /// General composition function for all modes, with a general solution for alpha channel
- ///
- /// Original backgrop color
- /// Original source color
- /// Desired transformed color, without taking Alpha channel in account
- /// The final color
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static TPixel Compose(Vector4 backdrop, Vector4 source, Vector4 xform)
- {
- DebugGuard.MustBeGreaterThan(source.W, 0, nameof(source.W));
-
- // calculate weights
- float xw = backdrop.W * source.W;
- float bw = backdrop.W - xw;
- float sw = source.W - xw;
-
- // calculate final alpha
- float a = xw + bw + sw;
-
- // calculate final value
- xform = ((xform * xw) + (backdrop * bw) + (source * sw)) / a;
- xform.W = a;
-
- TPixel packed = default(TPixel);
- packed.PackFromVector4(xform);
-
- return packed;
- }
- }
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.PixelFormats.PixelBlenders
+{
+ using System.Numerics;
+ using System.Runtime.CompilerServices;
+
+ ///
+ /// Collection of Porter Duff alpha blending functions
+ ///
+ ///
+ /// These functions are designed to be a general solution for all color cases,
+ /// that is, they take in account the alpha value of both the backdrop
+ /// and source, and there's no need to alpha-premultiply neither the backdrop
+ /// nor the source.
+ /// Note there are faster functions for when the backdrop color is known
+ /// to be opaque
+ ///
+ internal static class PorterDuffFunctions
+ {
+ ///
+ /// Source over backdrop
+ ///
+ /// Backgrop color
+ /// Source color
+ /// Opacity applied to Source Alpha
+ /// Output color
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Vector4 NormalBlendFunction(Vector4 backdrop, Vector4 source, float opacity)
+ {
+ source.W *= opacity;
+ if (source.W == 0)
+ {
+ return backdrop;
+ }
+
+ return Compose(backdrop, source, source);
+ }
+
+ ///
+ /// Source multiplied by backdrop
+ ///
+ /// Backgrop color
+ /// Source color
+ /// Opacity applied to Source Alpha
+ /// Output color
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Vector4 MultiplyFunction(Vector4 backdrop, Vector4 source, float opacity)
+ {
+ source.W *= opacity;
+ if (source.W == 0)
+ {
+ return backdrop;
+ }
+
+ return Compose(backdrop, source, backdrop * source);
+ }
+
+ ///
+ /// Source added to backdrop
+ ///
+ /// Backgrop color
+ /// Source color
+ /// Opacity applied to Source Alpha
+ /// Output color
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Vector4 AddFunction(Vector4 backdrop, Vector4 source, float opacity)
+ {
+ source.W *= opacity;
+ if (source.W == 0)
+ {
+ return backdrop;
+ }
+
+ return Compose(backdrop, source, Vector4.Min(Vector4.One, backdrop + source));
+ }
+
+ ///
+ /// Source substracted from backdrop
+ ///
+ /// Backgrop color
+ /// Source color
+ /// Opacity applied to Source Alpha
+ /// Output color
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Vector4 SubstractFunction(Vector4 backdrop, Vector4 source, float opacity)
+ {
+ source.W *= opacity;
+ if (source.W == 0)
+ {
+ return backdrop;
+ }
+
+ return Compose(backdrop, source, Vector4.Max(Vector4.Zero, backdrop - source));
+ }
+
+ ///
+ /// Complement of source multiplied by the complement of backdrop
+ ///
+ /// Backgrop color
+ /// Source color
+ /// Opacity applied to Source Alpha
+ /// Output color
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Vector4 ScreenFunction(Vector4 backdrop, Vector4 source, float opacity)
+ {
+ source.W *= opacity;
+ if (source.W == 0)
+ {
+ return backdrop;
+ }
+
+ return Compose(backdrop, source, Vector4.One - ((Vector4.One - backdrop) * (Vector4.One - source)));
+ }
+
+ ///
+ /// Per element, chooses the smallest value of source and backdrop
+ ///
+ /// Backgrop color
+ /// Source color
+ /// Opacity applied to Source Alpha
+ /// Output color
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Vector4 DarkenFunction(Vector4 backdrop, Vector4 source, float opacity)
+ {
+ source.W *= opacity;
+ if (source.W == 0)
+ {
+ return backdrop;
+ }
+
+ return Compose(backdrop, source, Vector4.Min(backdrop, source));
+ }
+
+ ///
+ /// Per element, chooses the largest value of source and backdrop
+ ///
+ /// Backgrop color
+ /// Source color
+ /// Opacity applied to Source Alpha
+ /// Output color
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Vector4 LightenFunction(Vector4 backdrop, Vector4 source, float opacity)
+ {
+ source.W *= opacity;
+ if (source.W == 0)
+ {
+ return backdrop;
+ }
+
+ return Compose(backdrop, source, Vector4.Max(backdrop, source));
+ }
+
+ ///
+ /// Overlays source over backdrop
+ ///
+ /// Backgrop color
+ /// Source color
+ /// Opacity applied to Source Alpha
+ /// Output color
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Vector4 OverlayFunction(Vector4 backdrop, Vector4 source, float opacity)
+ {
+ source.W *= opacity;
+ if (source.W == 0)
+ {
+ return backdrop;
+ }
+
+ float cr = OverlayValueFunction(backdrop.X, source.X);
+ float cg = OverlayValueFunction(backdrop.Y, source.Y);
+ float cb = OverlayValueFunction(backdrop.Z, source.Z);
+
+ return Compose(backdrop, source, Vector4.Min(Vector4.One, new Vector4(cr, cg, cb, 0)));
+ }
+
+ ///
+ /// Hard light effect
+ ///
+ /// Backgrop color
+ /// Source color
+ /// Opacity applied to Source Alpha
+ /// Output color
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Vector4 HardLightFunction(Vector4 backdrop, Vector4 source, float opacity)
+ {
+ source.W *= opacity;
+ if (source.W == 0)
+ {
+ return backdrop;
+ }
+
+ float cr = OverlayValueFunction(source.X, backdrop.X);
+ float cg = OverlayValueFunction(source.Y, backdrop.Y);
+ float cb = OverlayValueFunction(source.Z, backdrop.Z);
+
+ return Compose(backdrop, source, Vector4.Min(Vector4.One, new Vector4(cr, cg, cb, 0)));
+ }
+
+ ///
+ /// Helper function for Overlay andHardLight modes
+ ///
+ /// Backdrop color element
+ /// Source color element
+ /// Overlay value
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static float OverlayValueFunction(float backdrop, float source)
+ {
+ return backdrop <= 0.5f ? (2 * backdrop * source) : 1 - ((2 * (1 - source)) * (1 - backdrop));
+ }
+
+ ///
+ /// General composition function for all modes, with a general solution for alpha channel
+ ///
+ /// Original backgrop color
+ /// Original source color
+ /// Desired transformed color, without taking Alpha channel in account
+ /// The final color
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static Vector4 Compose(Vector4 backdrop, Vector4 source, Vector4 xform)
+ {
+ DebugGuard.MustBeGreaterThan(source.W, 0, nameof(source.W));
+
+ // calculate weights
+ float xw = backdrop.W * source.W;
+ float bw = backdrop.W - xw;
+ float sw = source.W - xw;
+
+ // calculate final alpha
+ float a = xw + bw + sw;
+
+ // calculate final value
+ xform = ((xform * xw) + (backdrop * bw) + (source * sw)) / a;
+ xform.W = a;
+
+ return xform;
+ }
+ }
}
\ No newline at end of file
diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions{TPixel}.cs
new file mode 100644
index 000000000..4e829212e
--- /dev/null
+++ b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions{TPixel}.cs
@@ -0,0 +1,151 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.PixelFormats.PixelBlenders
+{
+ using System.Numerics;
+ using System.Runtime.CompilerServices;
+
+ ///
+ /// Collection of Porter Duff alpha blending functions
+ ///
+ /// Pixel Format
+ ///
+ /// These functions are designed to be a general solution for all color cases,
+ /// that is, they take in account the alpha value of both the backdrop
+ /// and source, and there's no need to alpha-premultiply neither the backdrop
+ /// nor the source.
+ /// Note there are faster functions for when the backdrop color is known
+ /// to be opaque
+ ///
+ internal static class PorterDuffFunctions
+ where TPixel : IPixel
+ {
+ ///
+ /// Source over backdrop
+ ///
+ /// Backgrop color
+ /// Source color
+ /// Opacity applied to Source Alpha
+ /// Output color
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static TPixel NormalBlendFunction(TPixel backdrop, TPixel source, float opacity)
+ {
+ return ToPixel(PorterDuffFunctions.NormalBlendFunction(backdrop.ToVector4(), source.ToVector4(), opacity));
+ }
+
+ ///
+ /// Source multiplied by backdrop
+ ///
+ /// Backgrop color
+ /// Source color
+ /// Opacity applied to Source Alpha
+ /// Output color
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static TPixel MultiplyFunction(TPixel backdrop, TPixel source, float opacity)
+ {
+ return ToPixel(PorterDuffFunctions.MultiplyFunction(backdrop.ToVector4(), source.ToVector4(), opacity));
+ }
+
+ ///
+ /// Source added to backdrop
+ ///
+ /// Backgrop color
+ /// Source color
+ /// Opacity applied to Source Alpha
+ /// Output color
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static TPixel AddFunction(TPixel backdrop, TPixel source, float opacity)
+ {
+ return ToPixel(PorterDuffFunctions.AddFunction(backdrop.ToVector4(), source.ToVector4(), opacity));
+ }
+
+ ///
+ /// Source substracted from backdrop
+ ///
+ /// Backgrop color
+ /// Source color
+ /// Opacity applied to Source Alpha
+ /// Output color
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static TPixel SubstractFunction(TPixel backdrop, TPixel source, float opacity)
+ {
+ return ToPixel(PorterDuffFunctions.SubstractFunction(backdrop.ToVector4(), source.ToVector4(), opacity));
+ }
+
+ ///
+ /// Complement of source multiplied by the complement of backdrop
+ ///
+ /// Backgrop color
+ /// Source color
+ /// Opacity applied to Source Alpha
+ /// Output color
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static TPixel ScreenFunction(TPixel backdrop, TPixel source, float opacity)
+ {
+ return ToPixel(PorterDuffFunctions.ScreenFunction(backdrop.ToVector4(), source.ToVector4(), opacity));
+ }
+
+ ///
+ /// Per element, chooses the smallest value of source and backdrop
+ ///
+ /// Backgrop color
+ /// Source color
+ /// Opacity applied to Source Alpha
+ /// Output color
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static TPixel DarkenFunction(TPixel backdrop, TPixel source, float opacity)
+ {
+ return ToPixel(PorterDuffFunctions.DarkenFunction(backdrop.ToVector4(), source.ToVector4(), opacity));
+ }
+
+ ///
+ /// Per element, chooses the largest value of source and backdrop
+ ///
+ /// Backgrop color
+ /// Source color
+ /// Opacity applied to Source Alpha
+ /// Output color
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static TPixel LightenFunction(TPixel backdrop, TPixel source, float opacity)
+ {
+ return ToPixel(PorterDuffFunctions.LightenFunction(backdrop.ToVector4(), source.ToVector4(), opacity));
+ }
+
+ ///
+ /// Overlays source over backdrop
+ ///
+ /// Backgrop color
+ /// Source color
+ /// Opacity applied to Source Alpha
+ /// Output color
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static TPixel OverlayFunction(TPixel backdrop, TPixel source, float opacity)
+ {
+ return ToPixel(PorterDuffFunctions.OverlayFunction(backdrop.ToVector4(), source.ToVector4(), opacity));
+ }
+
+ ///
+ /// Hard light effect
+ ///
+ /// Backgrop color
+ /// Source color
+ /// Opacity applied to Source Alpha
+ /// Output color
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static TPixel HardLightFunction(TPixel backdrop, TPixel source, float opacity)
+ {
+ return ToPixel(PorterDuffFunctions.HardLightFunction(backdrop.ToVector4(), source.ToVector4(), opacity));
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static TPixel ToPixel(Vector4 vector)
+ {
+ TPixel p = default(TPixel);
+ p.PackFromVector4(vector);
+ return p;
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs b/tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs
new file mode 100644
index 000000000..a616733b5
--- /dev/null
+++ b/tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs
@@ -0,0 +1,103 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Benchmarks
+{
+
+ using BenchmarkDotNet.Attributes;
+ using ImageSharp.PixelFormats;
+ using ImageSharp.Drawing;
+ using ImageSharp.Processing.Processors;
+ using CoreImage = ImageSharp.Image;
+ using CoreSize = ImageSharp.Size;
+ using System.Numerics;
+ using ImageSharp.PixelFormats.PixelBlenders;
+
+ public class PorterDuffBulkVsPixel : BenchmarkBase
+ {
+ private void BulkVectorConvert(BufferSpan destination, BufferSpan background, BufferSpan source, BufferSpan amount)
+ where TPixel : struct, IPixel
+ {
+ Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length));
+ Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length));
+ Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length));
+
+ using (Buffer buffer = new Buffer(destination.Length * 3))
+ {
+ BufferSpan destinationSpan = buffer.Slice(0, destination.Length);
+ BufferSpan backgroundSpan = buffer.Slice(destination.Length, destination.Length);
+ BufferSpan sourceSpan = buffer.Slice(destination.Length * 2, destination.Length);
+
+ PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length);
+ PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length);
+
+ for (int i = 0; i < destination.Length; i++)
+ {
+ destinationSpan[i] = PorterDuffFunctions.NormalBlendFunction(backgroundSpan[i], sourceSpan[i], amount[i]);
+ }
+
+ PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length);
+ }
+ }
+ private void BulkPixelConvert(BufferSpan destination, BufferSpan background, BufferSpan source, BufferSpan amount)
+ where TPixel : struct, IPixel
+ {
+ Guard.MustBeGreaterThanOrEqualTo(destination.Length, background.Length, nameof(destination));
+ Guard.MustBeGreaterThanOrEqualTo(source.Length, background.Length, nameof(destination));
+ Guard.MustBeGreaterThanOrEqualTo(amount.Length, background.Length, nameof(destination));
+
+ for (int i = 0; i < destination.Length; i++)
+ {
+ destination[i] = PorterDuffFunctions.NormalBlendFunction(destination[i], source[i], amount[i]);
+ }
+ }
+
+ [Benchmark(Description = "ImageSharp BulkVectorConvert")]
+ public CoreSize BulkVectorConvert()
+ {
+ using (CoreImage image = new CoreImage(800, 800))
+ {
+ Buffer amounts = new Buffer(image.Width);
+
+ for (int x = 0; x < image.Width; x++)
+ {
+ amounts[x] = 1;
+ }
+ using (PixelAccessor pixels = image.Lock())
+ {
+ for (int y = 0; y < image.Height; y++)
+ {
+ BufferSpan span = pixels.GetRowSpan(y);
+ BulkVectorConvert(span, span, span, amounts);
+ }
+ }
+ return new CoreSize(image.Width, image.Height);
+ }
+ }
+
+ [Benchmark(Description = "ImageSharp BulkPixelConvert")]
+ public CoreSize BulkPixelConvert()
+ {
+ using (CoreImage image = new CoreImage(800, 800))
+ {
+ Buffer amounts = new Buffer(image.Width);
+
+ for (int x = 0; x < image.Width; x++)
+ {
+ amounts[x] = 1;
+ }
+ using (PixelAccessor pixels = image.Lock())
+ {
+ for (int y = 0; y < image.Height; y++)
+ {
+ BufferSpan span = pixels.GetRowSpan(y);
+ BulkPixelConvert(span, span, span, amounts);
+ }
+ }
+ return new CoreSize(image.Width, image.Height);
+ }
+ }
+ }
+}