Browse Source

Add Vector4 based functions.

af/merge-core
Scott Williams 9 years ago
parent
commit
ff97d6b1c9
  1. 22
      src/ImageSharp/PixelFormats/PixelBlenders/DefaultAddPixelBlender{TPixel}.cs
  2. 22
      src/ImageSharp/PixelFormats/PixelBlenders/DefaultDarkenPixelBlender{TPixel}.cs
  3. 22
      src/ImageSharp/PixelFormats/PixelBlenders/DefaultHardLightPixelBlender{TPixel}.cs
  4. 22
      src/ImageSharp/PixelFormats/PixelBlenders/DefaultLightenPixelBlender{TPixel}.cs
  5. 22
      src/ImageSharp/PixelFormats/PixelBlenders/DefaultMultiplyPixelBlender{TPixel}.cs
  6. 22
      src/ImageSharp/PixelFormats/PixelBlenders/DefaultNormalPixelBlender{TPixel}.cs
  7. 22
      src/ImageSharp/PixelFormats/PixelBlenders/DefaultOverlayPixelBlender{TPixel}.cs
  8. 22
      src/ImageSharp/PixelFormats/PixelBlenders/DefaultScreenPixelBlender{TPixel}.cs
  9. 22
      src/ImageSharp/PixelFormats/PixelBlenders/DefaultSubstractPixelBlender{TPixel}.cs
  10. 514
      src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs
  11. 151
      src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions{TPixel}.cs
  12. 103
      tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs

22
src/ImageSharp/PixelFormats/PixelBlenders/DefaultAddPixelBlender{TPixel}.cs

@ -30,13 +30,25 @@ namespace ImageSharp.PixelFormats.PixelBlenders
/// <inheritdoc /> /// <inheritdoc />
public override void Compose(BufferSpan<TPixel> destination, BufferSpan<TPixel> background, BufferSpan<TPixel> source, BufferSpan<float> amount) public override void Compose(BufferSpan<TPixel> destination, BufferSpan<TPixel> background, BufferSpan<TPixel> source, BufferSpan<float> amount)
{ {
Guard.MustBeGreaterThanOrEqualTo(destination.Length, background.Length, nameof(destination)); Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length));
Guard.MustBeGreaterThanOrEqualTo(source.Length, background.Length, nameof(destination)); Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length));
Guard.MustBeGreaterThanOrEqualTo(amount.Length, background.Length, nameof(destination)); Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length));
for (int i = 0; i < destination.Length; i++) using (Buffer<Vector4> buffer = new Buffer<Vector4>(destination.Length * 3))
{ {
destination[i] = PorterDuffFunctions<TPixel>.AddFunction(destination[i], source[i], amount[i]); BufferSpan<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
BufferSpan<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
BufferSpan<Vector4> sourceSpan = buffer.Slice(destination.Length * 2, destination.Length);
PixelOperations<TPixel>.Instance.ToVector4(background, backgroundSpan, destination.Length);
PixelOperations<TPixel>.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<TPixel>.Instance.PackFromVector4(destinationSpan, destination, destination.Length);
} }
} }
} }

22
src/ImageSharp/PixelFormats/PixelBlenders/DefaultDarkenPixelBlender{TPixel}.cs

@ -30,13 +30,25 @@ namespace ImageSharp.PixelFormats.PixelBlenders
/// <inheritdoc /> /// <inheritdoc />
public override void Compose(BufferSpan<TPixel> destination, BufferSpan<TPixel> background, BufferSpan<TPixel> source, BufferSpan<float> amount) public override void Compose(BufferSpan<TPixel> destination, BufferSpan<TPixel> background, BufferSpan<TPixel> source, BufferSpan<float> amount)
{ {
Guard.MustBeGreaterThanOrEqualTo(destination.Length, background.Length, nameof(destination)); Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length));
Guard.MustBeGreaterThanOrEqualTo(source.Length, background.Length, nameof(destination)); Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length));
Guard.MustBeGreaterThanOrEqualTo(amount.Length, background.Length, nameof(destination)); Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length));
for (int i = 0; i < destination.Length; i++) using (Buffer<Vector4> buffer = new Buffer<Vector4>(destination.Length * 3))
{ {
destination[i] = PorterDuffFunctions<TPixel>.DarkenFunction(destination[i], source[i], amount[i]); BufferSpan<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
BufferSpan<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
BufferSpan<Vector4> sourceSpan = buffer.Slice(destination.Length * 2, destination.Length);
PixelOperations<TPixel>.Instance.ToVector4(background, backgroundSpan, destination.Length);
PixelOperations<TPixel>.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<TPixel>.Instance.PackFromVector4(destinationSpan, destination, destination.Length);
} }
} }
} }

22
src/ImageSharp/PixelFormats/PixelBlenders/DefaultHardLightPixelBlender{TPixel}.cs

@ -30,13 +30,25 @@ namespace ImageSharp.PixelFormats.PixelBlenders
/// <inheritdoc /> /// <inheritdoc />
public override void Compose(BufferSpan<TPixel> destination, BufferSpan<TPixel> background, BufferSpan<TPixel> source, BufferSpan<float> amount) public override void Compose(BufferSpan<TPixel> destination, BufferSpan<TPixel> background, BufferSpan<TPixel> source, BufferSpan<float> amount)
{ {
Guard.MustBeGreaterThanOrEqualTo(destination.Length, background.Length, nameof(destination)); Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length));
Guard.MustBeGreaterThanOrEqualTo(source.Length, background.Length, nameof(destination)); Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length));
Guard.MustBeGreaterThanOrEqualTo(amount.Length, background.Length, nameof(destination)); Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length));
for (int i = 0; i < destination.Length; i++) using (Buffer<Vector4> buffer = new Buffer<Vector4>(destination.Length * 3))
{ {
destination[i] = PorterDuffFunctions<TPixel>.HardLightFunction(destination[i], source[i], amount[i]); BufferSpan<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
BufferSpan<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
BufferSpan<Vector4> sourceSpan = buffer.Slice(destination.Length * 2, destination.Length);
PixelOperations<TPixel>.Instance.ToVector4(background, backgroundSpan, destination.Length);
PixelOperations<TPixel>.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<TPixel>.Instance.PackFromVector4(destinationSpan, destination, destination.Length);
} }
} }
} }

22
src/ImageSharp/PixelFormats/PixelBlenders/DefaultLightenPixelBlender{TPixel}.cs

@ -30,13 +30,25 @@ namespace ImageSharp.PixelFormats.PixelBlenders
/// <inheritdoc /> /// <inheritdoc />
public override void Compose(BufferSpan<TPixel> destination, BufferSpan<TPixel> background, BufferSpan<TPixel> source, BufferSpan<float> amount) public override void Compose(BufferSpan<TPixel> destination, BufferSpan<TPixel> background, BufferSpan<TPixel> source, BufferSpan<float> amount)
{ {
Guard.MustBeGreaterThanOrEqualTo(destination.Length, background.Length, nameof(destination)); Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length));
Guard.MustBeGreaterThanOrEqualTo(source.Length, background.Length, nameof(destination)); Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length));
Guard.MustBeGreaterThanOrEqualTo(amount.Length, background.Length, nameof(destination)); Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length));
for (int i = 0; i < destination.Length; i++) using (Buffer<Vector4> buffer = new Buffer<Vector4>(destination.Length * 3))
{ {
destination[i] = PorterDuffFunctions<TPixel>.LightenFunction(destination[i], source[i], amount[i]); BufferSpan<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
BufferSpan<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
BufferSpan<Vector4> sourceSpan = buffer.Slice(destination.Length * 2, destination.Length);
PixelOperations<TPixel>.Instance.ToVector4(background, backgroundSpan, destination.Length);
PixelOperations<TPixel>.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<TPixel>.Instance.PackFromVector4(destinationSpan, destination, destination.Length);
} }
} }
} }

22
src/ImageSharp/PixelFormats/PixelBlenders/DefaultMultiplyPixelBlender{TPixel}.cs

@ -30,13 +30,25 @@ namespace ImageSharp.PixelFormats.PixelBlenders
/// <inheritdoc /> /// <inheritdoc />
public override void Compose(BufferSpan<TPixel> destination, BufferSpan<TPixel> background, BufferSpan<TPixel> source, BufferSpan<float> amount) public override void Compose(BufferSpan<TPixel> destination, BufferSpan<TPixel> background, BufferSpan<TPixel> source, BufferSpan<float> amount)
{ {
Guard.MustBeGreaterThanOrEqualTo(destination.Length, background.Length, nameof(destination)); Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length));
Guard.MustBeGreaterThanOrEqualTo(source.Length, background.Length, nameof(destination)); Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length));
Guard.MustBeGreaterThanOrEqualTo(amount.Length, background.Length, nameof(destination)); Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length));
for (int i = 0; i < destination.Length; i++) using (Buffer<Vector4> buffer = new Buffer<Vector4>(destination.Length * 3))
{ {
destination[i] = PorterDuffFunctions<TPixel>.MultiplyFunction(destination[i], source[i], amount[i]); BufferSpan<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
BufferSpan<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
BufferSpan<Vector4> sourceSpan = buffer.Slice(destination.Length * 2, destination.Length);
PixelOperations<TPixel>.Instance.ToVector4(background, backgroundSpan, destination.Length);
PixelOperations<TPixel>.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<TPixel>.Instance.PackFromVector4(destinationSpan, destination, destination.Length);
} }
} }
} }

22
src/ImageSharp/PixelFormats/PixelBlenders/DefaultNormalPixelBlender{TPixel}.cs

@ -30,13 +30,25 @@ namespace ImageSharp.PixelFormats.PixelBlenders
/// <inheritdoc /> /// <inheritdoc />
public override void Compose(BufferSpan<TPixel> destination, BufferSpan<TPixel> background, BufferSpan<TPixel> source, BufferSpan<float> amount) public override void Compose(BufferSpan<TPixel> destination, BufferSpan<TPixel> background, BufferSpan<TPixel> source, BufferSpan<float> amount)
{ {
Guard.MustBeGreaterThanOrEqualTo(destination.Length, background.Length, nameof(destination)); Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length));
Guard.MustBeGreaterThanOrEqualTo(source.Length, background.Length, nameof(destination)); Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length));
Guard.MustBeGreaterThanOrEqualTo(amount.Length, background.Length, nameof(destination)); Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length));
for (int i = 0; i < destination.Length; i++) using (Buffer<Vector4> buffer = new Buffer<Vector4>(destination.Length * 3))
{ {
destination[i] = PorterDuffFunctions<TPixel>.NormalBlendFunction(destination[i], source[i], amount[i]); BufferSpan<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
BufferSpan<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
BufferSpan<Vector4> sourceSpan = buffer.Slice(destination.Length * 2, destination.Length);
PixelOperations<TPixel>.Instance.ToVector4(background, backgroundSpan, destination.Length);
PixelOperations<TPixel>.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<TPixel>.Instance.PackFromVector4(destinationSpan, destination, destination.Length);
} }
} }
} }

22
src/ImageSharp/PixelFormats/PixelBlenders/DefaultOverlayPixelBlender{TPixel}.cs

@ -30,13 +30,25 @@ namespace ImageSharp.PixelFormats.PixelBlenders
/// <inheritdoc /> /// <inheritdoc />
public override void Compose(BufferSpan<TPixel> destination, BufferSpan<TPixel> background, BufferSpan<TPixel> source, BufferSpan<float> amount) public override void Compose(BufferSpan<TPixel> destination, BufferSpan<TPixel> background, BufferSpan<TPixel> source, BufferSpan<float> amount)
{ {
Guard.MustBeGreaterThanOrEqualTo(destination.Length, background.Length, nameof(destination)); Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length));
Guard.MustBeGreaterThanOrEqualTo(source.Length, background.Length, nameof(destination)); Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length));
Guard.MustBeGreaterThanOrEqualTo(amount.Length, background.Length, nameof(destination)); Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length));
for (int i = 0; i < destination.Length; i++) using (Buffer<Vector4> buffer = new Buffer<Vector4>(destination.Length * 3))
{ {
destination[i] = PorterDuffFunctions<TPixel>.OverlayFunction(destination[i], source[i], amount[i]); BufferSpan<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
BufferSpan<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
BufferSpan<Vector4> sourceSpan = buffer.Slice(destination.Length * 2, destination.Length);
PixelOperations<TPixel>.Instance.ToVector4(background, backgroundSpan, destination.Length);
PixelOperations<TPixel>.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<TPixel>.Instance.PackFromVector4(destinationSpan, destination, destination.Length);
} }
} }
} }

22
src/ImageSharp/PixelFormats/PixelBlenders/DefaultScreenPixelBlender{TPixel}.cs

@ -30,13 +30,25 @@ namespace ImageSharp.PixelFormats.PixelBlenders
/// <inheritdoc /> /// <inheritdoc />
public override void Compose(BufferSpan<TPixel> destination, BufferSpan<TPixel> background, BufferSpan<TPixel> source, BufferSpan<float> amount) public override void Compose(BufferSpan<TPixel> destination, BufferSpan<TPixel> background, BufferSpan<TPixel> source, BufferSpan<float> amount)
{ {
Guard.MustBeGreaterThanOrEqualTo(destination.Length, background.Length, nameof(destination)); Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length));
Guard.MustBeGreaterThanOrEqualTo(source.Length, background.Length, nameof(destination)); Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length));
Guard.MustBeGreaterThanOrEqualTo(amount.Length, background.Length, nameof(destination)); Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length));
for (int i = 0; i < destination.Length; i++) using (Buffer<Vector4> buffer = new Buffer<Vector4>(destination.Length * 3))
{ {
destination[i] = PorterDuffFunctions<TPixel>.ScreenFunction(destination[i], source[i], amount[i]); BufferSpan<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
BufferSpan<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
BufferSpan<Vector4> sourceSpan = buffer.Slice(destination.Length * 2, destination.Length);
PixelOperations<TPixel>.Instance.ToVector4(background, backgroundSpan, destination.Length);
PixelOperations<TPixel>.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<TPixel>.Instance.PackFromVector4(destinationSpan, destination, destination.Length);
} }
} }
} }

22
src/ImageSharp/PixelFormats/PixelBlenders/DefaultSubstractPixelBlender{TPixel}.cs

@ -30,13 +30,25 @@ namespace ImageSharp.PixelFormats.PixelBlenders
/// <inheritdoc /> /// <inheritdoc />
public override void Compose(BufferSpan<TPixel> destination, BufferSpan<TPixel> background, BufferSpan<TPixel> source, BufferSpan<float> amount) public override void Compose(BufferSpan<TPixel> destination, BufferSpan<TPixel> background, BufferSpan<TPixel> source, BufferSpan<float> amount)
{ {
Guard.MustBeGreaterThanOrEqualTo(destination.Length, background.Length, nameof(destination)); Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length));
Guard.MustBeGreaterThanOrEqualTo(source.Length, background.Length, nameof(destination)); Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length));
Guard.MustBeGreaterThanOrEqualTo(amount.Length, background.Length, nameof(destination)); Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length));
for (int i = 0; i < destination.Length; i++) using (Buffer<Vector4> buffer = new Buffer<Vector4>(destination.Length * 3))
{ {
destination[i] = PorterDuffFunctions<TPixel>.SubstractFunction(destination[i], source[i], amount[i]); BufferSpan<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
BufferSpan<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
BufferSpan<Vector4> sourceSpan = buffer.Slice(destination.Length * 2, destination.Length);
PixelOperations<TPixel>.Instance.ToVector4(background, backgroundSpan, destination.Length);
PixelOperations<TPixel>.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<TPixel>.Instance.PackFromVector4(destinationSpan, destination, destination.Length);
} }
} }
} }

514
src/ImageSharp/PixelFormats/PorterDuffFunctions.cs → src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs

@ -1,274 +1,242 @@
// <copyright file="PorterDuffFunctions.cs" company="James Jackson-South"> // <copyright file="PorterDuffFunctions.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors. // Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
// </copyright> // </copyright>
namespace ImageSharp.PixelFormats namespace ImageSharp.PixelFormats.PixelBlenders
{ {
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
/// <summary> /// <summary>
/// Collection of Porter Duff alpha blending functions /// Collection of Porter Duff alpha blending functions
/// </summary> /// </summary>
/// <typeparam name="TPixel">Pixel Format</typeparam> /// <remarks>
/// <remarks> /// These functions are designed to be a general solution for all color cases,
/// 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
/// 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
/// and source, and there's no need to alpha-premultiply neither the backdrop /// nor the source.
/// nor the source. /// Note there are faster functions for when the backdrop color is known
/// Note there are faster functions for when the backdrop color is known /// to be opaque
/// to be opaque /// </remarks>
/// </remarks> internal static class PorterDuffFunctions
internal static class PorterDuffFunctions<TPixel> {
where TPixel : IPixel /// <summary>
{ /// Source over backdrop
/// <summary> /// </summary>
/// Source over backdrop /// <param name="backdrop">Backgrop color</param>
/// </summary> /// <param name="source">Source color</param>
/// <param name="backdrop">Backgrop color</param> /// <param name="opacity">Opacity applied to Source Alpha</param>
/// <param name="source">Source color</param> /// <returns>Output color</returns>
/// <param name="opacity">Opacity applied to Source Alpha</param> [MethodImpl(MethodImplOptions.AggressiveInlining)]
/// <returns>Output color</returns> public static Vector4 NormalBlendFunction(Vector4 backdrop, Vector4 source, float opacity)
[MethodImpl(MethodImplOptions.AggressiveInlining)] {
public static TPixel NormalBlendFunction(TPixel backdrop, TPixel source, float opacity) source.W *= opacity;
{ if (source.W == 0)
Vector4 l = source.ToVector4(); {
l.W *= opacity; return backdrop;
if (l.W == 0) }
{
return backdrop; return Compose(backdrop, source, source);
} }
Vector4 b = backdrop.ToVector4(); /// <summary>
/// Source multiplied by backdrop
return Compose(b, l, l); /// </summary>
} /// <param name="backdrop">Backgrop color</param>
/// <param name="source">Source color</param>
/// <summary> /// <param name="opacity">Opacity applied to Source Alpha</param>
/// Source multiplied by backdrop /// <returns>Output color</returns>
/// </summary> [MethodImpl(MethodImplOptions.AggressiveInlining)]
/// <param name="backdrop">Backgrop color</param> public static Vector4 MultiplyFunction(Vector4 backdrop, Vector4 source, float opacity)
/// <param name="source">Source color</param> {
/// <param name="opacity">Opacity applied to Source Alpha</param> source.W *= opacity;
/// <returns>Output color</returns> if (source.W == 0)
[MethodImpl(MethodImplOptions.AggressiveInlining)] {
public static TPixel MultiplyFunction(TPixel backdrop, TPixel source, float opacity) return backdrop;
{ }
Vector4 l = source.ToVector4();
l.W *= opacity; return Compose(backdrop, source, backdrop * source);
if (l.W == 0) }
{
return backdrop; /// <summary>
} /// Source added to backdrop
/// </summary>
Vector4 b = backdrop.ToVector4(); /// <param name="backdrop">Backgrop color</param>
/// <param name="source">Source color</param>
return Compose(b, l, b * l); /// <param name="opacity">Opacity applied to Source Alpha</param>
} /// <returns>Output color</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
/// <summary> public static Vector4 AddFunction(Vector4 backdrop, Vector4 source, float opacity)
/// Source added to backdrop {
/// </summary> source.W *= opacity;
/// <param name="backdrop">Backgrop color</param> if (source.W == 0)
/// <param name="source">Source color</param> {
/// <param name="opacity">Opacity applied to Source Alpha</param> return backdrop;
/// <returns>Output color</returns> }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TPixel AddFunction(TPixel backdrop, TPixel source, float opacity) return Compose(backdrop, source, Vector4.Min(Vector4.One, backdrop + source));
{ }
Vector4 l = source.ToVector4();
l.W *= opacity; /// <summary>
if (l.W == 0) /// Source substracted from backdrop
{ /// </summary>
return backdrop; /// <param name="backdrop">Backgrop color</param>
} /// <param name="source">Source color</param>
/// <param name="opacity">Opacity applied to Source Alpha</param>
Vector4 b = backdrop.ToVector4(); /// <returns>Output color</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
return Compose(b, l, Vector4.Min(Vector4.One, b + l)); public static Vector4 SubstractFunction(Vector4 backdrop, Vector4 source, float opacity)
} {
source.W *= opacity;
/// <summary> if (source.W == 0)
/// Source substracted from backdrop {
/// </summary> return backdrop;
/// <param name="backdrop">Backgrop color</param> }
/// <param name="source">Source color</param>
/// <param name="opacity">Opacity applied to Source Alpha</param> return Compose(backdrop, source, Vector4.Max(Vector4.Zero, backdrop - source));
/// <returns>Output color</returns> }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TPixel SubstractFunction(TPixel backdrop, TPixel source, float opacity) /// <summary>
{ /// Complement of source multiplied by the complement of backdrop
Vector4 l = source.ToVector4(); /// </summary>
l.W *= opacity; /// <param name="backdrop">Backgrop color</param>
if (l.W == 0) /// <param name="source">Source color</param>
{ /// <param name="opacity">Opacity applied to Source Alpha</param>
return backdrop; /// <returns>Output color</returns>
} [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 ScreenFunction(Vector4 backdrop, Vector4 source, float opacity)
Vector4 b = backdrop.ToVector4(); {
source.W *= opacity;
return Compose(b, l, Vector4.Max(Vector4.Zero, b - l)); if (source.W == 0)
} {
return backdrop;
/// <summary> }
/// Complement of source multiplied by the complement of backdrop
/// </summary> return Compose(backdrop, source, Vector4.One - ((Vector4.One - backdrop) * (Vector4.One - source)));
/// <param name="backdrop">Backgrop color</param> }
/// <param name="source">Source color</param>
/// <param name="opacity">Opacity applied to Source Alpha</param> /// <summary>
/// <returns>Output color</returns> /// Per element, chooses the smallest value of source and backdrop
[MethodImpl(MethodImplOptions.AggressiveInlining)] /// </summary>
public static TPixel ScreenFunction(TPixel backdrop, TPixel source, float opacity) /// <param name="backdrop">Backgrop color</param>
{ /// <param name="source">Source color</param>
Vector4 l = source.ToVector4(); /// <param name="opacity">Opacity applied to Source Alpha</param>
l.W *= opacity; /// <returns>Output color</returns>
if (l.W == 0) [MethodImpl(MethodImplOptions.AggressiveInlining)]
{ public static Vector4 DarkenFunction(Vector4 backdrop, Vector4 source, float opacity)
return backdrop; {
} source.W *= opacity;
if (source.W == 0)
Vector4 b = backdrop.ToVector4(); {
return backdrop;
return Compose(b, l, Vector4.One - ((Vector4.One - b) * (Vector4.One - l))); }
}
return Compose(backdrop, source, Vector4.Min(backdrop, source));
/// <summary> }
/// Per element, chooses the smallest value of source and backdrop
/// </summary> /// <summary>
/// <param name="backdrop">Backgrop color</param> /// Per element, chooses the largest value of source and backdrop
/// <param name="source">Source color</param> /// </summary>
/// <param name="opacity">Opacity applied to Source Alpha</param> /// <param name="backdrop">Backgrop color</param>
/// <returns>Output color</returns> /// <param name="source">Source color</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] /// <param name="opacity">Opacity applied to Source Alpha</param>
public static TPixel DarkenFunction(TPixel backdrop, TPixel source, float opacity) /// <returns>Output color</returns>
{ [MethodImpl(MethodImplOptions.AggressiveInlining)]
Vector4 l = source.ToVector4(); public static Vector4 LightenFunction(Vector4 backdrop, Vector4 source, float opacity)
l.W *= opacity; {
if (l.W == 0) source.W *= opacity;
{ if (source.W == 0)
return backdrop; {
} return backdrop;
}
Vector4 b = backdrop.ToVector4();
return Compose(backdrop, source, Vector4.Max(backdrop, source));
return Compose(b, l, Vector4.Min(b, l)); }
}
/// <summary>
/// <summary> /// Overlays source over backdrop
/// Per element, chooses the largest value of source and backdrop /// </summary>
/// </summary> /// <param name="backdrop">Backgrop color</param>
/// <param name="backdrop">Backgrop color</param> /// <param name="source">Source color</param>
/// <param name="source">Source color</param> /// <param name="opacity">Opacity applied to Source Alpha</param>
/// <param name="opacity">Opacity applied to Source Alpha</param> /// <returns>Output color</returns>
/// <returns>Output color</returns> [MethodImpl(MethodImplOptions.AggressiveInlining)]
[MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector4 OverlayFunction(Vector4 backdrop, Vector4 source, float opacity)
public static TPixel LightenFunction(TPixel backdrop, TPixel source, float opacity) {
{ source.W *= opacity;
Vector4 l = source.ToVector4(); if (source.W == 0)
l.W *= opacity; {
if (l.W == 0) return backdrop;
{ }
return backdrop;
} float cr = OverlayValueFunction(backdrop.X, source.X);
float cg = OverlayValueFunction(backdrop.Y, source.Y);
Vector4 b = backdrop.ToVector4(); float cb = OverlayValueFunction(backdrop.Z, source.Z);
return Compose(b, l, Vector4.Max(b, l)); return Compose(backdrop, source, Vector4.Min(Vector4.One, new Vector4(cr, cg, cb, 0)));
} }
/// <summary> /// <summary>
/// Overlays source over backdrop /// Hard light effect
/// </summary> /// </summary>
/// <param name="backdrop">Backgrop color</param> /// <param name="backdrop">Backgrop color</param>
/// <param name="source">Source color</param> /// <param name="source">Source color</param>
/// <param name="opacity">Opacity applied to Source Alpha</param> /// <param name="opacity">Opacity applied to Source Alpha</param>
/// <returns>Output color</returns> /// <returns>Output color</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TPixel OverlayFunction(TPixel backdrop, TPixel source, float opacity) public static Vector4 HardLightFunction(Vector4 backdrop, Vector4 source, float opacity)
{ {
Vector4 l = source.ToVector4(); source.W *= opacity;
l.W *= opacity; if (source.W == 0)
if (l.W == 0) {
{ return backdrop;
return backdrop; }
}
float cr = OverlayValueFunction(source.X, backdrop.X);
Vector4 b = backdrop.ToVector4(); float cg = OverlayValueFunction(source.Y, backdrop.Y);
float cb = OverlayValueFunction(source.Z, backdrop.Z);
float cr = OverlayValueFunction(b.X, l.X);
float cg = OverlayValueFunction(b.Y, l.Y); return Compose(backdrop, source, Vector4.Min(Vector4.One, new Vector4(cr, cg, cb, 0)));
float cb = OverlayValueFunction(b.Z, l.Z); }
return Compose(b, l, Vector4.Min(Vector4.One, new Vector4(cr, cg, cb, 0))); /// <summary>
} /// Helper function for Overlay andHardLight modes
/// </summary>
/// <summary> /// <param name="backdrop">Backdrop color element</param>
/// Hard light effect /// <param name="source">Source color element</param>
/// </summary> /// <returns>Overlay value</returns>
/// <param name="backdrop">Backgrop color</param> [MethodImpl(MethodImplOptions.AggressiveInlining)]
/// <param name="source">Source color</param> private static float OverlayValueFunction(float backdrop, float source)
/// <param name="opacity">Opacity applied to Source Alpha</param> {
/// <returns>Output color</returns> return backdrop <= 0.5f ? (2 * backdrop * source) : 1 - ((2 * (1 - source)) * (1 - backdrop));
[MethodImpl(MethodImplOptions.AggressiveInlining)] }
public static TPixel HardLightFunction(TPixel backdrop, TPixel source, float opacity)
{ /// <summary>
Vector4 l = source.ToVector4(); /// General composition function for all modes, with a general solution for alpha channel
l.W *= opacity; /// </summary>
if (l.W == 0) /// <param name="backdrop">Original backgrop color</param>
{ /// <param name="source">Original source color</param>
return backdrop; /// <param name="xform">Desired transformed color, without taking Alpha channel in account</param>
} /// <returns>The final color</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
Vector4 b = backdrop.ToVector4(); private static Vector4 Compose(Vector4 backdrop, Vector4 source, Vector4 xform)
{
float cr = OverlayValueFunction(l.X, b.X); DebugGuard.MustBeGreaterThan(source.W, 0, nameof(source.W));
float cg = OverlayValueFunction(l.Y, b.Y);
float cb = OverlayValueFunction(l.Z, b.Z); // calculate weights
float xw = backdrop.W * source.W;
return Compose(b, l, Vector4.Min(Vector4.One, new Vector4(cr, cg, cb, 0))); float bw = backdrop.W - xw;
} float sw = source.W - xw;
/// <summary> // calculate final alpha
/// Helper function for Overlay andHardLight modes float a = xw + bw + sw;
/// </summary>
/// <param name="backdrop">Backdrop color element</param> // calculate final value
/// <param name="source">Source color element</param> xform = ((xform * xw) + (backdrop * bw) + (source * sw)) / a;
/// <returns>Overlay value</returns> xform.W = a;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static float OverlayValueFunction(float backdrop, float source) return xform;
{ }
return backdrop <= 0.5f ? (2 * backdrop * source) : 1 - ((2 * (1 - source)) * (1 - backdrop)); }
}
/// <summary>
/// General composition function for all modes, with a general solution for alpha channel
/// </summary>
/// <param name="backdrop">Original backgrop color</param>
/// <param name="source">Original source color</param>
/// <param name="xform">Desired transformed color, without taking Alpha channel in account</param>
/// <returns>The final color</returns>
[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;
}
}
} }

151
src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions{TPixel}.cs

@ -0,0 +1,151 @@
// <copyright file="PorterDuffFunctions.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.PixelFormats.PixelBlenders
{
using System.Numerics;
using System.Runtime.CompilerServices;
/// <summary>
/// Collection of Porter Duff alpha blending functions
/// </summary>
/// <typeparam name="TPixel">Pixel Format</typeparam>
/// <remarks>
/// 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
/// </remarks>
internal static class PorterDuffFunctions<TPixel>
where TPixel : IPixel
{
/// <summary>
/// Source over backdrop
/// </summary>
/// <param name="backdrop">Backgrop color</param>
/// <param name="source">Source color</param>
/// <param name="opacity">Opacity applied to Source Alpha</param>
/// <returns>Output color</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TPixel NormalBlendFunction(TPixel backdrop, TPixel source, float opacity)
{
return ToPixel(PorterDuffFunctions.NormalBlendFunction(backdrop.ToVector4(), source.ToVector4(), opacity));
}
/// <summary>
/// Source multiplied by backdrop
/// </summary>
/// <param name="backdrop">Backgrop color</param>
/// <param name="source">Source color</param>
/// <param name="opacity">Opacity applied to Source Alpha</param>
/// <returns>Output color</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TPixel MultiplyFunction(TPixel backdrop, TPixel source, float opacity)
{
return ToPixel(PorterDuffFunctions.MultiplyFunction(backdrop.ToVector4(), source.ToVector4(), opacity));
}
/// <summary>
/// Source added to backdrop
/// </summary>
/// <param name="backdrop">Backgrop color</param>
/// <param name="source">Source color</param>
/// <param name="opacity">Opacity applied to Source Alpha</param>
/// <returns>Output color</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TPixel AddFunction(TPixel backdrop, TPixel source, float opacity)
{
return ToPixel(PorterDuffFunctions.AddFunction(backdrop.ToVector4(), source.ToVector4(), opacity));
}
/// <summary>
/// Source substracted from backdrop
/// </summary>
/// <param name="backdrop">Backgrop color</param>
/// <param name="source">Source color</param>
/// <param name="opacity">Opacity applied to Source Alpha</param>
/// <returns>Output color</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TPixel SubstractFunction(TPixel backdrop, TPixel source, float opacity)
{
return ToPixel(PorterDuffFunctions.SubstractFunction(backdrop.ToVector4(), source.ToVector4(), opacity));
}
/// <summary>
/// Complement of source multiplied by the complement of backdrop
/// </summary>
/// <param name="backdrop">Backgrop color</param>
/// <param name="source">Source color</param>
/// <param name="opacity">Opacity applied to Source Alpha</param>
/// <returns>Output color</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TPixel ScreenFunction(TPixel backdrop, TPixel source, float opacity)
{
return ToPixel(PorterDuffFunctions.ScreenFunction(backdrop.ToVector4(), source.ToVector4(), opacity));
}
/// <summary>
/// Per element, chooses the smallest value of source and backdrop
/// </summary>
/// <param name="backdrop">Backgrop color</param>
/// <param name="source">Source color</param>
/// <param name="opacity">Opacity applied to Source Alpha</param>
/// <returns>Output color</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TPixel DarkenFunction(TPixel backdrop, TPixel source, float opacity)
{
return ToPixel(PorterDuffFunctions.DarkenFunction(backdrop.ToVector4(), source.ToVector4(), opacity));
}
/// <summary>
/// Per element, chooses the largest value of source and backdrop
/// </summary>
/// <param name="backdrop">Backgrop color</param>
/// <param name="source">Source color</param>
/// <param name="opacity">Opacity applied to Source Alpha</param>
/// <returns>Output color</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TPixel LightenFunction(TPixel backdrop, TPixel source, float opacity)
{
return ToPixel(PorterDuffFunctions.LightenFunction(backdrop.ToVector4(), source.ToVector4(), opacity));
}
/// <summary>
/// Overlays source over backdrop
/// </summary>
/// <param name="backdrop">Backgrop color</param>
/// <param name="source">Source color</param>
/// <param name="opacity">Opacity applied to Source Alpha</param>
/// <returns>Output color</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TPixel OverlayFunction(TPixel backdrop, TPixel source, float opacity)
{
return ToPixel(PorterDuffFunctions.OverlayFunction(backdrop.ToVector4(), source.ToVector4(), opacity));
}
/// <summary>
/// Hard light effect
/// </summary>
/// <param name="backdrop">Backgrop color</param>
/// <param name="source">Source color</param>
/// <param name="opacity">Opacity applied to Source Alpha</param>
/// <returns>Output color</returns>
[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;
}
}
}

103
tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs

@ -0,0 +1,103 @@
// <copyright file="Crop.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
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<TPixel>(BufferSpan<TPixel> destination, BufferSpan<TPixel> background, BufferSpan<TPixel> source, BufferSpan<float> amount)
where TPixel : struct, IPixel<TPixel>
{
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<Vector4> buffer = new Buffer<Vector4>(destination.Length * 3))
{
BufferSpan<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
BufferSpan<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
BufferSpan<Vector4> sourceSpan = buffer.Slice(destination.Length * 2, destination.Length);
PixelOperations<TPixel>.Instance.ToVector4(background, backgroundSpan, destination.Length);
PixelOperations<TPixel>.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<TPixel>.Instance.PackFromVector4(destinationSpan, destination, destination.Length);
}
}
private void BulkPixelConvert<TPixel>(BufferSpan<TPixel> destination, BufferSpan<TPixel> background, BufferSpan<TPixel> source, BufferSpan<float> amount)
where TPixel : struct, IPixel<TPixel>
{
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<TPixel>.NormalBlendFunction(destination[i], source[i], amount[i]);
}
}
[Benchmark(Description = "ImageSharp BulkVectorConvert")]
public CoreSize BulkVectorConvert()
{
using (CoreImage image = new CoreImage(800, 800))
{
Buffer<float> amounts = new Buffer<float>(image.Width);
for (int x = 0; x < image.Width; x++)
{
amounts[x] = 1;
}
using (PixelAccessor<Rgba32> pixels = image.Lock())
{
for (int y = 0; y < image.Height; y++)
{
BufferSpan<Rgba32> 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<float> amounts = new Buffer<float>(image.Width);
for (int x = 0; x < image.Width; x++)
{
amounts[x] = 1;
}
using (PixelAccessor<Rgba32> pixels = image.Lock())
{
for (int y = 0; y < image.Height; y++)
{
BufferSpan<Rgba32> span = pixels.GetRowSpan(y);
BulkPixelConvert(span, span, span, amounts);
}
}
return new CoreSize(image.Width, image.Height);
}
}
}
}
Loading…
Cancel
Save