diff --git a/src/ImageSharp/PixelFormats/PixelTransformMode.cs b/src/ImageSharp/PixelFormats/PixelTransformMode.cs new file mode 100644 index 000000000..7ac7eb029 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelTransformMode.cs @@ -0,0 +1,58 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.PixelFormats +{ + /// + /// Porter Duff Blending composition modes + /// + public enum PixelTransformMode + { + /// + /// Default blending mode, also known as "Normal" or "Alpha Blending" + /// + Normal, + + /// + /// Backdrop + Source + /// + Multiply, + + /// + /// Backdrop + Source + /// + Add, + + /// + /// Backdrop - Source + /// + Substract, + + /// + /// Screen effect + /// + Screen, + + /// + /// Darken effect + /// + Darken, + + /// + /// Lighten effect + /// + Lighten, + + /// + /// Overlay effect + /// + Overlay, + + /// + /// Hard light effect + /// + HardLight + } +} diff --git a/src/ImageSharp/PixelFormats/PixelTransformModeExtensions.cs b/src/ImageSharp/PixelFormats/PixelTransformModeExtensions.cs new file mode 100644 index 000000000..199224462 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelTransformModeExtensions.cs @@ -0,0 +1,56 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Collections.Generic; + using System.Text; + using PixelFormats; + + /// + /// Extensions to retrieve the appropiate pixel transformation functions for + /// + public static class PixelTransformModeExtensions + { + /// + /// Gets a pixel transformation function + /// + /// The pixel format used for both Backdrop and Source + /// The Duff Porter mode + /// A function that transforms a Backdrop and Source colors into a final color + public static Func GetPixelFunction(this PixelTransformMode mode) + where TPixel : IPixel + { + return mode.GetPixelFunction(); + } + + /// + /// Gets a pixel transformation function + /// + /// The pixel format used for Backdrop and Output + /// The pixel format used for Source + /// The Duff Porter mode + /// A function that transforms a Backdrop and Source colors into a final color + public static Func GetPixelFunction(this PixelTransformMode mode) + where TBckPixel : IPixel + where TSrcPixel : IPixel + { + switch (mode) + { + case PixelTransformMode.Normal: return PorterDuffFunctions.NormalBlendFunction; + case PixelTransformMode.Multiply: return PorterDuffFunctions.MultiplyFunction; + case PixelTransformMode.Add: return PorterDuffFunctions.AddFunction; + case PixelTransformMode.Screen: return PorterDuffFunctions.ScreenFunction; + case PixelTransformMode.Darken: return PorterDuffFunctions.DarkenFunction; + case PixelTransformMode.Lighten: return PorterDuffFunctions.LightenFunction; + case PixelTransformMode.Overlay: return PorterDuffFunctions.OverlayFunction; + case PixelTransformMode.HardLight: return PorterDuffFunctions.HardLightFunction; + + default: throw new NotImplementedException(nameof(mode)); + } + } + } +} diff --git a/src/ImageSharp/PixelFormats/PorterDuffFunctions.cs b/src/ImageSharp/PixelFormats/PorterDuffFunctions.cs new file mode 100644 index 000000000..0e9b985e8 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PorterDuffFunctions.cs @@ -0,0 +1,267 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.PixelFormats +{ + using System.Numerics; + using System.Runtime.CompilerServices; + + /// + /// Collection of Porter Duff alpha blending functions + /// + /// Backdrop Pixel Format + /// Source 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 TBckPixel : IPixel + where TsrcPixel : IPixel + { + /// + /// Source over backdrop + /// + /// Backgrop color + /// Source color + /// Opacity applied to Source Alpha + /// Output color + public static TBckPixel NormalBlendFunction(TBckPixel backdrop, TsrcPixel source, float opacity) + { + Vector4 l = source.ToVector4(); + l.W *= opacity; + if (l.W == 0) + { + return backdrop; + } + + Vector4 b = backdrop.ToVector4(); + + return Compose(b, l, l); + } + + /// + /// Source multiplied by backdrop + /// + /// Backgrop color + /// Source color + /// Opacity applied to Source Alpha + /// Output color + public static TBckPixel MultiplyFunction(TBckPixel backdrop, TsrcPixel source, float opacity) + { + Vector4 l = source.ToVector4(); + l.W *= opacity; + if (l.W == 0) + { + return backdrop; + } + + Vector4 b = backdrop.ToVector4(); + + return Compose(b, l, b * l); + } + + /// + /// Source added to backdrop + /// + /// Backgrop color + /// Source color + /// Opacity applied to Source Alpha + /// Output color + public static TBckPixel AddFunction(TBckPixel backdrop, TsrcPixel source, float opacity) + { + Vector4 l = source.ToVector4(); + l.W *= opacity; + if (l.W == 0) + { + return backdrop; + } + + Vector4 b = backdrop.ToVector4(); + + return Compose(b, l, Vector4.Min(Vector4.One, b + l)); + } + + /// + /// Source substracted from backdrop + /// + /// Backgrop color + /// Source color + /// Opacity applied to Source Alpha + /// Output color + public static TBckPixel SubstractFunction(TBckPixel backdrop, TsrcPixel source, float opacity) + { + Vector4 l = source.ToVector4(); + l.W *= opacity; + if (l.W == 0) + { + return backdrop; + } + + Vector4 b = backdrop.ToVector4(); + + return Compose(b, l, Vector4.Max(Vector4.Zero, b - l)); + } + + /// + /// Complement of source multiplied by the complement of backdrop + /// + /// Backgrop color + /// Source color + /// Opacity applied to Source Alpha + /// Output color + public static TBckPixel ScreenFunction(TBckPixel backdrop, TsrcPixel source, float opacity) + { + Vector4 l = source.ToVector4(); + l.W *= opacity; + if (l.W == 0) + { + return backdrop; + } + + Vector4 b = backdrop.ToVector4(); + + return Compose(b, l, Vector4.One - ((Vector4.One - b) * (Vector4.One - l))); + } + + /// + /// Per element, chooses the smallest value of source and backdrop + /// + /// Backgrop color + /// Source color + /// Opacity applied to Source Alpha + /// Output color + public static TBckPixel DarkenFunction(TBckPixel backdrop, TsrcPixel source, float opacity) + { + Vector4 l = source.ToVector4(); + l.W *= opacity; + if (l.W == 0) + { + return backdrop; + } + + Vector4 b = backdrop.ToVector4(); + + return Compose(b, l, Vector4.Min(b, l)); + } + + /// + /// Per element, chooses the largest value of source and backdrop + /// + /// Backgrop color + /// Source color + /// Opacity applied to Source Alpha + /// Output color + public static TBckPixel LightenFunction(TBckPixel backdrop, TsrcPixel source, float opacity) + { + Vector4 l = source.ToVector4(); + l.W *= opacity; + if (l.W == 0) + { + return backdrop; + } + + Vector4 b = backdrop.ToVector4(); + + return Compose(b, l, Vector4.Max(b, l)); + } + + /// + /// Overlays source over backdrop + /// + /// Backgrop color + /// Source color + /// Opacity applied to Source Alpha + /// Output color + public static TBckPixel OverlayFunction(TBckPixel backdrop, TsrcPixel source, float opacity) + { + Vector4 l = source.ToVector4(); + l.W *= opacity; + if (l.W == 0) + { + return backdrop; + } + + Vector4 b = backdrop.ToVector4(); + + float cr = OverlayValueFunction(b.X, l.X); + float cg = OverlayValueFunction(b.Y, l.Y); + float cb = OverlayValueFunction(b.Z, l.Z); + + return Compose(b, l, Vector4.Min(Vector4.One, new Vector4(cr, cg, cb, 0))); + } + + /// + /// Hard light effect + /// + /// Backgrop color + /// Source color + /// Opacity applied to Source Alpha + /// Output color + public static TBckPixel HardLightFunction(TBckPixel backdrop, TsrcPixel source, float opacity) + { + Vector4 l = source.ToVector4(); + l.W *= opacity; + if (l.W == 0) + { + return backdrop; + } + + Vector4 b = backdrop.ToVector4(); + + float cr = OverlayValueFunction(l.X, b.X); + float cg = OverlayValueFunction(l.Y, b.Y); + float cb = OverlayValueFunction(l.Z, b.Z); + + return Compose(b, l, Vector4.Min(Vector4.One, new Vector4(cr, cg, cb, 0))); + } + + /// + /// Helper function for Overlay andHardLight modes + /// + /// Backdrop color element + /// Source color element + /// Overlay value + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static float OverlayValueFunction(float backdrop, float source) + { + return backdrop <= 0.5f ? (2 * backdrop * source) : 1 - ((2 * (1 - source)) * (1 - backdrop)); + } + + /// + /// General composition function for all modes, with a general solution for alpha channel + /// + /// Original backgrop color + /// Original source color + /// Desired transformed color, without taking Alpha channel in account + /// The final color + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static TBckPixel Compose(Vector4 backdrop, Vector4 source, Vector4 xform) + { + System.Diagnostics.Debug.Assert(source.W > 0, nameof(source) + " Alpha must be non 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)) / a; + xform.W = a; + + TBckPixel packed = default(TBckPixel); + packed.PackFromVector4(xform); + + return packed; + } + } +} \ No newline at end of file