From e111974e3e08533ed59153c5ec9451f2f2ce5cd7 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Sat, 17 Jun 2017 18:31:42 +0100 Subject: [PATCH 1/8] generate pixel blenders --- .editorconfig | 6 +- src/ImageSharp/ImageSharp.csproj | 18 + .../PixelFormats/PixelBlenderMode.cs | 62 +- .../DefaultAddPixelBlender{TPixel}.cs | 57 -- .../DefaultDarkenPixelBlender{TPixel}.cs | 57 -- .../DefaultHardLightPixelBlender{TPixel}.cs | 57 -- .../DefaultLightenPixelBlender{TPixel}.cs | 57 -- .../DefaultMultiplyPixelBlender{TPixel}.cs | 57 -- .../DefaultNormalPixelBlender{TPixel}.cs | 57 -- .../DefaultOverlayPixelBlender{TPixel}.cs | 57 -- .../DefaultPixelBlenders.Generated.cs | 849 ++++++++++++++++++ .../DefaultPixelBlenders.Generated.tt | 118 +++ .../DefaultScreenPixelBlender{TPixel}.cs | 57 -- .../DefaultSubstractPixelBlender{TPixel}.cs | 57 -- .../PorterDuffFunctions.Generated.cs | 314 +++++++ .../PorterDuffFunctions.Generated.tt | 126 +++ .../PixelBlenders/PorterDuffFunctions.cs | 20 +- .../PorterDuffFunctions{TPixel}.cs | 151 ---- .../PixelOperations{TPixel}.PixelBenders.cs | 39 +- .../PixelBlenders/PorterDuffBulkVsPixel.cs | 4 +- .../PixelBlenders/PorterDuffFunctionsTests.cs | 18 +- .../PorterDuffFunctionsTests_TPixel.cs | 54 +- .../PixelOperationsTests.Blender.cs | 66 +- 23 files changed, 1608 insertions(+), 750 deletions(-) delete mode 100644 src/ImageSharp/PixelFormats/PixelBlenders/DefaultAddPixelBlender{TPixel}.cs delete mode 100644 src/ImageSharp/PixelFormats/PixelBlenders/DefaultDarkenPixelBlender{TPixel}.cs delete mode 100644 src/ImageSharp/PixelFormats/PixelBlenders/DefaultHardLightPixelBlender{TPixel}.cs delete mode 100644 src/ImageSharp/PixelFormats/PixelBlenders/DefaultLightenPixelBlender{TPixel}.cs delete mode 100644 src/ImageSharp/PixelFormats/PixelBlenders/DefaultMultiplyPixelBlender{TPixel}.cs delete mode 100644 src/ImageSharp/PixelFormats/PixelBlenders/DefaultNormalPixelBlender{TPixel}.cs delete mode 100644 src/ImageSharp/PixelFormats/PixelBlenders/DefaultOverlayPixelBlender{TPixel}.cs create mode 100644 src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs create mode 100644 src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt delete mode 100644 src/ImageSharp/PixelFormats/PixelBlenders/DefaultScreenPixelBlender{TPixel}.cs delete mode 100644 src/ImageSharp/PixelFormats/PixelBlenders/DefaultSubstractPixelBlender{TPixel}.cs create mode 100644 src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.cs create mode 100644 src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.tt delete mode 100644 src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions{TPixel}.cs diff --git a/.editorconfig b/.editorconfig index fa43757a9..b725c5cce 100644 --- a/.editorconfig +++ b/.editorconfig @@ -13,4 +13,8 @@ dotnet_style_predefined_type_for_locals_parameters_members = true:warning dotnet_style_predefined_type_for_member_access = true:warning dotnet_style_qualification_for_field = true:warning dotnet_style_qualification_for_method = true:warning -dotnet_style_qualification_for_property = true:warning \ No newline at end of file +dotnet_style_qualification_for_property = true:warning + +[*.tt] +indent_style = space +indent_size = 4 \ No newline at end of file diff --git a/src/ImageSharp/ImageSharp.csproj b/src/ImageSharp/ImageSharp.csproj index 17f7bf58f..3b72f12a8 100644 --- a/src/ImageSharp/ImageSharp.csproj +++ b/src/ImageSharp/ImageSharp.csproj @@ -63,6 +63,14 @@ TextTemplatingFileGenerator Rgba32.PixelOperations.Generated.cs + + PorterDuffFunctions.Generated.cs + TextTemplatingFileGenerator + + + DefaultPixelBlenders.Generated.cs + TextTemplatingFileGenerator + @@ -83,5 +91,15 @@ True Rgba32.PixelOperations.Generated.tt + + True + True + DefaultPixelBlenders.Generated.tt + + + True + True + PorterDuffFunctions.Generated.tt + \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/PixelBlenderMode.cs b/src/ImageSharp/PixelFormats/PixelBlenderMode.cs index d8031fe6e..1e48f7181 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenderMode.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenderMode.cs @@ -57,6 +57,66 @@ namespace ImageSharp.PixelFormats /// /// Multiplies or screens the colors, depending on the source value. /// - HardLight + HardLight, + + /// + /// returns the source colors + /// + Src, + + /// + /// returns the source over the destination + /// + Atop, + + /// + /// returns the detination over the source + /// + Over, + + /// + /// the source where the desitnation and source overlap + /// + In, + + /// + /// the destination where the desitnation and source overlap + /// + Out, + + /// + /// the destination where the source does not overlap it + /// + Dest, + + /// + /// the source where they dont overlap othersie dest in overlapping parts + /// + DestAtop, + + /// + /// the destnation over the source + /// + DestOver, + + /// + /// the destination where the desitnation and source overlap + /// + DestIn, + + /// + /// the source where the desitnation and source overlap + /// + DestOut, + + /// + /// the clear. + /// + Clear, + + /// + /// clear where they overlap + /// + Xor } } diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultAddPixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultAddPixelBlender{TPixel}.cs deleted file mode 100644 index 261a98674..000000000 --- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultAddPixelBlender{TPixel}.cs +++ /dev/null @@ -1,57 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.PixelFormats.PixelBlenders -{ - using System; - using System.Numerics; - - using ImageSharp.Memory; - using ImageSharp.PixelFormats; - - /// - /// Applies an "Add" blending to pixels. - /// - /// The type of the pixel - internal class DefaultAddPixelBlender : PixelBlender - where TPixel : struct, IPixel - { - /// - /// Gets the static instance of this blender. - /// - public static DefaultAddPixelBlender Instance { get; } = new DefaultAddPixelBlender(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return PorterDuffFunctions.AddFunction(background, source, amount); - } - - /// - public override void Blend(Span destination, Span background, Span source, Span amount) - { - 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)) - { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span 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 deleted file mode 100644 index bca99e2f0..000000000 --- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultDarkenPixelBlender{TPixel}.cs +++ /dev/null @@ -1,57 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.PixelFormats.PixelBlenders -{ - using System; - using System.Numerics; - - using ImageSharp.Memory; - using ImageSharp.PixelFormats; - - /// - /// Applies an "Darken" blending to pixels. - /// - /// The type of the pixel - internal class DefaultDarkenPixelBlender : PixelBlender - where TPixel : struct, IPixel - { - /// - /// Gets the static instance of this blender. - /// - public static DefaultDarkenPixelBlender Instance { get; } = new DefaultDarkenPixelBlender(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return PorterDuffFunctions.DarkenFunction(background, source, amount); - } - - /// - public override void Blend(Span destination, Span background, Span source, Span amount) - { - 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)) - { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span 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 deleted file mode 100644 index 646423cff..000000000 --- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultHardLightPixelBlender{TPixel}.cs +++ /dev/null @@ -1,57 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.PixelFormats.PixelBlenders -{ - using System; - using System.Numerics; - - using ImageSharp.Memory; - using ImageSharp.PixelFormats; - - /// - /// Applies an "Hard Light" blending to pixels. - /// - /// The type of the pixel - internal class DefaultHardLightPixelBlender : PixelBlender - where TPixel : struct, IPixel - { - /// - /// Gets the static instance of this blender. - /// - public static DefaultHardLightPixelBlender Instance { get; } = new DefaultHardLightPixelBlender(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return PorterDuffFunctions.HardLightFunction(background, source, amount); - } - - /// - public override void Blend(Span destination, Span background, Span source, Span amount) - { - 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)) - { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span 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 deleted file mode 100644 index 55ad81e7a..000000000 --- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultLightenPixelBlender{TPixel}.cs +++ /dev/null @@ -1,57 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.PixelFormats.PixelBlenders -{ - using System; - using System.Numerics; - - using ImageSharp.Memory; - using ImageSharp.PixelFormats; - - /// - /// Applies an "Lighten" blending to pixels. - /// - /// The type of the pixel - internal class DefaultLightenPixelBlender : PixelBlender - where TPixel : struct, IPixel - { - /// - /// Gets the static instance of this blender. - /// - public static DefaultLightenPixelBlender Instance { get; } = new DefaultLightenPixelBlender(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return PorterDuffFunctions.LightenFunction(background, source, amount); - } - - /// - public override void Blend(Span destination, Span background, Span source, Span amount) - { - 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)) - { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span 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 deleted file mode 100644 index e21efaed0..000000000 --- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultMultiplyPixelBlender{TPixel}.cs +++ /dev/null @@ -1,57 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.PixelFormats.PixelBlenders -{ - using System; - using System.Numerics; - - using ImageSharp.Memory; - using ImageSharp.PixelFormats; - - /// - /// Applies an "Multiply" blending to pixels. - /// - /// The type of the pixel - internal class DefaultMultiplyPixelBlender : PixelBlender - where TPixel : struct, IPixel - { - /// - /// Gets the static instance of this blender. - /// - public static DefaultMultiplyPixelBlender Instance { get; } = new DefaultMultiplyPixelBlender(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return PorterDuffFunctions.MultiplyFunction(background, source, amount); - } - - /// - public override void Blend(Span destination, Span background, Span source, Span amount) - { - 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)) - { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span 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 deleted file mode 100644 index 9d63d11e0..000000000 --- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultNormalPixelBlender{TPixel}.cs +++ /dev/null @@ -1,57 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.PixelFormats.PixelBlenders -{ - using System; - using System.Numerics; - - using ImageSharp.Memory; - using ImageSharp.PixelFormats; - - /// - /// Applies a "Normal" otherwise nown as "Alpha Blending" blending to pixels. - /// - /// The type of the pixel - internal class DefaultNormalPixelBlender : PixelBlender - where TPixel : struct, IPixel - { - /// - /// Gets the static instance of this blender. - /// - public static DefaultNormalPixelBlender Instance { get; } = new DefaultNormalPixelBlender(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return PorterDuffFunctions.NormalBlendFunction(background, source, amount); - } - - /// - public override void Blend(Span destination, Span background, Span source, Span amount) - { - 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)) - { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span 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 deleted file mode 100644 index 8172909ec..000000000 --- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultOverlayPixelBlender{TPixel}.cs +++ /dev/null @@ -1,57 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.PixelFormats.PixelBlenders -{ - using System; - using System.Numerics; - - using ImageSharp.Memory; - using ImageSharp.PixelFormats; - - /// - /// Applies an "Overlay" blending to pixels. - /// - /// The type of the pixel - internal class DefaultOverlayPixelBlender : PixelBlender - where TPixel : struct, IPixel - { - /// - /// Gets the static instance of this blender. - /// - public static DefaultOverlayPixelBlender Instance { get; } = new DefaultOverlayPixelBlender(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return PorterDuffFunctions.OverlayFunction(background, source, amount); - } - - /// - public override void Blend(Span destination, Span background, Span source, Span amount) - { - 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)) - { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span 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/DefaultPixelBlenders.Generated.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs new file mode 100644 index 000000000..915d9a924 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs @@ -0,0 +1,849 @@ +// +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.PixelFormats.PixelBlenders +{ + using System; + using System.Numerics; + using ImageSharp.Memory; + + + /// + /// Collection of Porter Duff alpha blending functions applying different composition models. + /// + /// + /// 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 DefaultPixelBlenders + where TPixel : struct, IPixel + { + + internal class Normal : PixelBlender + { + + /// + /// Gets the static instance of this blender. + /// + public static Normal Instance { get; } = new Normal(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return PorterDuffFunctions.Normal(background, source, amount); + } + + /// + public override void Blend(Span destination, Span background, Span source, Span amount) + { + 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)) + { + Span destinationSpan = buffer.Slice(0, destination.Length); + Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); + Span 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.Normal(backgroundSpan[i], sourceSpan[i], amount[i]); + } + + PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); + } + } + } + internal class Multiply : PixelBlender + { + + /// + /// Gets the static instance of this blender. + /// + public static Multiply Instance { get; } = new Multiply(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return PorterDuffFunctions.Multiply(background, source, amount); + } + + /// + public override void Blend(Span destination, Span background, Span source, Span amount) + { + 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)) + { + Span destinationSpan = buffer.Slice(0, destination.Length); + Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); + Span 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.Multiply(backgroundSpan[i], sourceSpan[i], amount[i]); + } + + PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); + } + } + } + internal class Add : PixelBlender + { + + /// + /// Gets the static instance of this blender. + /// + public static Add Instance { get; } = new Add(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return PorterDuffFunctions.Add(background, source, amount); + } + + /// + public override void Blend(Span destination, Span background, Span source, Span amount) + { + 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)) + { + Span destinationSpan = buffer.Slice(0, destination.Length); + Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); + Span 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.Add(backgroundSpan[i], sourceSpan[i], amount[i]); + } + + PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); + } + } + } + internal class Substract : PixelBlender + { + + /// + /// Gets the static instance of this blender. + /// + public static Substract Instance { get; } = new Substract(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return PorterDuffFunctions.Substract(background, source, amount); + } + + /// + public override void Blend(Span destination, Span background, Span source, Span amount) + { + 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)) + { + Span destinationSpan = buffer.Slice(0, destination.Length); + Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); + Span 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.Substract(backgroundSpan[i], sourceSpan[i], amount[i]); + } + + PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); + } + } + } + internal class Screen : PixelBlender + { + + /// + /// Gets the static instance of this blender. + /// + public static Screen Instance { get; } = new Screen(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return PorterDuffFunctions.Screen(background, source, amount); + } + + /// + public override void Blend(Span destination, Span background, Span source, Span amount) + { + 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)) + { + Span destinationSpan = buffer.Slice(0, destination.Length); + Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); + Span 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.Screen(backgroundSpan[i], sourceSpan[i], amount[i]); + } + + PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); + } + } + } + internal class Darken : PixelBlender + { + + /// + /// Gets the static instance of this blender. + /// + public static Darken Instance { get; } = new Darken(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return PorterDuffFunctions.Darken(background, source, amount); + } + + /// + public override void Blend(Span destination, Span background, Span source, Span amount) + { + 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)) + { + Span destinationSpan = buffer.Slice(0, destination.Length); + Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); + Span 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.Darken(backgroundSpan[i], sourceSpan[i], amount[i]); + } + + PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); + } + } + } + internal class Lighten : PixelBlender + { + + /// + /// Gets the static instance of this blender. + /// + public static Lighten Instance { get; } = new Lighten(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return PorterDuffFunctions.Lighten(background, source, amount); + } + + /// + public override void Blend(Span destination, Span background, Span source, Span amount) + { + 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)) + { + Span destinationSpan = buffer.Slice(0, destination.Length); + Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); + Span 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.Lighten(backgroundSpan[i], sourceSpan[i], amount[i]); + } + + PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); + } + } + } + internal class Overlay : PixelBlender + { + + /// + /// Gets the static instance of this blender. + /// + public static Overlay Instance { get; } = new Overlay(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return PorterDuffFunctions.Overlay(background, source, amount); + } + + /// + public override void Blend(Span destination, Span background, Span source, Span amount) + { + 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)) + { + Span destinationSpan = buffer.Slice(0, destination.Length); + Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); + Span 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.Overlay(backgroundSpan[i], sourceSpan[i], amount[i]); + } + + PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); + } + } + } + internal class HardLight : PixelBlender + { + + /// + /// Gets the static instance of this blender. + /// + public static HardLight Instance { get; } = new HardLight(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return PorterDuffFunctions.HardLight(background, source, amount); + } + + /// + public override void Blend(Span destination, Span background, Span source, Span amount) + { + 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)) + { + Span destinationSpan = buffer.Slice(0, destination.Length); + Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); + Span 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.HardLight(backgroundSpan[i], sourceSpan[i], amount[i]); + } + + PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); + } + } + } + internal class Src : PixelBlender + { + + /// + /// Gets the static instance of this blender. + /// + public static Src Instance { get; } = new Src(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return PorterDuffFunctions.Src(background, source, amount); + } + + /// + public override void Blend(Span destination, Span background, Span source, Span amount) + { + 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)) + { + Span destinationSpan = buffer.Slice(0, destination.Length); + Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); + Span 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.Src(backgroundSpan[i], sourceSpan[i], amount[i]); + } + + PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); + } + } + } + internal class Atop : PixelBlender + { + + /// + /// Gets the static instance of this blender. + /// + public static Atop Instance { get; } = new Atop(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return PorterDuffFunctions.Atop(background, source, amount); + } + + /// + public override void Blend(Span destination, Span background, Span source, Span amount) + { + 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)) + { + Span destinationSpan = buffer.Slice(0, destination.Length); + Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); + Span 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.Atop(backgroundSpan[i], sourceSpan[i], amount[i]); + } + + PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); + } + } + } + internal class Over : PixelBlender + { + + /// + /// Gets the static instance of this blender. + /// + public static Over Instance { get; } = new Over(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return PorterDuffFunctions.Over(background, source, amount); + } + + /// + public override void Blend(Span destination, Span background, Span source, Span amount) + { + 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)) + { + Span destinationSpan = buffer.Slice(0, destination.Length); + Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); + Span 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.Over(backgroundSpan[i], sourceSpan[i], amount[i]); + } + + PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); + } + } + } + internal class In : PixelBlender + { + + /// + /// Gets the static instance of this blender. + /// + public static In Instance { get; } = new In(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return PorterDuffFunctions.In(background, source, amount); + } + + /// + public override void Blend(Span destination, Span background, Span source, Span amount) + { + 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)) + { + Span destinationSpan = buffer.Slice(0, destination.Length); + Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); + Span 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.In(backgroundSpan[i], sourceSpan[i], amount[i]); + } + + PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); + } + } + } + internal class Out : PixelBlender + { + + /// + /// Gets the static instance of this blender. + /// + public static Out Instance { get; } = new Out(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return PorterDuffFunctions.Out(background, source, amount); + } + + /// + public override void Blend(Span destination, Span background, Span source, Span amount) + { + 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)) + { + Span destinationSpan = buffer.Slice(0, destination.Length); + Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); + Span 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.Out(backgroundSpan[i], sourceSpan[i], amount[i]); + } + + PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); + } + } + } + internal class Dest : PixelBlender + { + + /// + /// Gets the static instance of this blender. + /// + public static Dest Instance { get; } = new Dest(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return PorterDuffFunctions.Dest(background, source, amount); + } + + /// + public override void Blend(Span destination, Span background, Span source, Span amount) + { + 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)) + { + Span destinationSpan = buffer.Slice(0, destination.Length); + Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); + Span 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.Dest(backgroundSpan[i], sourceSpan[i], amount[i]); + } + + PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); + } + } + } + internal class DestAtop : PixelBlender + { + + /// + /// Gets the static instance of this blender. + /// + public static DestAtop Instance { get; } = new DestAtop(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return PorterDuffFunctions.DestAtop(background, source, amount); + } + + /// + public override void Blend(Span destination, Span background, Span source, Span amount) + { + 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)) + { + Span destinationSpan = buffer.Slice(0, destination.Length); + Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); + Span 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.DestAtop(backgroundSpan[i], sourceSpan[i], amount[i]); + } + + PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); + } + } + } + internal class DestOver : PixelBlender + { + + /// + /// Gets the static instance of this blender. + /// + public static DestOver Instance { get; } = new DestOver(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return PorterDuffFunctions.DestOver(background, source, amount); + } + + /// + public override void Blend(Span destination, Span background, Span source, Span amount) + { + 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)) + { + Span destinationSpan = buffer.Slice(0, destination.Length); + Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); + Span 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.DestOver(backgroundSpan[i], sourceSpan[i], amount[i]); + } + + PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); + } + } + } + internal class DestIn : PixelBlender + { + + /// + /// Gets the static instance of this blender. + /// + public static DestIn Instance { get; } = new DestIn(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return PorterDuffFunctions.DestIn(background, source, amount); + } + + /// + public override void Blend(Span destination, Span background, Span source, Span amount) + { + 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)) + { + Span destinationSpan = buffer.Slice(0, destination.Length); + Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); + Span 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.DestIn(backgroundSpan[i], sourceSpan[i], amount[i]); + } + + PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); + } + } + } + internal class DestOut : PixelBlender + { + + /// + /// Gets the static instance of this blender. + /// + public static DestOut Instance { get; } = new DestOut(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return PorterDuffFunctions.DestOut(background, source, amount); + } + + /// + public override void Blend(Span destination, Span background, Span source, Span amount) + { + 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)) + { + Span destinationSpan = buffer.Slice(0, destination.Length); + Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); + Span 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.DestOut(backgroundSpan[i], sourceSpan[i], amount[i]); + } + + PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); + } + } + } + internal class Clear : PixelBlender + { + + /// + /// Gets the static instance of this blender. + /// + public static Clear Instance { get; } = new Clear(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return PorterDuffFunctions.Clear(background, source, amount); + } + + /// + public override void Blend(Span destination, Span background, Span source, Span amount) + { + 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)) + { + Span destinationSpan = buffer.Slice(0, destination.Length); + Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); + Span 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.Clear(backgroundSpan[i], sourceSpan[i], amount[i]); + } + + PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); + } + } + } + internal class Xor : PixelBlender + { + + /// + /// Gets the static instance of this blender. + /// + public static Xor Instance { get; } = new Xor(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return PorterDuffFunctions.Xor(background, source, amount); + } + + /// + public override void Blend(Span destination, Span background, Span source, Span amount) + { + 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)) + { + Span destinationSpan = buffer.Slice(0, destination.Length); + Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); + Span 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.Xor(backgroundSpan[i], sourceSpan[i], amount[i]); + } + + PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); + } + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt new file mode 100644 index 000000000..230b05499 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt @@ -0,0 +1,118 @@ +<# +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// +#> +<#@ template debug="false" hostspecific="false" language="C#" #> +<#@ assembly name="System.Core" #> +<#@ import namespace="System.Linq" #> +<#@ import namespace="System.Text" #> +<#@ import namespace="System.Collections.Generic" #> +<#@ output extension=".cs" #> +// +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.PixelFormats.PixelBlenders +{ + using System; + using System.Numerics; + using ImageSharp.Memory; + + + /// + /// Collection of Porter Duff alpha blending functions applying different composition models. + /// + /// + /// 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 DefaultPixelBlenders + where TPixel : struct, IPixel + { + +<# + + + + string[] blenders = new []{ + "Normal", + "Multiply", + "Add", + "Substract", + "Screen", + "Darken", + "Lighten", + "Overlay", + "HardLight", + "Src" , + "Atop" , + "Over" , + "In" , + "Out" , + "Dest" , + "DestAtop" , + "DestOver" , + "DestIn" , + "DestOut" , + "Clear" , + "Xor" , + }; + + + + foreach(var blender in blenders) { +#> + internal class <#=blender#> : PixelBlender + { + + /// + /// Gets the static instance of this blender. + /// + public static <#=blender#> Instance { get; } = new <#=blender#>(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return PorterDuffFunctions.<#=blender#>(background, source, amount); + } + + /// + public override void Blend(Span destination, Span background, Span source, Span amount) + { + 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)) + { + Span destinationSpan = buffer.Slice(0, destination.Length); + Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); + Span 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.<#=blender#>(backgroundSpan[i], sourceSpan[i], amount[i]); + } + + PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); + } + } + } +<# + + } + +#> + } +} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultScreenPixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultScreenPixelBlender{TPixel}.cs deleted file mode 100644 index 8405c3946..000000000 --- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultScreenPixelBlender{TPixel}.cs +++ /dev/null @@ -1,57 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.PixelFormats.PixelBlenders -{ - using System; - using System.Numerics; - - using ImageSharp.Memory; - using ImageSharp.PixelFormats; - - /// - /// Applies an "Screen" blending to pixels. - /// - /// The type of the pixel - internal class DefaultScreenPixelBlender : PixelBlender - where TPixel : struct, IPixel - { - /// - /// Gets the static instance of this blender. - /// - public static DefaultScreenPixelBlender Instance { get; } = new DefaultScreenPixelBlender(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return PorterDuffFunctions.ScreenFunction(background, source, amount); - } - - /// - public override void Blend(Span destination, Span background, Span source, Span amount) - { - 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)) - { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span 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 deleted file mode 100644 index ab44cb760..000000000 --- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultSubstractPixelBlender{TPixel}.cs +++ /dev/null @@ -1,57 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.PixelFormats.PixelBlenders -{ - using System; - using System.Numerics; - - using ImageSharp.Memory; - using ImageSharp.PixelFormats; - - /// - /// Applies an "Subtract" blending to pixels. - /// - /// The type of the pixel - internal class DefaultSubstractPixelBlender : PixelBlender - where TPixel : struct, IPixel - { - /// - /// Gets the static instance of this blender. - /// - public static DefaultSubstractPixelBlender Instance { get; } = new DefaultSubstractPixelBlender(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return PorterDuffFunctions.SubstractFunction(background, source, amount); - } - - /// - public override void Blend(Span destination, Span background, Span source, Span amount) - { - 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)) - { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span 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/PixelBlenders/PorterDuffFunctions.Generated.cs b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.cs new file mode 100644 index 000000000..5bd4c601c --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.cs @@ -0,0 +1,314 @@ +// +// +// 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; + + + internal static partial class PorterDuffFunctions + { + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 Src(Vector4 backdrop, Vector4 source, float amount) + { + source.W *= amount; + if (source.W == 0) + { + return Vector4.Zero; + } + + return Compose(Vector4.Zero, source, source); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 Atop(Vector4 backdrop, Vector4 source, float amount) + { + return backdrop; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 Over(Vector4 backdrop, Vector4 source, float amount) + { + source.W *= amount; + if (source.W == 0) + { + return backdrop; + } + + return Compose(backdrop, source, source); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 In(Vector4 backdrop, Vector4 source, float amount) + { + return Vector4.Zero; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 Out(Vector4 backdrop, Vector4 source, float amount) + { + source.W *= amount; + if (source.W == 0) + { + return Vector4.Zero; + } + + return Compose(Vector4.Zero, source, Vector4.Zero); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 Dest(Vector4 backdrop, Vector4 source, float amount) + { + return backdrop; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 DestAtop(Vector4 backdrop, Vector4 source, float amount) + { + source.W *= amount; + if (source.W == 0) + { + return Vector4.Zero; + } + + return Compose(Vector4.Zero, source, backdrop); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 DestOver(Vector4 backdrop, Vector4 source, float amount) + { + source.W *= amount; + if (source.W == 0) + { + return backdrop; + } + + return Compose(backdrop, source, backdrop); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 DestIn(Vector4 backdrop, Vector4 source, float amount) + { + return Vector4.Zero; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 DestOut(Vector4 backdrop, Vector4 source, float amount) + { + return backdrop; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 Clear(Vector4 backdrop, Vector4 source, float amount) + { + return Vector4.Zero; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 Xor(Vector4 backdrop, Vector4 source, float amount) + { + source.W *= amount; + if (source.W == 0) + { + return backdrop; + } + + return Compose(backdrop, source, Vector4.Zero); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel Normal(TPixel backdrop, TPixel source, float amount) + where TPixel : struct, IPixel + { + TPixel dest = default(TPixel); + dest.PackFromVector4(Normal(backdrop.ToVector4(), source.ToVector4(), amount)); + return dest; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel Multiply(TPixel backdrop, TPixel source, float amount) + where TPixel : struct, IPixel + { + TPixel dest = default(TPixel); + dest.PackFromVector4(Multiply(backdrop.ToVector4(), source.ToVector4(), amount)); + return dest; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel Add(TPixel backdrop, TPixel source, float amount) + where TPixel : struct, IPixel + { + TPixel dest = default(TPixel); + dest.PackFromVector4(Add(backdrop.ToVector4(), source.ToVector4(), amount)); + return dest; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel Substract(TPixel backdrop, TPixel source, float amount) + where TPixel : struct, IPixel + { + TPixel dest = default(TPixel); + dest.PackFromVector4(Substract(backdrop.ToVector4(), source.ToVector4(), amount)); + return dest; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel Screen(TPixel backdrop, TPixel source, float amount) + where TPixel : struct, IPixel + { + TPixel dest = default(TPixel); + dest.PackFromVector4(Screen(backdrop.ToVector4(), source.ToVector4(), amount)); + return dest; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel Darken(TPixel backdrop, TPixel source, float amount) + where TPixel : struct, IPixel + { + TPixel dest = default(TPixel); + dest.PackFromVector4(Darken(backdrop.ToVector4(), source.ToVector4(), amount)); + return dest; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel Lighten(TPixel backdrop, TPixel source, float amount) + where TPixel : struct, IPixel + { + TPixel dest = default(TPixel); + dest.PackFromVector4(Lighten(backdrop.ToVector4(), source.ToVector4(), amount)); + return dest; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel Overlay(TPixel backdrop, TPixel source, float amount) + where TPixel : struct, IPixel + { + TPixel dest = default(TPixel); + dest.PackFromVector4(Overlay(backdrop.ToVector4(), source.ToVector4(), amount)); + return dest; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel HardLight(TPixel backdrop, TPixel source, float amount) + where TPixel : struct, IPixel + { + TPixel dest = default(TPixel); + dest.PackFromVector4(HardLight(backdrop.ToVector4(), source.ToVector4(), amount)); + return dest; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel Src(TPixel backdrop, TPixel source, float amount) + where TPixel : struct, IPixel + { + TPixel dest = default(TPixel); + dest.PackFromVector4(Src(backdrop.ToVector4(), source.ToVector4(), amount)); + return dest; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel Atop(TPixel backdrop, TPixel source, float amount) + where TPixel : struct, IPixel + { + TPixel dest = default(TPixel); + dest.PackFromVector4(Atop(backdrop.ToVector4(), source.ToVector4(), amount)); + return dest; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel Over(TPixel backdrop, TPixel source, float amount) + where TPixel : struct, IPixel + { + TPixel dest = default(TPixel); + dest.PackFromVector4(Over(backdrop.ToVector4(), source.ToVector4(), amount)); + return dest; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel In(TPixel backdrop, TPixel source, float amount) + where TPixel : struct, IPixel + { + TPixel dest = default(TPixel); + dest.PackFromVector4(In(backdrop.ToVector4(), source.ToVector4(), amount)); + return dest; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel Out(TPixel backdrop, TPixel source, float amount) + where TPixel : struct, IPixel + { + TPixel dest = default(TPixel); + dest.PackFromVector4(Out(backdrop.ToVector4(), source.ToVector4(), amount)); + return dest; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel Dest(TPixel backdrop, TPixel source, float amount) + where TPixel : struct, IPixel + { + TPixel dest = default(TPixel); + dest.PackFromVector4(Dest(backdrop.ToVector4(), source.ToVector4(), amount)); + return dest; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel DestAtop(TPixel backdrop, TPixel source, float amount) + where TPixel : struct, IPixel + { + TPixel dest = default(TPixel); + dest.PackFromVector4(DestAtop(backdrop.ToVector4(), source.ToVector4(), amount)); + return dest; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel DestOver(TPixel backdrop, TPixel source, float amount) + where TPixel : struct, IPixel + { + TPixel dest = default(TPixel); + dest.PackFromVector4(DestOver(backdrop.ToVector4(), source.ToVector4(), amount)); + return dest; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel DestIn(TPixel backdrop, TPixel source, float amount) + where TPixel : struct, IPixel + { + TPixel dest = default(TPixel); + dest.PackFromVector4(DestIn(backdrop.ToVector4(), source.ToVector4(), amount)); + return dest; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel DestOut(TPixel backdrop, TPixel source, float amount) + where TPixel : struct, IPixel + { + TPixel dest = default(TPixel); + dest.PackFromVector4(DestOut(backdrop.ToVector4(), source.ToVector4(), amount)); + return dest; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel Clear(TPixel backdrop, TPixel source, float amount) + where TPixel : struct, IPixel + { + TPixel dest = default(TPixel); + dest.PackFromVector4(Clear(backdrop.ToVector4(), source.ToVector4(), amount)); + return dest; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel Xor(TPixel backdrop, TPixel source, float amount) + where TPixel : struct, IPixel + { + TPixel dest = default(TPixel); + dest.PackFromVector4(Xor(backdrop.ToVector4(), source.ToVector4(), amount)); + return dest; + } + + } +} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.tt b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.tt new file mode 100644 index 000000000..15b2c46a3 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.tt @@ -0,0 +1,126 @@ +<# +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// +#> +<#@ template debug="false" hostspecific="false" language="C#" #> +<#@ assembly name="System.Core" #> +<#@ import namespace="System.Linq" #> +<#@ import namespace="System.Text" #> +<#@ import namespace="System.Collections.Generic" #> +<#@ output extension=".cs" #> +// +// +// 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; + + + internal static partial class PorterDuffFunctions + { + +<# + + void GeneratePixelBlender (string blender) + { +#> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel <#=blender#>(TPixel backdrop, TPixel source, float amount) + where TPixel : struct, IPixel + { + TPixel dest = default(TPixel); + dest.PackFromVector4(<#=blender#>(backdrop.ToVector4(), source.ToVector4(), amount)); + return dest; + } + +<# + } + + void GenerateVectorCompositor(string name, string sourceVar, string destVar, string blendVar) + { + if(sourceVar == "0") sourceVar= "Vector4.Zero"; + if(destVar == "0") destVar= "Vector4.Zero"; + if(blendVar == "0") blendVar= "Vector4.Zero"; + + if(sourceVar == "s") sourceVar= "source"; + if(destVar == "s") destVar= "source"; + if(blendVar == "s") blendVar= "source"; + + if(sourceVar == "d") sourceVar= "backdrop"; + if(destVar == "d") destVar= "backdrop"; + if(blendVar == "d") blendVar= "backdrop"; +#> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 <#=name#>(Vector4 backdrop, Vector4 source, float amount) + { +<# + if(sourceVar == "Vector4.Zero") + { +#> + return <#=destVar#>; +<# + }else{ +#> + <#=sourceVar#>.W *= amount; + if (<#=sourceVar#>.W == 0) + { + return <#=destVar#>; + } + + return Compose(<#=destVar#>, <#=sourceVar#>, <#=blendVar#>); +<# + } +#> + } + +<# + } + + + GenerateVectorCompositor("Src", "s", "0", "s"); + GenerateVectorCompositor("Atop", "0", "d", "s"); + GenerateVectorCompositor("Over", "s", "d", "s"); + GenerateVectorCompositor("In", "0", "0", "s"); + GenerateVectorCompositor("Out", "s", "0", "0"); + GenerateVectorCompositor("Dest", "0", "d", "d"); + GenerateVectorCompositor("DestAtop", "s", "0", "d"); + GenerateVectorCompositor("DestOver", "s", "d", "d"); + GenerateVectorCompositor("DestIn", "0", "0", "d"); + GenerateVectorCompositor("DestOut", "0", "d", "0"); + GenerateVectorCompositor("Clear", "0", "0", "0"); + GenerateVectorCompositor("Xor", "s", "d", "0"); + + + GeneratePixelBlender("Normal"); + GeneratePixelBlender("Multiply"); + GeneratePixelBlender("Add"); + GeneratePixelBlender("Substract"); + GeneratePixelBlender("Screen"); + GeneratePixelBlender("Darken"); + GeneratePixelBlender("Lighten"); + GeneratePixelBlender("Overlay"); + GeneratePixelBlender("HardLight"); + + GeneratePixelBlender("Src"); + GeneratePixelBlender("Atop"); + GeneratePixelBlender("Over"); + GeneratePixelBlender("In"); + GeneratePixelBlender("Out"); + GeneratePixelBlender("Dest"); + GeneratePixelBlender("DestAtop"); + GeneratePixelBlender("DestOver"); + GeneratePixelBlender("DestIn"); + GeneratePixelBlender("DestOut"); + GeneratePixelBlender("Clear"); + GeneratePixelBlender("Xor"); + + +#> + } +} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs index 25eb6a5c8..9e21d3510 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs @@ -29,7 +29,7 @@ namespace ImageSharp.PixelFormats.PixelBlenders /// Opacity applied to Source Alpha /// Output color [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 NormalBlendFunction(Vector4 backdrop, Vector4 source, float opacity) + public static Vector4 Normal(Vector4 backdrop, Vector4 source, float opacity) { source.W *= opacity; if (source.W == 0) @@ -48,7 +48,7 @@ namespace ImageSharp.PixelFormats.PixelBlenders /// Opacity applied to Source Alpha /// Output color [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 MultiplyFunction(Vector4 backdrop, Vector4 source, float opacity) + public static Vector4 Multiply(Vector4 backdrop, Vector4 source, float opacity) { source.W *= opacity; if (source.W == 0) @@ -67,7 +67,7 @@ namespace ImageSharp.PixelFormats.PixelBlenders /// Opacity applied to Source Alpha /// Output color [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 AddFunction(Vector4 backdrop, Vector4 source, float opacity) + public static Vector4 Add(Vector4 backdrop, Vector4 source, float opacity) { source.W *= opacity; if (source.W == 0) @@ -86,7 +86,7 @@ namespace ImageSharp.PixelFormats.PixelBlenders /// Opacity applied to Source Alpha /// Output color [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 SubstractFunction(Vector4 backdrop, Vector4 source, float opacity) + public static Vector4 Substract(Vector4 backdrop, Vector4 source, float opacity) { source.W *= opacity; if (source.W == 0) @@ -105,7 +105,7 @@ namespace ImageSharp.PixelFormats.PixelBlenders /// Opacity applied to Source Alpha /// Output color [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 ScreenFunction(Vector4 backdrop, Vector4 source, float opacity) + public static Vector4 Screen(Vector4 backdrop, Vector4 source, float opacity) { source.W *= opacity; if (source.W == 0) @@ -124,7 +124,7 @@ namespace ImageSharp.PixelFormats.PixelBlenders /// Opacity applied to Source Alpha /// Output color [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 DarkenFunction(Vector4 backdrop, Vector4 source, float opacity) + public static Vector4 Darken(Vector4 backdrop, Vector4 source, float opacity) { source.W *= opacity; if (source.W == 0) @@ -143,7 +143,7 @@ namespace ImageSharp.PixelFormats.PixelBlenders /// Opacity applied to Source Alpha /// Output color [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 LightenFunction(Vector4 backdrop, Vector4 source, float opacity) + public static Vector4 Lighten(Vector4 backdrop, Vector4 source, float opacity) { source.W *= opacity; if (source.W == 0) @@ -162,7 +162,7 @@ namespace ImageSharp.PixelFormats.PixelBlenders /// Opacity applied to Source Alpha /// Output color [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 OverlayFunction(Vector4 backdrop, Vector4 source, float opacity) + public static Vector4 Overlay(Vector4 backdrop, Vector4 source, float opacity) { source.W *= opacity; if (source.W == 0) @@ -185,7 +185,7 @@ namespace ImageSharp.PixelFormats.PixelBlenders /// Opacity applied to Source Alpha /// Output color [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 HardLightFunction(Vector4 backdrop, Vector4 source, float opacity) + public static Vector4 HardLight(Vector4 backdrop, Vector4 source, float opacity) { source.W *= opacity; if (source.W == 0) @@ -222,7 +222,7 @@ namespace ImageSharp.PixelFormats.PixelBlenders [MethodImpl(MethodImplOptions.AggressiveInlining)] private static Vector4 Compose(Vector4 backdrop, Vector4 source, Vector4 xform) { - DebugGuard.MustBeGreaterThan(source.W, 0, nameof(source.W)); + //DebugGuard.MustBeGreaterThan(source.W, 0, nameof(source.W)); // calculate weights float xw = backdrop.W * source.W; diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions{TPixel}.cs deleted file mode 100644 index 4e829212e..000000000 --- a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions{TPixel}.cs +++ /dev/null @@ -1,151 +0,0 @@ -// -// 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/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.PixelBenders.cs b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.PixelBenders.cs index cab357c41..5a3737dc6 100644 --- a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.PixelBenders.cs +++ b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.PixelBenders.cs @@ -22,25 +22,30 @@ namespace ImageSharp.PixelFormats { switch (mode) { - case PixelBlenderMode.Multiply: - return DefaultMultiplyPixelBlender.Instance; - case PixelBlenderMode.Add: - return DefaultAddPixelBlender.Instance; - case PixelBlenderMode.Substract: - return DefaultSubstractPixelBlender.Instance; - case PixelBlenderMode.Screen: - return DefaultScreenPixelBlender.Instance; - case PixelBlenderMode.Darken: - return DefaultDarkenPixelBlender.Instance; - case PixelBlenderMode.Lighten: - return DefaultLightenPixelBlender.Instance; - case PixelBlenderMode.Overlay: - return DefaultOverlayPixelBlender.Instance; - case PixelBlenderMode.HardLight: - return DefaultHardLightPixelBlender.Instance; + case PixelBlenderMode.Multiply: return DefaultPixelBlenders.Multiply.Instance; + case PixelBlenderMode.Add: return DefaultPixelBlenders.Add.Instance; + case PixelBlenderMode.Substract: return DefaultPixelBlenders.Substract.Instance; + case PixelBlenderMode.Screen: return DefaultPixelBlenders.Screen.Instance; + case PixelBlenderMode.Darken: return DefaultPixelBlenders.Darken.Instance; + case PixelBlenderMode.Lighten: return DefaultPixelBlenders.Lighten.Instance; + case PixelBlenderMode.Overlay: return DefaultPixelBlenders.Overlay.Instance; + case PixelBlenderMode.HardLight: return DefaultPixelBlenders.HardLight.Instance; + case PixelBlenderMode.Src: return DefaultPixelBlenders.Src.Instance; + case PixelBlenderMode.Atop: return DefaultPixelBlenders.Atop.Instance; + case PixelBlenderMode.Over: return DefaultPixelBlenders.Over.Instance; + case PixelBlenderMode.In: return DefaultPixelBlenders.In.Instance; + case PixelBlenderMode.Out: return DefaultPixelBlenders.Out.Instance; + case PixelBlenderMode.Dest: return DefaultPixelBlenders.Dest.Instance; + case PixelBlenderMode.DestAtop: return DefaultPixelBlenders.DestAtop.Instance; + case PixelBlenderMode.DestOver: return DefaultPixelBlenders.DestOver.Instance; + case PixelBlenderMode.DestIn: return DefaultPixelBlenders.DestIn.Instance; + case PixelBlenderMode.DestOut: return DefaultPixelBlenders.DestOut.Instance; + case PixelBlenderMode.Clear: return DefaultPixelBlenders.Clear.Instance; + case PixelBlenderMode.Xor: return DefaultPixelBlenders.Xor.Instance; + case PixelBlenderMode.Normal: default: - return DefaultNormalPixelBlender.Instance; + return DefaultPixelBlenders.Normal.Instance; } } } diff --git a/tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs b/tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs index 1da69f1a8..7d7453ae3 100644 --- a/tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs +++ b/tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs @@ -35,7 +35,7 @@ namespace ImageSharp.Benchmarks for (int i = 0; i < destination.Length; i++) { - destinationSpan[i] = PorterDuffFunctions.NormalBlendFunction(backgroundSpan[i], sourceSpan[i], amount[i]); + destinationSpan[i] = PorterDuffFunctions.Normal(backgroundSpan[i], sourceSpan[i], amount[i]); } PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); @@ -50,7 +50,7 @@ namespace ImageSharp.Benchmarks for (int i = 0; i < destination.Length; i++) { - destination[i] = PorterDuffFunctions.NormalBlendFunction(destination[i], source[i], amount[i]); + destination[i] = PorterDuffFunctions.Normal(destination[i], source[i], amount[i]); } } diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests.cs index 45962c589..97d550592 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests.cs @@ -24,7 +24,7 @@ namespace ImageSharp.Tests.PixelFormats.PixelBlenders [MemberData(nameof(NormalBlendFunctionData))] public void NormalBlendFunction(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) { - Vector4 actual = PorterDuffFunctions.NormalBlendFunction(back, source, amount); + Vector4 actual = PorterDuffFunctions.Normal((Vector4)back, source, amount); Assert.Equal(expected, actual); } @@ -43,7 +43,7 @@ namespace ImageSharp.Tests.PixelFormats.PixelBlenders [MemberData(nameof(MultiplyFunctionData))] public void MultiplyFunction(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) { - Vector4 actual = PorterDuffFunctions.MultiplyFunction(back, source, amount); + Vector4 actual = PorterDuffFunctions.Multiply((Vector4)back, source, amount); VectorAssert.Equal(expected, actual, 5); } @@ -62,7 +62,7 @@ namespace ImageSharp.Tests.PixelFormats.PixelBlenders [MemberData(nameof(AddFunctionData))] public void AddFunction(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) { - Vector4 actual = PorterDuffFunctions.MultiplyFunction(back, source, amount); + Vector4 actual = PorterDuffFunctions.Multiply((Vector4)back, source, amount); VectorAssert.Equal(expected, actual, 5); } @@ -81,7 +81,7 @@ namespace ImageSharp.Tests.PixelFormats.PixelBlenders [MemberData(nameof(SubstractFunctionData))] public void SubstractFunction(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) { - Vector4 actual = PorterDuffFunctions.SubstractFunction(back, source, amount); + Vector4 actual = PorterDuffFunctions.Substract((Vector4)back, source, amount); VectorAssert.Equal(expected, actual, 5); } @@ -100,7 +100,7 @@ namespace ImageSharp.Tests.PixelFormats.PixelBlenders [MemberData(nameof(ScreenFunctionData))] public void ScreenFunction(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) { - Vector4 actual = PorterDuffFunctions.ScreenFunction(back, source, amount); + Vector4 actual = PorterDuffFunctions.Screen((Vector4)back, source, amount); VectorAssert.Equal(expected, actual, 5); } @@ -119,7 +119,7 @@ namespace ImageSharp.Tests.PixelFormats.PixelBlenders [MemberData(nameof(DarkenFunctionData))] public void DarkenFunction(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) { - Vector4 actual = PorterDuffFunctions.DarkenFunction(back, source, amount); + Vector4 actual = PorterDuffFunctions.Darken((Vector4)back, source, amount); VectorAssert.Equal(expected, actual, 5); } @@ -138,7 +138,7 @@ namespace ImageSharp.Tests.PixelFormats.PixelBlenders [MemberData(nameof(LightenFunctionData))] public void LightenFunction(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) { - Vector4 actual = PorterDuffFunctions.LightenFunction(back, source, amount); + Vector4 actual = PorterDuffFunctions.Lighten((Vector4)back, source, amount); VectorAssert.Equal(expected, actual, 5); } @@ -157,7 +157,7 @@ namespace ImageSharp.Tests.PixelFormats.PixelBlenders [MemberData(nameof(OverlayFunctionData))] public void OverlayFunction(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) { - Vector4 actual = PorterDuffFunctions.OverlayFunction(back, source, amount); + Vector4 actual = PorterDuffFunctions.Overlay((Vector4)back, source, amount); VectorAssert.Equal(expected, actual, 5); } @@ -176,7 +176,7 @@ namespace ImageSharp.Tests.PixelFormats.PixelBlenders [MemberData(nameof(HardLightFunctionData))] public void HardLightFunction(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) { - Vector4 actual = PorterDuffFunctions.HardLightFunction(back, source, amount); + Vector4 actual = PorterDuffFunctions.HardLight((Vector4)back, source, amount); VectorAssert.Equal(expected, actual, 5); } } diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests_TPixel.cs b/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests_TPixel.cs index 8932f1ffe..b2a663d07 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests_TPixel.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests_TPixel.cs @@ -32,7 +32,7 @@ namespace ImageSharp.Tests.PixelFormats.PixelBlenders public void NormalBlendFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) where TPixel : struct, IPixel { - TPixel actual = PorterDuffFunctions.NormalBlendFunction(back, source, amount); + TPixel actual = PorterDuffFunctions.Normal((TPixel)(TPixel)back, source, amount); VectorAssert.Equal(expected, actual, 2); } @@ -41,7 +41,7 @@ namespace ImageSharp.Tests.PixelFormats.PixelBlenders public void NormalBlendFunction_Blender(TestPixel back, TestPixel source, float amount, TestPixel expected) where TPixel : struct, IPixel { - TPixel actual = new DefaultNormalPixelBlender().Blend(back, source, amount); + TPixel actual = new DefaultPixelBlenders.Normal().Blend(back, source, amount); VectorAssert.Equal(expected, actual, 2); } @@ -51,7 +51,7 @@ namespace ImageSharp.Tests.PixelFormats.PixelBlenders where TPixel : struct, IPixel { Span dest = new Span(new TPixel[1]); - new DefaultNormalPixelBlender().Blend(dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); + new DefaultPixelBlenders.Normal().Blend(dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); VectorAssert.Equal(expected, dest[0], 2); } @@ -71,7 +71,7 @@ namespace ImageSharp.Tests.PixelFormats.PixelBlenders public void MultiplyFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) where TPixel : struct, IPixel { - TPixel actual = PorterDuffFunctions.MultiplyFunction(back, source, amount); + TPixel actual = PorterDuffFunctions.Multiply((TPixel)back, source, amount); VectorAssert.Equal(expected, actual, 2); } @@ -80,7 +80,7 @@ namespace ImageSharp.Tests.PixelFormats.PixelBlenders public void MultiplyFunction_Blender(TestPixel back, TestPixel source, float amount, TestPixel expected) where TPixel : struct, IPixel { - TPixel actual = new DefaultMultiplyPixelBlender().Blend(back, source, amount); + TPixel actual = new DefaultPixelBlenders.Multiply().Blend(back, source, amount); VectorAssert.Equal(expected, actual, 2); } @@ -90,7 +90,7 @@ namespace ImageSharp.Tests.PixelFormats.PixelBlenders where TPixel : struct, IPixel { Span dest = new Span(new TPixel[1]); - new DefaultMultiplyPixelBlender().Blend(dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); + new DefaultPixelBlenders.Multiply().Blend(dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); VectorAssert.Equal(expected, dest[0], 2); } @@ -110,7 +110,7 @@ namespace ImageSharp.Tests.PixelFormats.PixelBlenders public void AddFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) where TPixel : struct, IPixel { - TPixel actual = PorterDuffFunctions.AddFunction(back, source, amount); + TPixel actual = PorterDuffFunctions.Add((TPixel)back, source, amount); VectorAssert.Equal(expected, actual, 2); } @@ -119,7 +119,7 @@ namespace ImageSharp.Tests.PixelFormats.PixelBlenders public void AddFunction_Blender(TestPixel back, TestPixel source, float amount, TestPixel expected) where TPixel : struct, IPixel { - TPixel actual = new DefaultAddPixelBlender().Blend(back, source, amount); + TPixel actual = new DefaultPixelBlenders.Add().Blend(back, source, amount); VectorAssert.Equal(expected, actual, 2); } @@ -129,7 +129,7 @@ namespace ImageSharp.Tests.PixelFormats.PixelBlenders where TPixel : struct, IPixel { Span dest = new Span(new TPixel[1]); - new DefaultAddPixelBlender().Blend(dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); + new DefaultPixelBlenders.Add().Blend(dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); VectorAssert.Equal(expected, dest[0], 2); } @@ -149,7 +149,7 @@ namespace ImageSharp.Tests.PixelFormats.PixelBlenders public void SubstractFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) where TPixel : struct, IPixel { - TPixel actual = PorterDuffFunctions.SubstractFunction(back, source, amount); + TPixel actual = PorterDuffFunctions.Substract((TPixel)back, source, amount); VectorAssert.Equal(expected, actual, 2); } @@ -158,7 +158,7 @@ namespace ImageSharp.Tests.PixelFormats.PixelBlenders public void SubstractFunction_Blender(TestPixel back, TestPixel source, float amount, TestPixel expected) where TPixel : struct, IPixel { - TPixel actual = new DefaultSubstractPixelBlender().Blend(back, source, amount); + TPixel actual = new DefaultPixelBlenders.Substract().Blend(back, source, amount); VectorAssert.Equal(expected, actual, 2); } @@ -168,7 +168,7 @@ namespace ImageSharp.Tests.PixelFormats.PixelBlenders where TPixel : struct, IPixel { Span dest = new Span(new TPixel[1]); - new DefaultSubstractPixelBlender().Blend(dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); + new DefaultPixelBlenders.Substract().Blend(dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); VectorAssert.Equal(expected, dest[0], 2); } @@ -188,7 +188,7 @@ namespace ImageSharp.Tests.PixelFormats.PixelBlenders public void ScreenFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) where TPixel : struct, IPixel { - TPixel actual = PorterDuffFunctions.ScreenFunction(back, source, amount); + TPixel actual = PorterDuffFunctions.Screen((TPixel)back, source, amount); VectorAssert.Equal(expected, actual, 2); } @@ -197,7 +197,7 @@ namespace ImageSharp.Tests.PixelFormats.PixelBlenders public void ScreenFunction_Blender(TestPixel back, TestPixel source, float amount, TestPixel expected) where TPixel : struct, IPixel { - TPixel actual = new DefaultScreenPixelBlender().Blend(back, source, amount); + TPixel actual = new DefaultPixelBlenders.Screen().Blend(back, source, amount); VectorAssert.Equal(expected, actual, 2); } @@ -207,7 +207,7 @@ namespace ImageSharp.Tests.PixelFormats.PixelBlenders where TPixel : struct, IPixel { Span dest = new Span(new TPixel[1]); - new DefaultScreenPixelBlender().Blend(dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); + new DefaultPixelBlenders.Screen().Blend(dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); VectorAssert.Equal(expected, dest[0], 2); } @@ -227,7 +227,7 @@ namespace ImageSharp.Tests.PixelFormats.PixelBlenders public void DarkenFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) where TPixel : struct, IPixel { - TPixel actual = PorterDuffFunctions.DarkenFunction(back, source, amount); + TPixel actual = PorterDuffFunctions.Darken((TPixel)back, source, amount); VectorAssert.Equal(expected, actual, 2); } @@ -236,7 +236,7 @@ namespace ImageSharp.Tests.PixelFormats.PixelBlenders public void DarkenFunction_Blender(TestPixel back, TestPixel source, float amount, TestPixel expected) where TPixel : struct, IPixel { - TPixel actual = new DefaultDarkenPixelBlender().Blend(back, source, amount); + TPixel actual = new DefaultPixelBlenders.Darken().Blend(back, source, amount); VectorAssert.Equal(expected, actual, 2); } @@ -246,7 +246,7 @@ namespace ImageSharp.Tests.PixelFormats.PixelBlenders where TPixel : struct, IPixel { Span dest = new Span(new TPixel[1]); - new DefaultDarkenPixelBlender().Blend(dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); + new DefaultPixelBlenders.Darken().Blend(dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); VectorAssert.Equal(expected, dest[0], 2); } @@ -266,7 +266,7 @@ namespace ImageSharp.Tests.PixelFormats.PixelBlenders public void LightenFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) where TPixel : struct, IPixel { - TPixel actual = PorterDuffFunctions.LightenFunction(back, source, amount); + TPixel actual = PorterDuffFunctions.Lighten((TPixel)back, source, amount); VectorAssert.Equal(expected, actual, 2); } @@ -275,7 +275,7 @@ namespace ImageSharp.Tests.PixelFormats.PixelBlenders public void LightenFunction_Blender(TestPixel back, TestPixel source, float amount, TestPixel expected) where TPixel : struct, IPixel { - TPixel actual = new DefaultLightenPixelBlender().Blend(back, source, amount); + TPixel actual = new DefaultPixelBlenders.Lighten().Blend(back, source, amount); VectorAssert.Equal(expected, actual, 2); } @@ -285,7 +285,7 @@ namespace ImageSharp.Tests.PixelFormats.PixelBlenders where TPixel : struct, IPixel { Span dest = new Span(new TPixel[1]); - new DefaultLightenPixelBlender().Blend(dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); + new DefaultPixelBlenders.Lighten().Blend(dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); VectorAssert.Equal(expected, dest[0], 2); } @@ -305,7 +305,7 @@ namespace ImageSharp.Tests.PixelFormats.PixelBlenders public void OverlayFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) where TPixel : struct, IPixel { - TPixel actual = PorterDuffFunctions.OverlayFunction(back, source, amount); + TPixel actual = PorterDuffFunctions.Overlay((TPixel)back, source, amount); VectorAssert.Equal(expected, actual, 2); } @@ -314,7 +314,7 @@ namespace ImageSharp.Tests.PixelFormats.PixelBlenders public void OverlayFunction_Blender(TestPixel back, TestPixel source, float amount, TestPixel expected) where TPixel : struct, IPixel { - TPixel actual = new DefaultOverlayPixelBlender().Blend(back, source, amount); + TPixel actual = new DefaultPixelBlenders.Overlay().Blend(back, source, amount); VectorAssert.Equal(expected, actual, 2); } @@ -324,7 +324,7 @@ namespace ImageSharp.Tests.PixelFormats.PixelBlenders where TPixel : struct, IPixel { Span dest = new Span(new TPixel[1]); - new DefaultOverlayPixelBlender().Blend(dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); + new DefaultPixelBlenders.Overlay().Blend(dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); VectorAssert.Equal(expected, dest[0], 2); } @@ -344,7 +344,7 @@ namespace ImageSharp.Tests.PixelFormats.PixelBlenders public void HardLightFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) where TPixel : struct, IPixel { - TPixel actual = PorterDuffFunctions.HardLightFunction(back, source, amount); + TPixel actual = PorterDuffFunctions.HardLight((TPixel)back, source, amount); VectorAssert.Equal(expected, actual, 2); } @@ -353,7 +353,7 @@ namespace ImageSharp.Tests.PixelFormats.PixelBlenders public void HardLightFunction_Blender(TestPixel back, TestPixel source, float amount, TestPixel expected) where TPixel : struct, IPixel { - TPixel actual = new DefaultHardLightPixelBlender().Blend(back, source, amount); + TPixel actual = new DefaultPixelBlenders.HardLight().Blend(back, source, amount); VectorAssert.Equal(expected, actual, 2); } @@ -363,7 +363,7 @@ namespace ImageSharp.Tests.PixelFormats.PixelBlenders where TPixel : struct, IPixel { Span dest = new Span(new TPixel[1]); - new DefaultHardLightPixelBlender().Blend(dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); + new DefaultPixelBlenders.HardLight().Blend(dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); VectorAssert.Equal(expected, dest[0], 2); } } diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperationsTests.Blender.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperationsTests.Blender.cs index ce81499ed..6bcada0ff 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelOperationsTests.Blender.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperationsTests.Blender.cs @@ -15,27 +15,55 @@ namespace ImageSharp.Tests.PixelFormats public partial class PixelOperationsTests { + + public static TheoryData BlenderMappings = new TheoryData() { - { new TestPixel(), typeof(DefaultNormalPixelBlender), PixelBlenderMode.Normal }, - { new TestPixel(), typeof(DefaultScreenPixelBlender), PixelBlenderMode.Screen }, - { new TestPixel(), typeof(DefaultHardLightPixelBlender), PixelBlenderMode.HardLight }, - { new TestPixel(), typeof(DefaultOverlayPixelBlender), PixelBlenderMode.Overlay }, - { new TestPixel(), typeof(DefaultDarkenPixelBlender), PixelBlenderMode.Darken }, - { new TestPixel(), typeof(DefaultLightenPixelBlender), PixelBlenderMode.Lighten }, - { new TestPixel(), typeof(DefaultAddPixelBlender), PixelBlenderMode.Add }, - { new TestPixel(), typeof(DefaultSubstractPixelBlender), PixelBlenderMode.Substract }, - { new TestPixel(), typeof(DefaultMultiplyPixelBlender), PixelBlenderMode.Multiply }, - - { new TestPixel(), typeof(DefaultNormalPixelBlender), PixelBlenderMode.Normal }, - { new TestPixel(), typeof(DefaultScreenPixelBlender), PixelBlenderMode.Screen }, - { new TestPixel(), typeof(DefaultHardLightPixelBlender), PixelBlenderMode.HardLight }, - { new TestPixel(), typeof(DefaultOverlayPixelBlender), PixelBlenderMode.Overlay }, - { new TestPixel(), typeof(DefaultDarkenPixelBlender), PixelBlenderMode.Darken }, - { new TestPixel(), typeof(DefaultLightenPixelBlender), PixelBlenderMode.Lighten }, - { new TestPixel(), typeof(DefaultAddPixelBlender), PixelBlenderMode.Add }, - { new TestPixel(), typeof(DefaultSubstractPixelBlender), PixelBlenderMode.Substract }, - { new TestPixel(), typeof(DefaultMultiplyPixelBlender), PixelBlenderMode.Multiply }, + { new TestPixel(), typeof(DefaultPixelBlenders.Normal), PixelBlenderMode.Normal }, + { new TestPixel(), typeof(DefaultPixelBlenders.Screen), PixelBlenderMode.Screen }, + { new TestPixel(), typeof(DefaultPixelBlenders.HardLight), PixelBlenderMode.HardLight }, + { new TestPixel(), typeof(DefaultPixelBlenders.Overlay), PixelBlenderMode.Overlay }, + { new TestPixel(), typeof(DefaultPixelBlenders.Darken), PixelBlenderMode.Darken }, + { new TestPixel(), typeof(DefaultPixelBlenders.Lighten), PixelBlenderMode.Lighten }, + { new TestPixel(), typeof(DefaultPixelBlenders.Add), PixelBlenderMode.Add }, + { new TestPixel(), typeof(DefaultPixelBlenders.Substract), PixelBlenderMode.Substract }, + { new TestPixel(), typeof(DefaultPixelBlenders.Multiply), PixelBlenderMode.Multiply }, + + { new TestPixel(), typeof(DefaultPixelBlenders.Src), PixelBlenderMode.Src }, + { new TestPixel(), typeof(DefaultPixelBlenders.Atop), PixelBlenderMode.Atop }, + { new TestPixel(), typeof(DefaultPixelBlenders.Over), PixelBlenderMode.Over }, + { new TestPixel(), typeof(DefaultPixelBlenders.In), PixelBlenderMode.In }, + { new TestPixel(), typeof(DefaultPixelBlenders.Out), PixelBlenderMode.Out }, + { new TestPixel(), typeof(DefaultPixelBlenders.Dest), PixelBlenderMode.Dest }, + { new TestPixel(), typeof(DefaultPixelBlenders.DestAtop), PixelBlenderMode.DestAtop }, + { new TestPixel(), typeof(DefaultPixelBlenders.DestOver), PixelBlenderMode.DestOver }, + { new TestPixel(), typeof(DefaultPixelBlenders.DestIn), PixelBlenderMode.DestIn }, + { new TestPixel(), typeof(DefaultPixelBlenders.DestOut), PixelBlenderMode.DestOut }, + { new TestPixel(), typeof(DefaultPixelBlenders.Clear), PixelBlenderMode.Clear }, + { new TestPixel(), typeof(DefaultPixelBlenders.Xor), PixelBlenderMode.Xor }, + + { new TestPixel(), typeof(DefaultPixelBlenders.Normal), PixelBlenderMode.Normal }, + { new TestPixel(), typeof(DefaultPixelBlenders.Screen), PixelBlenderMode.Screen }, + { new TestPixel(), typeof(DefaultPixelBlenders.HardLight), PixelBlenderMode.HardLight }, + { new TestPixel(), typeof(DefaultPixelBlenders.Overlay), PixelBlenderMode.Overlay }, + { new TestPixel(), typeof(DefaultPixelBlenders.Darken), PixelBlenderMode.Darken }, + { new TestPixel(), typeof(DefaultPixelBlenders.Lighten), PixelBlenderMode.Lighten }, + { new TestPixel(), typeof(DefaultPixelBlenders.Add), PixelBlenderMode.Add }, + { new TestPixel(), typeof(DefaultPixelBlenders.Substract), PixelBlenderMode.Substract }, + { new TestPixel(), typeof(DefaultPixelBlenders.Multiply), PixelBlenderMode.Multiply }, + { new TestPixel(), typeof(DefaultPixelBlenders.Src), PixelBlenderMode.Src }, + { new TestPixel(), typeof(DefaultPixelBlenders.Atop), PixelBlenderMode.Atop }, + { new TestPixel(), typeof(DefaultPixelBlenders.Over), PixelBlenderMode.Over }, + { new TestPixel(), typeof(DefaultPixelBlenders.In), PixelBlenderMode.In }, + { new TestPixel(), typeof(DefaultPixelBlenders.Out), PixelBlenderMode.Out }, + { new TestPixel(), typeof(DefaultPixelBlenders.Dest), PixelBlenderMode.Dest }, + { new TestPixel(), typeof(DefaultPixelBlenders.DestAtop), PixelBlenderMode.DestAtop }, + { new TestPixel(), typeof(DefaultPixelBlenders.DestOver), PixelBlenderMode.DestOver }, + { new TestPixel(), typeof(DefaultPixelBlenders.DestIn), PixelBlenderMode.DestIn }, + { new TestPixel(), typeof(DefaultPixelBlenders.DestOut), PixelBlenderMode.DestOut }, + { new TestPixel(), typeof(DefaultPixelBlenders.Clear), PixelBlenderMode.Clear }, + { new TestPixel(), typeof(DefaultPixelBlenders.Xor), PixelBlenderMode.Xor }, + }; [Theory] From 96b394f35ce1b2a8efc2b5a6a38b4e22ecae8753 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Sat, 17 Jun 2017 20:31:33 +0100 Subject: [PATCH 2/8] cleanup and reduce branches --- .../PorterDuffFunctions.Generated.cs | 80 ++++++------------- .../PorterDuffFunctions.Generated.tt | 52 ++++-------- .../PixelBlenders/PorterDuffFunctions.cs | 47 ----------- .../ImageSharp.Tests/Drawing/BlendedShapes.cs | 28 ++++++- 4 files changed, 68 insertions(+), 139 deletions(-) diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.cs b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.cs index 5bd4c601c..a0e4495b6 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.cs @@ -14,110 +14,80 @@ namespace ImageSharp.PixelFormats.PixelBlenders { [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 Src(Vector4 backdrop, Vector4 source, float amount) - { - source.W *= amount; - if (source.W == 0) - { - return Vector4.Zero; - } - + public static Vector4 Src(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; return Compose(Vector4.Zero, source, source); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 Atop(Vector4 backdrop, Vector4 source, float amount) + public static Vector4 Atop(Vector4 backdrop, Vector4 source, float opacity) { - return backdrop; + return Compose(backdrop, Vector4.Zero, source); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 Over(Vector4 backdrop, Vector4 source, float amount) + public static Vector4 Over(Vector4 backdrop, Vector4 source, float opacity) { - source.W *= amount; - if (source.W == 0) - { - return backdrop; - } - + source.W *= opacity; return Compose(backdrop, source, source); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 In(Vector4 backdrop, Vector4 source, float amount) + public static Vector4 In(Vector4 backdrop, Vector4 source, float opacity) { - return Vector4.Zero; + return Compose(Vector4.Zero, Vector4.Zero, source); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 Out(Vector4 backdrop, Vector4 source, float amount) + public static Vector4 Out(Vector4 backdrop, Vector4 source, float opacity) { - source.W *= amount; - if (source.W == 0) - { - return Vector4.Zero; - } - + source.W *= opacity; return Compose(Vector4.Zero, source, Vector4.Zero); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 Dest(Vector4 backdrop, Vector4 source, float amount) + public static Vector4 Dest(Vector4 backdrop, Vector4 source, float opacity) { - return backdrop; + return Compose(backdrop, Vector4.Zero, backdrop); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 DestAtop(Vector4 backdrop, Vector4 source, float amount) + public static Vector4 DestAtop(Vector4 backdrop, Vector4 source, float opacity) { - source.W *= amount; - if (source.W == 0) - { - return Vector4.Zero; - } - + source.W *= opacity; return Compose(Vector4.Zero, source, backdrop); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 DestOver(Vector4 backdrop, Vector4 source, float amount) + public static Vector4 DestOver(Vector4 backdrop, Vector4 source, float opacity) { - source.W *= amount; - if (source.W == 0) - { - return backdrop; - } - + source.W *= opacity; return Compose(backdrop, source, backdrop); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 DestIn(Vector4 backdrop, Vector4 source, float amount) + public static Vector4 DestIn(Vector4 backdrop, Vector4 source, float opacity) { - return Vector4.Zero; + return Compose(Vector4.Zero, Vector4.Zero, backdrop); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 DestOut(Vector4 backdrop, Vector4 source, float amount) + public static Vector4 DestOut(Vector4 backdrop, Vector4 source, float opacity) { - return backdrop; + return Compose(backdrop, Vector4.Zero, Vector4.Zero); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 Clear(Vector4 backdrop, Vector4 source, float amount) + public static Vector4 Clear(Vector4 backdrop, Vector4 source, float opacity) { - return Vector4.Zero; + return Compose(Vector4.Zero, Vector4.Zero, Vector4.Zero); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 Xor(Vector4 backdrop, Vector4 source, float amount) + public static Vector4 Xor(Vector4 backdrop, Vector4 source, float opacity) { - source.W *= amount; - if (source.W == 0) - { - return backdrop; - } - + source.W *= opacity; return Compose(backdrop, source, Vector4.Zero); } diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.tt b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.tt index 15b2c46a3..68bd65041 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.tt +++ b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.tt @@ -44,57 +44,37 @@ namespace ImageSharp.PixelFormats.PixelBlenders void GenerateVectorCompositor(string name, string sourceVar, string destVar, string blendVar) { - if(sourceVar == "0") sourceVar= "Vector4.Zero"; - if(destVar == "0") destVar= "Vector4.Zero"; - if(blendVar == "0") blendVar= "Vector4.Zero"; - - if(sourceVar == "s") sourceVar= "source"; - if(destVar == "s") destVar= "source"; - if(blendVar == "s") blendVar= "source"; - - if(sourceVar == "d") sourceVar= "backdrop"; - if(destVar == "d") destVar= "backdrop"; - if(blendVar == "d") blendVar= "backdrop"; #> [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 <#=name#>(Vector4 backdrop, Vector4 source, float amount) + public static Vector4 <#=name#>(Vector4 backdrop, Vector4 source, float opacity) { <# - if(sourceVar == "Vector4.Zero") + if(sourceVar != "Vector4.Zero") { #> - return <#=destVar#>; -<# - }else{ -#> - <#=sourceVar#>.W *= amount; - if (<#=sourceVar#>.W == 0) - { - return <#=destVar#>; - } - - return Compose(<#=destVar#>, <#=sourceVar#>, <#=blendVar#>); + <#=sourceVar#>.W *= opacity; <# } #> + return Compose(<#=destVar#>, <#=sourceVar#>, <#=blendVar#>); } <# } - GenerateVectorCompositor("Src", "s", "0", "s"); - GenerateVectorCompositor("Atop", "0", "d", "s"); - GenerateVectorCompositor("Over", "s", "d", "s"); - GenerateVectorCompositor("In", "0", "0", "s"); - GenerateVectorCompositor("Out", "s", "0", "0"); - GenerateVectorCompositor("Dest", "0", "d", "d"); - GenerateVectorCompositor("DestAtop", "s", "0", "d"); - GenerateVectorCompositor("DestOver", "s", "d", "d"); - GenerateVectorCompositor("DestIn", "0", "0", "d"); - GenerateVectorCompositor("DestOut", "0", "d", "0"); - GenerateVectorCompositor("Clear", "0", "0", "0"); - GenerateVectorCompositor("Xor", "s", "d", "0"); + GenerateVectorCompositor("Src", "source", "Vector4.Zero", "source"); + GenerateVectorCompositor("Atop", "Vector4.Zero", "backdrop", "source"); + GenerateVectorCompositor("Over", "source", "backdrop", "source"); + GenerateVectorCompositor("In", "Vector4.Zero", "Vector4.Zero", "source"); + GenerateVectorCompositor("Out", "source", "Vector4.Zero", "Vector4.Zero"); + GenerateVectorCompositor("Dest", "Vector4.Zero", "backdrop", "backdrop"); + GenerateVectorCompositor("DestAtop", "source", "Vector4.Zero", "backdrop"); + GenerateVectorCompositor("DestOver", "source", "backdrop", "backdrop"); + GenerateVectorCompositor("DestIn", "Vector4.Zero", "Vector4.Zero", "backdrop"); + GenerateVectorCompositor("DestOut", "Vector4.Zero", "backdrop", "Vector4.Zero"); + GenerateVectorCompositor("Clear", "Vector4.Zero", "Vector4.Zero", "Vector4.Zero"); + GenerateVectorCompositor("Xor", "source", "backdrop", "Vector4.Zero"); GeneratePixelBlender("Normal"); diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs index 9e21d3510..bbb6ca4de 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs @@ -32,11 +32,6 @@ namespace ImageSharp.PixelFormats.PixelBlenders public static Vector4 Normal(Vector4 backdrop, Vector4 source, float opacity) { source.W *= opacity; - if (source.W == 0) - { - return backdrop; - } - return Compose(backdrop, source, source); } @@ -51,11 +46,6 @@ namespace ImageSharp.PixelFormats.PixelBlenders public static Vector4 Multiply(Vector4 backdrop, Vector4 source, float opacity) { source.W *= opacity; - if (source.W == 0) - { - return backdrop; - } - return Compose(backdrop, source, backdrop * source); } @@ -70,11 +60,6 @@ namespace ImageSharp.PixelFormats.PixelBlenders public static Vector4 Add(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)); } @@ -89,11 +74,6 @@ namespace ImageSharp.PixelFormats.PixelBlenders public static Vector4 Substract(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)); } @@ -108,11 +88,6 @@ namespace ImageSharp.PixelFormats.PixelBlenders public static Vector4 Screen(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))); } @@ -127,11 +102,6 @@ namespace ImageSharp.PixelFormats.PixelBlenders public static Vector4 Darken(Vector4 backdrop, Vector4 source, float opacity) { source.W *= opacity; - if (source.W == 0) - { - return backdrop; - } - return Compose(backdrop, source, Vector4.Min(backdrop, source)); } @@ -146,11 +116,6 @@ namespace ImageSharp.PixelFormats.PixelBlenders public static Vector4 Lighten(Vector4 backdrop, Vector4 source, float opacity) { source.W *= opacity; - if (source.W == 0) - { - return backdrop; - } - return Compose(backdrop, source, Vector4.Max(backdrop, source)); } @@ -165,11 +130,6 @@ namespace ImageSharp.PixelFormats.PixelBlenders public static Vector4 Overlay(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); @@ -188,11 +148,6 @@ namespace ImageSharp.PixelFormats.PixelBlenders public static Vector4 HardLight(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); @@ -222,8 +177,6 @@ namespace ImageSharp.PixelFormats.PixelBlenders [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; diff --git a/tests/ImageSharp.Tests/Drawing/BlendedShapes.cs b/tests/ImageSharp.Tests/Drawing/BlendedShapes.cs index 23f0569e7..a3df1803d 100644 --- a/tests/ImageSharp.Tests/Drawing/BlendedShapes.cs +++ b/tests/ImageSharp.Tests/Drawing/BlendedShapes.cs @@ -41,7 +41,7 @@ namespace ImageSharp.Tests.Drawing using (var img = provider.GetImage()) { img.Fill(NamedColors.DarkBlue, new Rectangle(0, 40, 100, 20)); - img.Fill(NamedColors.HotPink, new Rectangle(20, 0, 40, 100), new ImageSharp.GraphicsOptions(true) + img.Fill(NamedColors.HotPink, new Rectangle(20, 0, 30, 100), new ImageSharp.GraphicsOptions(true) { BlenderMode = mode }); @@ -52,5 +52,31 @@ namespace ImageSharp.Tests.Drawing img.DebugSave(provider, new { mode }); } } + + + [Theory] + [WithBlankImages(nameof(modes), 100, 100, PixelTypes.Rgba32)] + public void DrawBlendedValues_transparent50Percent(TestImageProvider provider, PixelBlenderMode mode) + where TPixel : struct, IPixel + { + using (var img = provider.GetImage()) + { + img.Fill(NamedColors.DarkBlue, new Rectangle(0, 40, 100, 20)); + img.Fill(NamedColors.HotPink, new Rectangle(20, 0, 30, 100), new ImageSharp.GraphicsOptions(true) + { + BlenderMode = mode + }); + var c = NamedColors.Red.ToVector4(); + c.W *= 0.5f; + TPixel pixel = default(TPixel); + pixel.PackFromVector4(c); + + img.Fill(pixel, new Rectangle(40, 0, 20, 100), new ImageSharp.GraphicsOptions(true) + { + BlenderMode = mode + }); + img.DebugSave(provider, new { mode }); + } + } } } From ae7dd7c0b698b77dada50cb7890ee0f1fd365855 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Sun, 18 Jun 2017 10:48:31 +0100 Subject: [PATCH 3/8] fix blending to allow non rectangualar regions to antialiase Added a sample to resize and added rounded corners to an image --- .gitignore | 1 + ImageSharp.sln | 19 +++++- .../AvatarWithRoundedCorner.csproj | 12 ++++ samples/AvatarWithRoundedCorner/Program.cs | 63 ++++++++++++++++++ samples/AvatarWithRoundedCorner/fb.jpg | Bin 0 -> 15787 bytes .../PorterDuffFunctions.Generated.cs | 36 +++++----- .../PorterDuffFunctions.Generated.tt | 15 ++--- .../PixelBlenders/PorterDuffFunctions.cs | 34 +++++----- .../ImageSharp.Tests/Drawing/BlendedShapes.cs | 54 +++++++++++---- 9 files changed, 176 insertions(+), 58 deletions(-) create mode 100644 samples/AvatarWithRoundedCorner/AvatarWithRoundedCorner.csproj create mode 100644 samples/AvatarWithRoundedCorner/Program.cs create mode 100644 samples/AvatarWithRoundedCorner/fb.jpg diff --git a/.gitignore b/.gitignore index fb8af2320..8034c8c89 100644 --- a/.gitignore +++ b/.gitignore @@ -217,3 +217,4 @@ artifacts/ #CodeCoverage **/CodeCoverage/* docs/ +/samples/AvatarWithRoundedCorner/output diff --git a/ImageSharp.sln b/ImageSharp.sln index 2e0cbd52e..35998e1aa 100644 --- a/ImageSharp.sln +++ b/ImageSharp.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.26403.7 +VisualStudioVersion = 15.0.26430.6 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SolutionItems", "SolutionItems", "{C317F1B1-D75E-4C6D-83EB-80367343E0D7}" ProjectSection(SolutionItems) = preProject @@ -45,6 +45,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageSharp.Benchmarks", "te EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImageSharp.Sandbox46", "tests\ImageSharp.Sandbox46\ImageSharp.Sandbox46.csproj", "{96188137-5FA6-4924-AB6E-4EFF79C6E0BB}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{7CC6D57E-B916-43B8-B315-A0BB92F260A2}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AvatarWithRoundedCorner", "samples\AvatarWithRoundedCorner\AvatarWithRoundedCorner.csproj", "{844FC582-4E78-4371-847D-EFD4D1103578}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -127,6 +131,18 @@ Global {96188137-5FA6-4924-AB6E-4EFF79C6E0BB}.Release|x64.Build.0 = Release|Any CPU {96188137-5FA6-4924-AB6E-4EFF79C6E0BB}.Release|x86.ActiveCfg = Release|Any CPU {96188137-5FA6-4924-AB6E-4EFF79C6E0BB}.Release|x86.Build.0 = Release|Any CPU + {844FC582-4E78-4371-847D-EFD4D1103578}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {844FC582-4E78-4371-847D-EFD4D1103578}.Debug|Any CPU.Build.0 = Debug|Any CPU + {844FC582-4E78-4371-847D-EFD4D1103578}.Debug|x64.ActiveCfg = Debug|Any CPU + {844FC582-4E78-4371-847D-EFD4D1103578}.Debug|x64.Build.0 = Debug|Any CPU + {844FC582-4E78-4371-847D-EFD4D1103578}.Debug|x86.ActiveCfg = Debug|Any CPU + {844FC582-4E78-4371-847D-EFD4D1103578}.Debug|x86.Build.0 = Debug|Any CPU + {844FC582-4E78-4371-847D-EFD4D1103578}.Release|Any CPU.ActiveCfg = Release|Any CPU + {844FC582-4E78-4371-847D-EFD4D1103578}.Release|Any CPU.Build.0 = Release|Any CPU + {844FC582-4E78-4371-847D-EFD4D1103578}.Release|x64.ActiveCfg = Release|Any CPU + {844FC582-4E78-4371-847D-EFD4D1103578}.Release|x64.Build.0 = Release|Any CPU + {844FC582-4E78-4371-847D-EFD4D1103578}.Release|x86.ActiveCfg = Release|Any CPU + {844FC582-4E78-4371-847D-EFD4D1103578}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -139,5 +155,6 @@ Global {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6} = {56801022-D71A-4FBE-BC5B-CBA08E2284EC} {2BF743D8-2A06-412D-96D7-F448F00C5EA5} = {56801022-D71A-4FBE-BC5B-CBA08E2284EC} {96188137-5FA6-4924-AB6E-4EFF79C6E0BB} = {56801022-D71A-4FBE-BC5B-CBA08E2284EC} + {844FC582-4E78-4371-847D-EFD4D1103578} = {7CC6D57E-B916-43B8-B315-A0BB92F260A2} EndGlobalSection EndGlobal diff --git a/samples/AvatarWithRoundedCorner/AvatarWithRoundedCorner.csproj b/samples/AvatarWithRoundedCorner/AvatarWithRoundedCorner.csproj new file mode 100644 index 000000000..e000aacf1 --- /dev/null +++ b/samples/AvatarWithRoundedCorner/AvatarWithRoundedCorner.csproj @@ -0,0 +1,12 @@ + + + + Exe + netcoreapp1.1 + + + + + + + \ No newline at end of file diff --git a/samples/AvatarWithRoundedCorner/Program.cs b/samples/AvatarWithRoundedCorner/Program.cs new file mode 100644 index 000000000..56b30744c --- /dev/null +++ b/samples/AvatarWithRoundedCorner/Program.cs @@ -0,0 +1,63 @@ + + +namespace AvatarWithRoundedCorner +{ + using System; + using System.Numerics; + using ImageSharp; + using SixLabors.Shapes; + + class Program + { + static void Main(string[] args) + { + + using (var image = Image.Load("fb.jpg")) + { + image.Resize(new ImageSharp.Processing.ResizeOptions + { + Size = new ImageSharp.Size(200, 200), + Mode = ImageSharp.Processing.ResizeMode.Crop + }); + + ApplyRoundedCourners(image, 30); + System.IO.Directory.CreateDirectory("output"); + image.Save("output/fb.png"); + } + } + + public static void ApplyRoundedCourners(Image img, float cornerRadius) + { + var corners = BuildCorners(img.Width, img.Height, cornerRadius); + // now we have our corners time to draw them + img.Fill(Rgba32.Transparent, corners, new GraphicsOptions(true) + { + BlenderMode = ImageSharp.PixelFormats.PixelBlenderMode.Src // enforces that any part of this shape that has color is punched out of the background + }); + } + + public static IPathCollection BuildCorners(int imageWidth, int imageHeight, float cornerRadius) + { + // first create a square + var rect = new SixLabors.Shapes.Rectangle(-0.5f, -0.5f, cornerRadius, cornerRadius); + + // then cut out of the square a circle so we are left with a corner + var cornerToptLeft = rect.Clip(new SixLabors.Shapes.Ellipse(cornerRadius-0.5f, cornerRadius - 0.5f, cornerRadius)); + + // corner is now a corner shape positions top left + //lets make 3 more positioned correctly, we cando that by translating the orgional artound the center of the image + var center = new Vector2(imageWidth / 2, imageHeight / 2); + var angle = Math.PI / 2f; + + float rightPos = imageWidth - cornerToptLeft.Bounds.Width +1; + float bottomPos = imageHeight - cornerToptLeft.Bounds.Height + 1; + + // move it across the widthof the image - the width of the shape + var cornerTopRight = cornerToptLeft.RotateDegree(90).Translate(rightPos, 0); + var cornerBottomLeft = cornerToptLeft.RotateDegree(-90).Translate(0, bottomPos); + var cornerBottomRight = cornerToptLeft.RotateDegree(180).Translate(rightPos, bottomPos); + + return new PathCollection(cornerToptLeft, cornerBottomLeft, cornerTopRight, cornerBottomRight); + } + } +} \ No newline at end of file diff --git a/samples/AvatarWithRoundedCorner/fb.jpg b/samples/AvatarWithRoundedCorner/fb.jpg new file mode 100644 index 0000000000000000000000000000000000000000..305294f47eee72bcc7fe5ec3fce0d33d39d0b293 GIT binary patch literal 15787 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 backdrop, Vector4 source, float opacity) { -<# - if(sourceVar != "Vector4.Zero") - { -#> - <#=sourceVar#>.W *= opacity; -<# - } -#> - return Compose(<#=destVar#>, <#=sourceVar#>, <#=blendVar#>); + <# if(sourceVar != "Vector4.Zero") { + #> + <#=sourceVar#>.W *= opacity; + <# + } #> + return Compose(<#=destVar#>, <#=sourceVar#>, <#=blendVar#>).Blend(backdrop, opacity); } <# diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs index bbb6ca4de..bc7bb2d90 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs @@ -31,8 +31,7 @@ namespace ImageSharp.PixelFormats.PixelBlenders [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector4 Normal(Vector4 backdrop, Vector4 source, float opacity) { - source.W *= opacity; - return Compose(backdrop, source, source); + return Over(backdrop, source, opacity); } /// @@ -45,8 +44,7 @@ namespace ImageSharp.PixelFormats.PixelBlenders [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector4 Multiply(Vector4 backdrop, Vector4 source, float opacity) { - source.W *= opacity; - return Compose(backdrop, source, backdrop * source); + return Compose(backdrop, source, backdrop * source).Blend(backdrop, opacity); } /// @@ -59,8 +57,7 @@ namespace ImageSharp.PixelFormats.PixelBlenders [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector4 Add(Vector4 backdrop, Vector4 source, float opacity) { - source.W *= opacity; - return Compose(backdrop, source, Vector4.Min(Vector4.One, backdrop + source)); + return Compose(backdrop, source, Vector4.Min(Vector4.One, backdrop + source)).Blend(backdrop, opacity); } /// @@ -73,8 +70,7 @@ namespace ImageSharp.PixelFormats.PixelBlenders [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector4 Substract(Vector4 backdrop, Vector4 source, float opacity) { - source.W *= opacity; - return Compose(backdrop, source, Vector4.Max(Vector4.Zero, backdrop - source)); + return Compose(backdrop, source, Vector4.Max(Vector4.Zero, backdrop - source)).Blend(backdrop, opacity); } /// @@ -87,8 +83,7 @@ namespace ImageSharp.PixelFormats.PixelBlenders [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector4 Screen(Vector4 backdrop, Vector4 source, float opacity) { - source.W *= opacity; - return Compose(backdrop, source, Vector4.One - ((Vector4.One - backdrop) * (Vector4.One - source))); + return Compose(backdrop, source, Vector4.One - ((Vector4.One - backdrop) * (Vector4.One - source))).Blend(backdrop, opacity); } /// @@ -101,8 +96,7 @@ namespace ImageSharp.PixelFormats.PixelBlenders [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector4 Darken(Vector4 backdrop, Vector4 source, float opacity) { - source.W *= opacity; - return Compose(backdrop, source, Vector4.Min(backdrop, source)); + return Compose(backdrop, source, Vector4.Min(backdrop, source)).Blend(backdrop, opacity); } /// @@ -115,8 +109,7 @@ namespace ImageSharp.PixelFormats.PixelBlenders [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector4 Lighten(Vector4 backdrop, Vector4 source, float opacity) { - source.W *= opacity; - return Compose(backdrop, source, Vector4.Max(backdrop, source)); + return Compose(backdrop, source, Vector4.Max(backdrop, source)).Blend(backdrop, opacity); } /// @@ -134,7 +127,7 @@ namespace ImageSharp.PixelFormats.PixelBlenders 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))); + return Compose(backdrop, source, Vector4.Min(Vector4.One, new Vector4(cr, cg, cb, 0))).Blend(backdrop, opacity); } /// @@ -147,12 +140,11 @@ namespace ImageSharp.PixelFormats.PixelBlenders [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector4 HardLight(Vector4 backdrop, Vector4 source, float opacity) { - source.W *= opacity; 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))); + return Compose(backdrop, source, Vector4.Min(Vector4.One, new Vector4(cr, cg, cb, 0))).Blend(backdrop, opacity); } /// @@ -186,10 +178,16 @@ namespace ImageSharp.PixelFormats.PixelBlenders float a = xw + bw + sw; // calculate final value - xform = ((xform * xw) + (backdrop * bw) + (source * sw)) / a; + xform = ((xform * xw) + (backdrop * bw) + (source * sw)) / MathF.Max(a, Constants.Epsilon); xform.W = a; return xform; } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector4 Blend(this Vector4 source, Vector4 backdrop, float opacity) + { + return Vector4.Lerp(backdrop, source, opacity); + } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Drawing/BlendedShapes.cs b/tests/ImageSharp.Tests/Drawing/BlendedShapes.cs index a3df1803d..de10fd24f 100644 --- a/tests/ImageSharp.Tests/Drawing/BlendedShapes.cs +++ b/tests/ImageSharp.Tests/Drawing/BlendedShapes.cs @@ -18,14 +18,16 @@ namespace ImageSharp.Tests.Drawing .Select(x=> new object[] { x }); [Theory] - [WithBlankImages(nameof(modes), 100, 100, PixelTypes.Rgba32)] + [WithBlankImages(nameof(modes), 250, 250, PixelTypes.Rgba32)] public void DrawBlendedValues(TestImageProvider provider, PixelBlenderMode mode) where TPixel : struct, IPixel { using (var img = provider.GetImage()) { - img.Fill(NamedColors.DarkBlue, new Rectangle(0, 40, 100, 20)); - img.Fill(NamedColors.HotPink, new Rectangle(40, 0, 20, 100), new ImageSharp.GraphicsOptions(true) + var scaleX = (img.Width / 100); + var scaleY = (img.Height / 100); + img.Fill(NamedColors.DarkBlue, new Rectangle(0 * scaleX, 40 * scaleY, 100 * scaleX, 20 * scaleY)); + img.Fill(NamedColors.HotPink, new Rectangle(20 * scaleX, 0 * scaleY, 30 * scaleX, 100 * scaleY), new ImageSharp.GraphicsOptions(true) { BlenderMode = mode }); @@ -34,18 +36,20 @@ namespace ImageSharp.Tests.Drawing } [Theory] - [WithBlankImages(nameof(modes), 100, 100, PixelTypes.Rgba32)] + [WithBlankImages(nameof(modes), 250, 250, PixelTypes.Rgba32)] public void DrawBlendedValues_transparent(TestImageProvider provider, PixelBlenderMode mode) where TPixel : struct, IPixel { using (var img = provider.GetImage()) { - img.Fill(NamedColors.DarkBlue, new Rectangle(0, 40, 100, 20)); - img.Fill(NamedColors.HotPink, new Rectangle(20, 0, 30, 100), new ImageSharp.GraphicsOptions(true) + var scaleX = (img.Width / 100); + var scaleY = (img.Height / 100); + img.Fill(NamedColors.DarkBlue, new Rectangle(0* scaleX, 40 * scaleY, 100 * scaleX, 20 * scaleY)); + img.Fill(NamedColors.HotPink, new Rectangle(20 * scaleX, 0 * scaleY, 30 * scaleX, 100 * scaleY), new ImageSharp.GraphicsOptions(true) { BlenderMode = mode }); - img.Fill(NamedColors.Transparent, new Rectangle(40, 0, 20, 100), new ImageSharp.GraphicsOptions(true) + img.Fill(NamedColors.Transparent, new SixLabors.Shapes.Ellipse(40 * scaleX, 50 * scaleY, 50 * scaleX, 50 * scaleY), new ImageSharp.GraphicsOptions(true) { BlenderMode = mode }); @@ -53,16 +57,17 @@ namespace ImageSharp.Tests.Drawing } } - [Theory] - [WithBlankImages(nameof(modes), 100, 100, PixelTypes.Rgba32)] + [WithBlankImages(nameof(modes), 250, 250, PixelTypes.Rgba32)] public void DrawBlendedValues_transparent50Percent(TestImageProvider provider, PixelBlenderMode mode) where TPixel : struct, IPixel { using (var img = provider.GetImage()) { - img.Fill(NamedColors.DarkBlue, new Rectangle(0, 40, 100, 20)); - img.Fill(NamedColors.HotPink, new Rectangle(20, 0, 30, 100), new ImageSharp.GraphicsOptions(true) + var scaleX = (img.Width / 100); + var scaleY = (img.Height / 100); + img.Fill(NamedColors.DarkBlue, new Rectangle(0 * scaleX, 40, 100 * scaleX, 20* scaleY)); + img.Fill(NamedColors.HotPink, new Rectangle(20 * scaleX, 0, 30 * scaleX, 100 * scaleY), new ImageSharp.GraphicsOptions(true) { BlenderMode = mode }); @@ -71,7 +76,32 @@ namespace ImageSharp.Tests.Drawing TPixel pixel = default(TPixel); pixel.PackFromVector4(c); - img.Fill(pixel, new Rectangle(40, 0, 20, 100), new ImageSharp.GraphicsOptions(true) + img.Fill(pixel, new SixLabors.Shapes.Ellipse(40 * scaleX, 50 * scaleY, 50 * scaleX, 50 * scaleY), new ImageSharp.GraphicsOptions(true) + { + BlenderMode = mode + }); + img.DebugSave(provider, new { mode }); + } + } + + + + [Theory] + [WithBlankImages(nameof(modes), 250, 250, PixelTypes.Rgba32)] + public void DrawBlendedValues_doldidEllips(TestImageProvider provider, PixelBlenderMode mode) + where TPixel : struct, IPixel + { + using (var img = provider.GetImage()) + { + var scaleX = (img.Width / 100); + var scaleY = (img.Height / 100); + img.Fill(NamedColors.DarkBlue, new Rectangle(0 * scaleX, 40* scaleY, 100 * scaleX, 20 * scaleY)); + //img.Fill(NamedColors.HotPink, new Rectangle(20 * scaleX, 0 * scaleY, 30 * scaleX, 100 * scaleY), new ImageSharp.GraphicsOptions(true) + //{ + // BlenderMode = mode + //}); + + img.Fill(NamedColors.Black, new SixLabors.Shapes.Ellipse(40 * scaleX, 50 * scaleY, 50 * scaleX, 50 * scaleY), new ImageSharp.GraphicsOptions(true) { BlenderMode = mode }); From 8cc447412503503f13d5632fe116006a2560801d Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Sun, 18 Jun 2017 12:40:47 +0100 Subject: [PATCH 4/8] ensure older blenders function as before --- .../PorterDuffFunctions.Generated.cs | 205 ++++++++++++++++-- .../PorterDuffFunctions.Generated.tt | 27 ++- .../PixelBlenders/PorterDuffFunctions.cs | 32 +-- 3 files changed, 221 insertions(+), 43 deletions(-) diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.cs b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.cs index a2cbb0719..4213be0ba 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.cs @@ -12,83 +12,250 @@ namespace ImageSharp.PixelFormats.PixelBlenders internal static partial class PorterDuffFunctions { - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector4 Src(Vector4 backdrop, Vector4 source, float opacity) { - source.W *= opacity; - return Compose(Vector4.Zero, source, source).Blend(backdrop, opacity); + opacity = opacity.Clamp(0, 1); + source.W *= opacity; + Vector4 xform = source; + + // calculate weights + float xw = Vector4.Zero.W * source.W; + float bw = Vector4.Zero.W - xw; + float sw = source.W - xw; + + // calculate final alpha + float a = xw + bw + sw; + + // calculate final value + xform = ((xform * xw) + (Vector4.Zero * bw) + (source * sw)) / MathF.Max(a, Constants.Epsilon); + + return Vector4.Lerp(backdrop, xform, opacity); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector4 Atop(Vector4 backdrop, Vector4 source, float opacity) { - return Compose(backdrop, Vector4.Zero, source).Blend(backdrop, opacity); + opacity = opacity.Clamp(0, 1); + Vector4 xform = source; + + // calculate weights + float xw = backdrop.W * Vector4.Zero.W; + float bw = backdrop.W - xw; + float sw = Vector4.Zero.W - xw; + + // calculate final alpha + float a = xw + bw + sw; + + // calculate final value + xform = ((xform * xw) + (backdrop * bw) + (Vector4.Zero * sw)) / MathF.Max(a, Constants.Epsilon); + + return Vector4.Lerp(backdrop, xform, opacity); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector4 Over(Vector4 backdrop, Vector4 source, float opacity) { - source.W *= opacity; - return Compose(backdrop, source, source).Blend(backdrop, opacity); + opacity = opacity.Clamp(0, 1); + source.W *= opacity; + Vector4 xform = source; + + // 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)) / MathF.Max(a, Constants.Epsilon); + + return Vector4.Lerp(backdrop, xform, opacity); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector4 In(Vector4 backdrop, Vector4 source, float opacity) { - return Compose(Vector4.Zero, Vector4.Zero, source).Blend(backdrop, opacity); + opacity = opacity.Clamp(0, 1); + Vector4 xform = source; + + // calculate weights + float xw = Vector4.Zero.W * Vector4.Zero.W; + float bw = Vector4.Zero.W - xw; + float sw = Vector4.Zero.W - xw; + + // calculate final alpha + float a = xw + bw + sw; + + // calculate final value + xform = ((xform * xw) + (Vector4.Zero * bw) + (Vector4.Zero * sw)) / MathF.Max(a, Constants.Epsilon); + + return Vector4.Lerp(backdrop, xform, opacity); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector4 Out(Vector4 backdrop, Vector4 source, float opacity) { - source.W *= opacity; - return Compose(Vector4.Zero, source, Vector4.Zero).Blend(backdrop, opacity); + opacity = opacity.Clamp(0, 1); + source.W *= opacity; + Vector4 xform = Vector4.Zero; + + // calculate weights + float xw = Vector4.Zero.W * source.W; + float bw = Vector4.Zero.W - xw; + float sw = source.W - xw; + + // calculate final alpha + float a = xw + bw + sw; + + // calculate final value + xform = ((xform * xw) + (Vector4.Zero * bw) + (source * sw)) / MathF.Max(a, Constants.Epsilon); + + return Vector4.Lerp(backdrop, xform, opacity); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector4 Dest(Vector4 backdrop, Vector4 source, float opacity) { - return Compose(backdrop, Vector4.Zero, backdrop).Blend(backdrop, opacity); + opacity = opacity.Clamp(0, 1); + Vector4 xform = backdrop; + + // calculate weights + float xw = backdrop.W * Vector4.Zero.W; + float bw = backdrop.W - xw; + float sw = Vector4.Zero.W - xw; + + // calculate final alpha + float a = xw + bw + sw; + + // calculate final value + xform = ((xform * xw) + (backdrop * bw) + (Vector4.Zero * sw)) / MathF.Max(a, Constants.Epsilon); + + return Vector4.Lerp(backdrop, xform, opacity); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector4 DestAtop(Vector4 backdrop, Vector4 source, float opacity) { - source.W *= opacity; - return Compose(Vector4.Zero, source, backdrop).Blend(backdrop, opacity); + opacity = opacity.Clamp(0, 1); + source.W *= opacity; + Vector4 xform = backdrop; + + // calculate weights + float xw = Vector4.Zero.W * source.W; + float bw = Vector4.Zero.W - xw; + float sw = source.W - xw; + + // calculate final alpha + float a = xw + bw + sw; + + // calculate final value + xform = ((xform * xw) + (Vector4.Zero * bw) + (source * sw)) / MathF.Max(a, Constants.Epsilon); + + return Vector4.Lerp(backdrop, xform, opacity); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector4 DestOver(Vector4 backdrop, Vector4 source, float opacity) { - source.W *= opacity; - return Compose(backdrop, source, backdrop).Blend(backdrop, opacity); + opacity = opacity.Clamp(0, 1); + source.W *= opacity; + Vector4 xform = backdrop; + + // 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)) / MathF.Max(a, Constants.Epsilon); + + return Vector4.Lerp(backdrop, xform, opacity); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector4 DestIn(Vector4 backdrop, Vector4 source, float opacity) { - return Compose(Vector4.Zero, Vector4.Zero, backdrop).Blend(backdrop, opacity); + opacity = opacity.Clamp(0, 1); + Vector4 xform = backdrop; + + // calculate weights + float xw = Vector4.Zero.W * Vector4.Zero.W; + float bw = Vector4.Zero.W - xw; + float sw = Vector4.Zero.W - xw; + + // calculate final alpha + float a = xw + bw + sw; + + // calculate final value + xform = ((xform * xw) + (Vector4.Zero * bw) + (Vector4.Zero * sw)) / MathF.Max(a, Constants.Epsilon); + + return Vector4.Lerp(backdrop, xform, opacity); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector4 DestOut(Vector4 backdrop, Vector4 source, float opacity) { - return Compose(backdrop, Vector4.Zero, Vector4.Zero).Blend(backdrop, opacity); + opacity = opacity.Clamp(0, 1); + Vector4 xform = Vector4.Zero; + + // calculate weights + float xw = backdrop.W * Vector4.Zero.W; + float bw = backdrop.W - xw; + float sw = Vector4.Zero.W - xw; + + // calculate final alpha + float a = xw + bw + sw; + + // calculate final value + xform = ((xform * xw) + (backdrop * bw) + (Vector4.Zero * sw)) / MathF.Max(a, Constants.Epsilon); + + return Vector4.Lerp(backdrop, xform, opacity); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector4 Clear(Vector4 backdrop, Vector4 source, float opacity) { - return Compose(Vector4.Zero, Vector4.Zero, Vector4.Zero).Blend(backdrop, opacity); + opacity = opacity.Clamp(0, 1); + Vector4 xform = Vector4.Zero; + + // calculate weights + float xw = Vector4.Zero.W * Vector4.Zero.W; + float bw = Vector4.Zero.W - xw; + float sw = Vector4.Zero.W - xw; + + // calculate final alpha + float a = xw + bw + sw; + + // calculate final value + xform = ((xform * xw) + (Vector4.Zero * bw) + (Vector4.Zero * sw)) / MathF.Max(a, Constants.Epsilon); + + return Vector4.Lerp(backdrop, xform, opacity); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector4 Xor(Vector4 backdrop, Vector4 source, float opacity) { - source.W *= opacity; - return Compose(backdrop, source, Vector4.Zero).Blend(backdrop, opacity); + opacity = opacity.Clamp(0, 1); + source.W *= opacity; + Vector4 xform = Vector4.Zero; + + // 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)) / MathF.Max(a, Constants.Epsilon); + + return Vector4.Lerp(backdrop, xform, opacity); } [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.tt b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.tt index 1b60481b6..53d22d8f3 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.tt +++ b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.tt @@ -24,7 +24,6 @@ namespace ImageSharp.PixelFormats.PixelBlenders internal static partial class PorterDuffFunctions { - <# void GeneratePixelBlender (string blender) @@ -48,18 +47,28 @@ namespace ImageSharp.PixelFormats.PixelBlenders [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector4 <#=name#>(Vector4 backdrop, Vector4 source, float opacity) { - <# if(sourceVar != "Vector4.Zero") { - #> - <#=sourceVar#>.W *= opacity; - <# - } #> - return Compose(<#=destVar#>, <#=sourceVar#>, <#=blendVar#>).Blend(backdrop, opacity); + opacity = opacity.Clamp(0, 1); +<# if(sourceVar != "Vector4.Zero" ) { #> + source.W *= opacity; +<# } #> + Vector4 xform = <#=blendVar#>; + + // calculate weights + float xw = <#=destVar#>.W * <#=sourceVar#>.W; + float bw = <#=destVar#>.W - xw; + float sw = <#=sourceVar#>.W - xw; + + // calculate final alpha + float a = xw + bw + sw; + + // calculate final value + xform = ((xform * xw) + (<#=destVar#> * bw) + (<#=sourceVar#> * sw)) / MathF.Max(a, Constants.Epsilon); + + return Vector4.Lerp(backdrop, xform, opacity); } <# } - - GenerateVectorCompositor("Src", "source", "Vector4.Zero", "source"); GenerateVectorCompositor("Atop", "Vector4.Zero", "backdrop", "source"); GenerateVectorCompositor("Over", "source", "backdrop", "source"); diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs index bc7bb2d90..b1fca9520 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs @@ -31,7 +31,8 @@ namespace ImageSharp.PixelFormats.PixelBlenders [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector4 Normal(Vector4 backdrop, Vector4 source, float opacity) { - return Over(backdrop, source, opacity); + source.W *= opacity; + return Compose(backdrop, source, source); } /// @@ -44,7 +45,8 @@ namespace ImageSharp.PixelFormats.PixelBlenders [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector4 Multiply(Vector4 backdrop, Vector4 source, float opacity) { - return Compose(backdrop, source, backdrop * source).Blend(backdrop, opacity); + source.W *= opacity; + return Compose(backdrop, source, backdrop * source); } /// @@ -57,7 +59,8 @@ namespace ImageSharp.PixelFormats.PixelBlenders [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector4 Add(Vector4 backdrop, Vector4 source, float opacity) { - return Compose(backdrop, source, Vector4.Min(Vector4.One, backdrop + source)).Blend(backdrop, opacity); + source.W *= opacity; + return Compose(backdrop, source, Vector4.Min(Vector4.One, backdrop + source)); } /// @@ -70,7 +73,8 @@ namespace ImageSharp.PixelFormats.PixelBlenders [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector4 Substract(Vector4 backdrop, Vector4 source, float opacity) { - return Compose(backdrop, source, Vector4.Max(Vector4.Zero, backdrop - source)).Blend(backdrop, opacity); + source.W *= opacity; + return Compose(backdrop, source, Vector4.Max(Vector4.Zero, backdrop - source)); } /// @@ -83,7 +87,8 @@ namespace ImageSharp.PixelFormats.PixelBlenders [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector4 Screen(Vector4 backdrop, Vector4 source, float opacity) { - return Compose(backdrop, source, Vector4.One - ((Vector4.One - backdrop) * (Vector4.One - source))).Blend(backdrop, opacity); + source.W *= opacity; + return Compose(backdrop, source, Vector4.One - ((Vector4.One - backdrop) * (Vector4.One - source))); } /// @@ -96,7 +101,8 @@ namespace ImageSharp.PixelFormats.PixelBlenders [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector4 Darken(Vector4 backdrop, Vector4 source, float opacity) { - return Compose(backdrop, source, Vector4.Min(backdrop, source)).Blend(backdrop, opacity); + source.W *= opacity; + return Compose(backdrop, source, Vector4.Min(backdrop, source)); } /// @@ -109,7 +115,8 @@ namespace ImageSharp.PixelFormats.PixelBlenders [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector4 Lighten(Vector4 backdrop, Vector4 source, float opacity) { - return Compose(backdrop, source, Vector4.Max(backdrop, source)).Blend(backdrop, opacity); + source.W *= opacity; + return Compose(backdrop, source, Vector4.Max(backdrop, source)); } /// @@ -127,7 +134,7 @@ namespace ImageSharp.PixelFormats.PixelBlenders 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))).Blend(backdrop, opacity); + return Compose(backdrop, source, Vector4.Min(Vector4.One, new Vector4(cr, cg, cb, 0))); } /// @@ -140,11 +147,12 @@ namespace ImageSharp.PixelFormats.PixelBlenders [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector4 HardLight(Vector4 backdrop, Vector4 source, float opacity) { + source.W *= opacity; 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))).Blend(backdrop, opacity); + return Compose(backdrop, source, Vector4.Min(Vector4.One, new Vector4(cr, cg, cb, 0))); } /// @@ -183,11 +191,5 @@ namespace ImageSharp.PixelFormats.PixelBlenders return xform; } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector4 Blend(this Vector4 source, Vector4 backdrop, float opacity) - { - return Vector4.Lerp(backdrop, source, opacity); - } } } \ No newline at end of file From e07cc28ce377ab877de2a50504ce7b8809043411 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Sun, 18 Jun 2017 12:48:16 +0100 Subject: [PATCH 5/8] impove sample --- samples/AvatarWithRoundedCorner/Program.cs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/samples/AvatarWithRoundedCorner/Program.cs b/samples/AvatarWithRoundedCorner/Program.cs index 56b30744c..5516a73fb 100644 --- a/samples/AvatarWithRoundedCorner/Program.cs +++ b/samples/AvatarWithRoundedCorner/Program.cs @@ -11,18 +11,25 @@ namespace AvatarWithRoundedCorner { static void Main(string[] args) { - - using (var image = Image.Load("fb.jpg")) + System.IO.Directory.CreateDirectory("output"); + + GenerateAvatar("fb.jpg", "output/fb.png", new ImageSharp.Size(200, 200), 20); + GenerateAvatar("fb.jpg", "output/fb-round.png", new ImageSharp.Size(200, 200), 100); + GenerateAvatar("fb.jpg", "output/fb-rounder.png", new ImageSharp.Size(200, 200), 150); + } + + private static void GenerateAvatar(string source, string destination, ImageSharp.Size size, float cornerRadius) + { + using (var image = Image.Load(source)) { image.Resize(new ImageSharp.Processing.ResizeOptions { - Size = new ImageSharp.Size(200, 200), + Size = size, Mode = ImageSharp.Processing.ResizeMode.Crop }); - ApplyRoundedCourners(image, 30); - System.IO.Directory.CreateDirectory("output"); - image.Save("output/fb.png"); + ApplyRoundedCourners(image, cornerRadius); + image.Save(destination); } } From 7e8ddd65bf59d75669f3dbcbb36deb5ab94d4f66 Mon Sep 17 00:00:00 2001 From: Johannes Bildstein Date: Sun, 18 Jun 2017 14:33:24 +0200 Subject: [PATCH 6/8] read and write duplicate tag data entries only once --- .../MetaData/Profiles/ICC/IccReader.cs | 16 +- .../MetaData/Profiles/ICC/IccWriter.cs | 25 +-- .../MetaData/Profiles/ICC/IccReaderTests.cs | 16 +- .../MetaData/Profiles/ICC/IccWriterTests.cs | 12 +- .../TestDataIcc/IccTestDataProfiles.cs | 171 +++++++++++------- 5 files changed, 153 insertions(+), 87 deletions(-) diff --git a/src/ImageSharp/MetaData/Profiles/ICC/IccReader.cs b/src/ImageSharp/MetaData/Profiles/ICC/IccReader.cs index d7f556a81..b24c96f02 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/IccReader.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/IccReader.cs @@ -5,6 +5,8 @@ namespace ImageSharp { + using System.Collections.Generic; + /// /// Reads and parses ICC data from a byte array /// @@ -85,9 +87,21 @@ namespace ImageSharp { IccTagTableEntry[] tagTable = this.ReadTagTable(reader); IccTagDataEntry[] entries = new IccTagDataEntry[tagTable.Length]; + var store = new Dictionary(); for (int i = 0; i < tagTable.Length; i++) { - IccTagDataEntry entry = reader.ReadTagDataEntry(tagTable[i]); + IccTagDataEntry entry; + uint offset = tagTable[i].Offset; + if (store.ContainsKey(offset)) + { + entry = store[offset]; + } + else + { + entry = reader.ReadTagDataEntry(tagTable[i]); + store.Add(offset, entry); + } + entry.TagSignature = tagTable[i].Signature; entries[i] = entry; } diff --git a/src/ImageSharp/MetaData/Profiles/ICC/IccWriter.cs b/src/ImageSharp/MetaData/Profiles/ICC/IccWriter.cs index b4e5f2868..19c00e8f5 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/IccWriter.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/IccWriter.cs @@ -5,6 +5,7 @@ namespace ImageSharp { + using System; using System.Collections.Generic; using System.Linq; @@ -76,30 +77,18 @@ namespace ImageSharp private IccTagTableEntry[] WriteTagData(IccDataWriter writer, List entries) { - var inData = new List(entries); - var dupData = new List(); - - while (inData.Count > 0) - { - IccTagDataEntry[] items = inData.Where(t => inData[0].Equals(t)).ToArray(); - dupData.Add(items); - foreach (IccTagDataEntry item in items) - { - inData.Remove(item); - } - } - - var table = new List(); + IEnumerable> grouped = entries.GroupBy(t => t); // (Header size) + (entry count) + (nr of entries) * (size of table entry) writer.SetIndex(128 + 4 + (entries.Count * 12)); - foreach (IccTagDataEntry[] entry in dupData) + var table = new List(); + foreach (IGrouping group in grouped) { - writer.WriteTagDataEntry(entry[0], out IccTagTableEntry tentry); - foreach (IccTagDataEntry item in entry) + writer.WriteTagDataEntry(group.Key, out IccTagTableEntry tableEntry); + foreach (IccTagDataEntry item in group) { - table.Add(new IccTagTableEntry(item.TagSignature, tentry.Offset, tentry.DataSize)); + table.Add(new IccTagTableEntry(item.TagSignature, tableEntry.Offset, tableEntry.DataSize)); } } diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/IccReaderTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/IccReaderTests.cs index 0db64c47f..34aa24fa6 100644 --- a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/IccReaderTests.cs +++ b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/IccReaderTests.cs @@ -10,10 +10,10 @@ namespace ImageSharp.Tests.Icc public class IccReaderTests { [Fact] - public void ReadProfile() + public void ReadProfile_NoEntries() { IccReader reader = CreateReader(); - + IccProfile output = reader.Read(IccTestDataProfiles.Header_Random_Array); Assert.Equal(0, output.Entries.Count); @@ -40,6 +40,18 @@ namespace ImageSharp.Tests.Icc Assert.Equal(header.Version, expected.Version); } + [Fact] + public void ReadProfile_DuplicateEntry() + { + IccReader reader = CreateReader(); + + IccProfile output = reader.Read(IccTestDataProfiles.Profile_Random_Array); + + Assert.Equal(2, output.Entries.Count); + Assert.True(ReferenceEquals(output.Entries[0], output.Entries[1])); + } + + private IccReader CreateReader() { return new IccReader(); diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/IccWriterTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/IccWriterTests.cs index 6192e6eae..7e3f8c0c9 100644 --- a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/IccWriterTests.cs +++ b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/IccWriterTests.cs @@ -10,7 +10,7 @@ namespace ImageSharp.Tests.Icc public class IccWriterTests { [Fact] - public void WriteProfile() + public void WriteProfile_NoEntries() { IccWriter writer = CreateWriter(); @@ -23,6 +23,16 @@ namespace ImageSharp.Tests.Icc Assert.Equal(IccTestDataProfiles.Header_Random_Array, output); } + [Fact] + public void WriteProfile_DuplicateEntry() + { + IccWriter writer = CreateWriter(); + + byte[] output = writer.Write(IccTestDataProfiles.Profile_Random_Val); + + Assert.Equal(IccTestDataProfiles.Profile_Random_Array, output); + } + private IccWriter CreateWriter() { return new IccWriter(); diff --git a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataProfiles.cs b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataProfiles.cs index 20fff50a8..32a4a8e57 100644 --- a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataProfiles.cs +++ b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataProfiles.cs @@ -10,83 +10,124 @@ namespace ImageSharp.Tests { internal static class IccTestDataProfiles { - public static readonly IccProfileHeader Header_Random_Write = new IccProfileHeader + public static readonly IccProfileHeader Header_Random_Write = CreateHeaderRandomValue( + 562, // should be overwritten + new IccProfileId(1, 2, 3, 4), // should be overwritten + "ijkl"); // should be overwritten to "acsp" + + public static readonly IccProfileHeader Header_Random_Read = CreateHeaderRandomValue(132, +#if !NETSTANDARD1_1 + new IccProfileId(2931428592, 418415738, 3086756963, 2237536530), +#else + IccProfileId.Zero, +#endif + "acsp"); + + public static readonly byte[] Header_Random_Array = CreateHeaderRandomArray(132, 0, new byte[] { - Class = IccProfileClass.DisplayDevice, - CmmType = "abcd", - CreationDate = new DateTime(1990, 11, 26, 7, 21, 42), - CreatorSignature = "dcba", - DataColorSpace = IccColorSpaceType.Rgb, - DeviceAttributes = IccDeviceAttribute.ChromaBlackWhite | IccDeviceAttribute.OpacityTransparent, - DeviceManufacturer = 123456789u, - DeviceModel = 987654321u, - FileSignature = "ijkl", // should be overwritten to "acsp" - Flags = IccProfileFlag.Embedded | IccProfileFlag.Independent, - Id = new IccProfileId(1, 2, 3, 4), // should be overwritten - PcsIlluminant = new Vector3(4, 5, 6), - PrimaryPlatformSignature = IccPrimaryPlatformType.MicrosoftCorporation, - ProfileConnectionSpace = IccColorSpaceType.CieXyz, - RenderingIntent = IccRenderingIntent.AbsoluteColorimetric, - Size = 562, // should be overwritten - Version = new Version(4, 3, 0), - }; +#if !NETSTANDARD1_1 + 0xAE, 0xBA, 0x0C, 0xF0, 0x18, 0xF0, 0x84, 0x7A, 0xB7, 0xFC, 0x2C, 0x63, 0x85, 0x5E, 0x19, 0x12, +#else + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +#endif + }); - public static readonly IccProfileHeader Header_Random_Read = new IccProfileHeader + public static IccProfileHeader CreateHeaderRandomValue(uint size, IccProfileId id, string fileSignature) { - Class = IccProfileClass.DisplayDevice, - CmmType = "abcd", - CreationDate = new DateTime(1990, 11, 26, 7, 21, 42), - CreatorSignature = "dcba", - DataColorSpace = IccColorSpaceType.Rgb, - DeviceAttributes = IccDeviceAttribute.ChromaBlackWhite | IccDeviceAttribute.OpacityTransparent, - DeviceManufacturer = 123456789u, - DeviceModel = 987654321u, - FileSignature = "acsp", - Flags = IccProfileFlag.Embedded | IccProfileFlag.Independent, + return new IccProfileHeader + { + Class = IccProfileClass.DisplayDevice, + CmmType = "abcd", + CreationDate = new DateTime(1990, 11, 26, 7, 21, 42), + CreatorSignature = "dcba", + DataColorSpace = IccColorSpaceType.Rgb, + DeviceAttributes = IccDeviceAttribute.ChromaBlackWhite | IccDeviceAttribute.OpacityTransparent, + DeviceManufacturer = 123456789u, + DeviceModel = 987654321u, + FileSignature = "acsp", + Flags = IccProfileFlag.Embedded | IccProfileFlag.Independent, #if !NETSTANDARD1_1 - Id = new IccProfileId(2931428592, 418415738, 3086756963, 2237536530), + Id = new IccProfileId(2931428592, 418415738, 3086756963, 2237536530), #else Id = IccProfileId.Zero, #endif - PcsIlluminant = new Vector3(4, 5, 6), - PrimaryPlatformSignature = IccPrimaryPlatformType.MicrosoftCorporation, - ProfileConnectionSpace = IccColorSpaceType.CieXyz, - RenderingIntent = IccRenderingIntent.AbsoluteColorimetric, - Size = 132, - Version = new Version(4, 3, 0), - }; + PcsIlluminant = new Vector3(4, 5, 6), + PrimaryPlatformSignature = IccPrimaryPlatformType.MicrosoftCorporation, + ProfileConnectionSpace = IccColorSpaceType.CieXyz, + RenderingIntent = IccRenderingIntent.AbsoluteColorimetric, + Size = size, + Version = new Version(4, 3, 0), + }; + } - public static readonly byte[] Header_Random_Array = + public static byte[] CreateHeaderRandomArray(uint size, uint nrOfEntries, byte[] profileId) { - 0x00, 0x00, 0x00, 0x84, // Size (132) - 0x61, 0x62, 0x63, 0x64, // CmmType - 0x04, 0x30, 0x00, 0x00, // Version - 0x6D, 0x6E, 0x74, 0x72, // Class - 0x52, 0x47, 0x42, 0x20, // DataColorSpace - 0x58, 0x59, 0x5A, 0x20, // ProfileConnectionSpace - 0x07, 0xC6, 0x00, 0x0B, 0x00, 0x1A, 0x00, 0x07, 0x00, 0x15, 0x00, 0x2A, // CreationDate - 0x61, 0x63, 0x73, 0x70, // FileSignature - 0x4D, 0x53, 0x46, 0x54, // PrimaryPlatformSignature - 0x00, 0x00, 0x00, 0x01, // Flags - 0x07, 0x5B, 0xCD, 0x15, // DeviceManufacturer - 0x3A, 0xDE, 0x68, 0xB1, // DeviceModel - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, // DeviceAttributes - 0x00, 0x00, 0x00, 0x03, // RenderingIntent - 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, // PcsIlluminant - 0x64, 0x63, 0x62, 0x61, // CreatorSignature + return ArrayHelper.Concat( + new byte[] + { + (byte)(size >> 24), (byte)(size >> 16), (byte)(size >> 8), (byte)size, // Size + 0x61, 0x62, 0x63, 0x64, // CmmType + 0x04, 0x30, 0x00, 0x00, // Version + 0x6D, 0x6E, 0x74, 0x72, // Class + 0x52, 0x47, 0x42, 0x20, // DataColorSpace + 0x58, 0x59, 0x5A, 0x20, // ProfileConnectionSpace + 0x07, 0xC6, 0x00, 0x0B, 0x00, 0x1A, 0x00, 0x07, 0x00, 0x15, 0x00, 0x2A, // CreationDate + 0x61, 0x63, 0x73, 0x70, // FileSignature + 0x4D, 0x53, 0x46, 0x54, // PrimaryPlatformSignature + 0x00, 0x00, 0x00, 0x01, // Flags + 0x07, 0x5B, 0xCD, 0x15, // DeviceManufacturer + 0x3A, 0xDE, 0x68, 0xB1, // DeviceModel + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, // DeviceAttributes + 0x00, 0x00, 0x00, 0x03, // RenderingIntent + 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, // PcsIlluminant + 0x64, 0x63, 0x62, 0x61, // CreatorSignature + }, + profileId, + new byte[] + { + // Padding + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + // Nr of tag table entries (0) + (byte)(nrOfEntries >> 24), (byte)(nrOfEntries >> 16), (byte)(nrOfEntries >> 8), (byte)nrOfEntries + }); + } + + public static byte[] Profile_Random_Array = ArrayHelper.Concat(CreateHeaderRandomArray(168, 2, new byte[] + { +#if !NETSTANDARD1_1 + 0xA9, 0x71, 0x8F, 0xC1, 0x1E, 0x2D, 0x64, 0x1B, 0x10, 0xF4, 0x7D, 0x6A, 0x5B, 0xF6, 0xAC, 0xB9 +#else + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +#endif + }), + new byte[] + { + 0x00, 0x00, 0x00, 0x00, // tag signature (Unknown) + 0x00, 0x00, 0x00, 0x9C, // tag offset (156) + 0x00, 0x00, 0x00, 0x0C, // tag size (12) + + 0x00, 0x00, 0x00, 0x00, // tag signature (Unknown) + 0x00, 0x00, 0x00, 0x9C, // tag offset (156) + 0x00, 0x00, 0x00, 0x0C, // tag size (12) + }, + IccTestDataTagDataEntry.TagDataEntryHeader_UnknownArr, + IccTestDataTagDataEntry.Unknown_Arr + ); + public static IccProfile Profile_Random_Val = new IccProfile(CreateHeaderRandomValue(168, #if !NETSTANDARD1_1 - 0xAE, 0xBA, 0x0C, 0xF0, 0x18, 0xF0, 0x84, 0x7A, 0xB7, 0xFC, 0x2C, 0x63, 0x85, 0x5E, 0x19, 0x12, // Id + new IccProfileId(0xA9718FC1, 0x1E2D641B, 0x10F47D6A, 0x5BF6ACB9), #else - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Id + IccProfileId.Zero, #endif - // Padding - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - // Nr of tag table entries (0) - 0x00, 0x00, 0x00, 0x00, - }; + "acsp"), + new IccTagDataEntry[] + { + IccTestDataTagDataEntry.Unknown_Val, + IccTestDataTagDataEntry.Unknown_Val + }); } } From 1d6d492d0c3f914fadc7ea661a6eafcf8ffdb968 Mon Sep 17 00:00:00 2001 From: Dirk Lemstra Date: Tue, 20 Jun 2017 22:51:37 +0200 Subject: [PATCH 7/8] Use var instead. --- src/ImageSharp/Formats/Png/ImageExtensions.cs | 2 +- src/ImageSharp/Image/Image.FromBytes.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ImageSharp/Formats/Png/ImageExtensions.cs b/src/ImageSharp/Formats/Png/ImageExtensions.cs index 44f242b3f..3841b313c 100644 --- a/src/ImageSharp/Formats/Png/ImageExtensions.cs +++ b/src/ImageSharp/Formats/Png/ImageExtensions.cs @@ -46,7 +46,7 @@ namespace ImageSharp public static Image SaveAsPng(this Image source, Stream stream, IPngEncoderOptions options) where TPixel : struct, IPixel { - PngEncoder encoder = new PngEncoder(); + var encoder = new PngEncoder(); encoder.Encode(source, stream, options); return source; diff --git a/src/ImageSharp/Image/Image.FromBytes.cs b/src/ImageSharp/Image/Image.FromBytes.cs index c7309c4b1..628092359 100644 --- a/src/ImageSharp/Image/Image.FromBytes.cs +++ b/src/ImageSharp/Image/Image.FromBytes.cs @@ -126,7 +126,7 @@ namespace ImageSharp public static Image Load(Configuration config, byte[] data, IDecoderOptions options) where TPixel : struct, IPixel { - using (MemoryStream ms = new MemoryStream(data)) + using (var ms = new MemoryStream(data)) { return Load(config, ms, options); } @@ -143,7 +143,7 @@ namespace ImageSharp public static Image Load(byte[] data, IImageDecoder decoder, IDecoderOptions options) where TPixel : struct, IPixel { - using (MemoryStream ms = new MemoryStream(data)) + using (var ms = new MemoryStream(data)) { return Load(ms, decoder, options); } From 82679e4b0a0aa239111de569dae46e72b4af652d Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 24 Jun 2017 00:08:35 +1000 Subject: [PATCH 8/8] Delete broken tests --- .../Tests/PixelFormats/PixelBlenderTests.cs | 50 ------------------- 1 file changed, 50 deletions(-) delete mode 100644 tests/ImageSharp.Sandbox46/Tests/PixelFormats/PixelBlenderTests.cs diff --git a/tests/ImageSharp.Sandbox46/Tests/PixelFormats/PixelBlenderTests.cs b/tests/ImageSharp.Sandbox46/Tests/PixelFormats/PixelBlenderTests.cs deleted file mode 100644 index 7efd3a6fc..000000000 --- a/tests/ImageSharp.Sandbox46/Tests/PixelFormats/PixelBlenderTests.cs +++ /dev/null @@ -1,50 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests.PixelFormats -{ - using System; - using System.Collections.Generic; - using System.Text; - using ImageSharp.PixelFormats; - using ImageSharp.PixelFormats.PixelBlenders; - using ImageSharp.Tests.TestUtilities; - using Xunit; - - public class PixelOperations - { - public static TheoryData BlenderMappings = new TheoryData() - { - { new TestPixel(), typeof(DefaultNormalPixelBlender), PixelBlenderMode.Normal }, - { new TestPixel(), typeof(DefaultScreenPixelBlender), PixelBlenderMode.Screen }, - { new TestPixel(), typeof(DefaultHardLightPixelBlender), PixelBlenderMode.HardLight }, - { new TestPixel(), typeof(DefaultOverlayPixelBlender), PixelBlenderMode.Overlay }, - { new TestPixel(), typeof(DefaultDarkenPixelBlender), PixelBlenderMode.Darken }, - { new TestPixel(), typeof(DefaultLightenPixelBlender), PixelBlenderMode.Lighten }, - { new TestPixel(), typeof(DefaultAddPixelBlender), PixelBlenderMode.Add }, - { new TestPixel(), typeof(DefaultSubstractPixelBlender), PixelBlenderMode.Substract }, - { new TestPixel(), typeof(DefaultMultiplyPixelBlender), PixelBlenderMode.Multiply }, - - { new TestPixel(), typeof(DefaultNormalPixelBlender), PixelBlenderMode.Normal }, - { new TestPixel(), typeof(DefaultScreenPixelBlender), PixelBlenderMode.Screen }, - { new TestPixel(), typeof(DefaultHardLightPixelBlender), PixelBlenderMode.HardLight }, - { new TestPixel(), typeof(DefaultOverlayPixelBlender), PixelBlenderMode.Overlay }, - { new TestPixel(), typeof(DefaultDarkenPixelBlender), PixelBlenderMode.Darken }, - { new TestPixel(), typeof(DefaultLightenPixelBlender), PixelBlenderMode.Lighten }, - { new TestPixel(), typeof(DefaultAddPixelBlender), PixelBlenderMode.Add }, - { new TestPixel(), typeof(DefaultSubstractPixelBlender), PixelBlenderMode.Substract }, - { new TestPixel(), typeof(DefaultMultiplyPixelBlender), PixelBlenderMode.Multiply }, - }; - - [Theory] - [MemberData(nameof(BlenderMappings))] - public void ReturnsCorrectBlender(TestPixel pixel, Type type, PixelBlenderMode mode) - where TPixel : struct, IPixel - { - PixelBlender blender = PixelOperations.Instance.GetPixelBlender(mode); - Assert.IsType(type, blender); - } - } -}