// // 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) { DebugGuard.MustBeGreaterThan(source.W, 0, nameof(source.W)); // calculate weights float xw = backdrop.W * source.W; float bw = backdrop.W - xw; float sw = source.W - xw; // calculate final alpha float a = xw + bw + sw; // calculate final value xform = ((xform * xw) + (backdrop * bw) + (source * sw)) / a; xform.W = a; TBckPixel packed = default(TBckPixel); packed.PackFromVector4(xform); return packed; } } }