From ff97d6b1c9ce0a5d5d2f2ecd43d4161e5ce2ac6b Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Sun, 30 Apr 2017 10:27:00 +0100 Subject: [PATCH] Add Vector4 based functions. --- .../DefaultAddPixelBlender{TPixel}.cs | 22 +- .../DefaultDarkenPixelBlender{TPixel}.cs | 22 +- .../DefaultHardLightPixelBlender{TPixel}.cs | 22 +- .../DefaultLightenPixelBlender{TPixel}.cs | 22 +- .../DefaultMultiplyPixelBlender{TPixel}.cs | 22 +- .../DefaultNormalPixelBlender{TPixel}.cs | 22 +- .../DefaultOverlayPixelBlender{TPixel}.cs | 22 +- .../DefaultScreenPixelBlender{TPixel}.cs | 22 +- .../DefaultSubstractPixelBlender{TPixel}.cs | 22 +- .../PorterDuffFunctions.cs | 514 ++++++++---------- .../PorterDuffFunctions{TPixel}.cs | 151 +++++ .../PixelBlenders/PorterDuffBulkVsPixel.cs | 103 ++++ 12 files changed, 648 insertions(+), 318 deletions(-) rename src/ImageSharp/PixelFormats/{ => PixelBlenders}/PorterDuffFunctions.cs (63%) create mode 100644 src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions{TPixel}.cs create mode 100644 tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs 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); + } + } + } +}