From 3671d9b906316d01df8ed2e6936f1a95e1208539 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Mon, 1 May 2017 14:10:32 +0100 Subject: [PATCH] blend functions everywhere. --- .../Brushes/RecolorBrush{TPixel}.cs | 18 +- .../Processors/DrawImageProcessor.cs | 6 +- .../Processors/DrawPathProcessor.cs | 45 ++- .../Processors/FillProcessor.cs | 24 +- src/ImageSharp/GraphicsOptions.cs | 2 +- .../PixelBlenders/PorterDuffFunctions.cs | 4 +- .../PixelFormats/Rgba32.Transforms.cs | 254 --------------- .../PixelFormats/RgbaVector.Transforms.cs | 251 --------------- .../PixelFormats/Vector4BlendTransforms.cs | 292 ------------------ .../Processing/ColorMatrix/Lomograph.cs | 33 +- .../Processing/ColorMatrix/Polaroid.cs | 33 +- .../Processing/Effects/BackgroundColor.cs | 37 ++- .../Processing/Overlays/Vignette.cs | 87 +++++- .../ColorMatrix/LomographProcessor.cs | 12 +- .../ColorMatrix/PolaroidProcessor.cs | 14 +- .../Effects/BackgroundColorProcessor.cs | 38 ++- .../Processors/Overlays/VignetteProcessor.cs | 36 ++- tests/ImageSharp.Benchmarks/Samplers/Glow.cs | 33 +- .../Colors/Rgba32TransformTests.cs | 118 ------- .../Colors/RgbaVectorTransformTests.cs | 120 ------- .../Drawing/DrawImageEffectTest.cs | 46 --- .../ImageSharp.Tests/Drawing/DrawImageTest.cs | 48 +-- 22 files changed, 358 insertions(+), 1193 deletions(-) delete mode 100644 src/ImageSharp/PixelFormats/Rgba32.Transforms.cs delete mode 100644 src/ImageSharp/PixelFormats/RgbaVector.Transforms.cs delete mode 100644 src/ImageSharp/PixelFormats/Vector4BlendTransforms.cs delete mode 100644 tests/ImageSharp.Tests/Colors/Rgba32TransformTests.cs delete mode 100644 tests/ImageSharp.Tests/Colors/RgbaVectorTransformTests.cs delete mode 100644 tests/ImageSharp.Tests/Drawing/DrawImageEffectTest.cs diff --git a/src/ImageSharp.Drawing/Brushes/RecolorBrush{TPixel}.cs b/src/ImageSharp.Drawing/Brushes/RecolorBrush{TPixel}.cs index 19ce469141..a96202b7b4 100644 --- a/src/ImageSharp.Drawing/Brushes/RecolorBrush{TPixel}.cs +++ b/src/ImageSharp.Drawing/Brushes/RecolorBrush{TPixel}.cs @@ -72,26 +72,29 @@ namespace ImageSharp.Drawing.Brushes /// /// The target color. /// - private readonly Vector4 targeTPixel; + private readonly Vector4 targetColor; /// /// The threshold. /// private readonly float threshold; + private readonly TPixel targetColorPixel; + /// /// Initializes a new instance of the class. /// /// The source pixels. /// Color of the source. - /// Color of the target. + /// Color of the target. /// The threshold . /// The options - public RecolorBrushApplicator(PixelAccessor sourcePixels, TPixel sourceColor, TPixel targeTPixel, float threshold, GraphicsOptions options) + public RecolorBrushApplicator(PixelAccessor sourcePixels, TPixel sourceColor, TPixel targetColor, float threshold, GraphicsOptions options) : base(sourcePixels, options) { this.sourceColor = sourceColor.ToVector4(); - this.targeTPixel = targeTPixel.ToVector4(); + this.targetColor = targetColor.ToVector4(); + this.targetColorPixel = targetColor; // Lets hack a min max extreams for a color space by letteing the IPackedPixel clamp our values to something in the correct spaces :) TPixel maxColor = default(TPixel); @@ -120,11 +123,10 @@ namespace ImageSharp.Drawing.Brushes if (distance <= this.threshold) { float lerpAmount = (this.threshold - distance) / this.threshold; - Vector4 blended = Vector4BlendTransforms.PremultipliedLerp( - background, - this.targeTPixel, + return this.Blender.Blend( + result, + this.targetColorPixel, lerpAmount); - result.PackFromVector4(blended); } return result; diff --git a/src/ImageSharp.Drawing/Processors/DrawImageProcessor.cs b/src/ImageSharp.Drawing/Processors/DrawImageProcessor.cs index e58db46895..c49631de85 100644 --- a/src/ImageSharp.Drawing/Processors/DrawImageProcessor.cs +++ b/src/ImageSharp.Drawing/Processors/DrawImageProcessor.cs @@ -74,9 +74,13 @@ namespace ImageSharp.Drawing.Processors Rectangle bounds = this.Image.Bounds; int minX = Math.Max(this.Location.X, sourceRectangle.X); int maxX = Math.Min(this.Location.X + bounds.Width, sourceRectangle.Width); + maxX = Math.Min(this.Location.X + this.Size.Width, maxX); + int minY = Math.Max(this.Location.Y, sourceRectangle.Y); int maxY = Math.Min(this.Location.Y + bounds.Height, sourceRectangle.Bottom); + maxY = Math.Min(this.Location.Y + this.Size.Height, maxY); + int width = maxX - minX; using (Buffer amount = new Buffer(width)) using (PixelAccessor toBlendPixels = targetImage.Lock()) @@ -94,7 +98,7 @@ namespace ImageSharp.Drawing.Processors y => { BufferSpan background = sourcePixels.GetRowSpan(y).Slice(minX, width); - BufferSpan foreground = toBlendPixels.GetRowSpan(y - minY).Slice(0, width); + BufferSpan foreground = toBlendPixels.GetRowSpan(y - this.Location.Y).Slice(0, width); this.blender.Blend(background, background, foreground, amount); }); } diff --git a/src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs b/src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs index 124722cc09..3fd829549d 100644 --- a/src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs +++ b/src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs @@ -86,37 +86,34 @@ namespace ImageSharp.Drawing.Processors polyStartY = 0; } + int width = maxX - minX; + PixelBlender blender = PixelOperations.Instance.GetPixelBlender(this.Options.BlenderMode); + Parallel.For( - minY, - maxY, - this.ParallelOptions, - y => - { - int offsetY = y - polyStartY; + minY, + maxY, + this.ParallelOptions, + y => + { + int offsetY = y - polyStartY; - for (int x = minX; x < maxX; x++) + using (Buffer amount = new Buffer(width)) + using (Buffer colors = new Buffer(width)) + { + for (int i = 0; i < width; i++) { - // TODO add find intersections code to skip and scan large regions of this. + int x = i + minX; int offsetX = x - startX; PointInfo info = this.Path.GetPointInfo(offsetX, offsetY); - ColoredPointInfo color = applicator.GetColor(offsetX, offsetY, info); - - float opacity = this.Opacity(color.DistanceFromElement); - - if (opacity > Constants.Epsilon) - { - Vector4 backgroundVector = sourcePixels[offsetX, offsetY].ToVector4(); - Vector4 sourceVector = color.Color.ToVector4(); - - Vector4 finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, opacity); - - TPixel packed = default(TPixel); - packed.PackFromVector4(finalColor); - sourcePixels[offsetX, offsetY] = packed; - } + amount[i] = (this.Opacity(color.DistanceFromElement) * this.Options.BlendPercentage).Clamp(0, 1); + colors[i] = color.Color; } - }); + + BufferSpan destination = sourcePixels.GetRowSpan(offsetY).Slice(minX - startX, width); + blender.Blend(destination, destination, colors, amount); + } + }); } } diff --git a/src/ImageSharp.Drawing/Processors/FillProcessor.cs b/src/ImageSharp.Drawing/Processors/FillProcessor.cs index 0634a06a3e..25eccd245c 100644 --- a/src/ImageSharp.Drawing/Processors/FillProcessor.cs +++ b/src/ImageSharp.Drawing/Processors/FillProcessor.cs @@ -62,32 +62,30 @@ namespace ImageSharp.Drawing.Processors startY = 0; } + int width = maxX - minX; + // we could possibly do some optermising by having knowledge about the individual brushes operate // for example If brush is SolidBrush then we could just get the color upfront // and skip using the IBrushApplicator?. using (PixelAccessor sourcePixels = source.Lock()) + using (Buffer amount = new Buffer(width)) using (BrushApplicator applicator = this.brush.CreateApplicator(sourcePixels, sourceRectangle, this.options)) { - Parallel.For( + for (int i = 0; i < width; i++) + { + amount[i] = this.options.BlendPercentage; + } + + Parallel.For( minY, maxY, this.ParallelOptions, y => { int offsetY = y - startY; - for (int x = minX; x < maxX; x++) - { - int offsetX = x - startX; - - Vector4 backgroundVector = sourcePixels[offsetX, offsetY].ToVector4(); - Vector4 sourceVector = applicator[offsetX, offsetY].ToVector4(); - - Vector4 finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, 1); + int offsetX = minX - startX; - TPixel packed = default(TPixel); - packed.PackFromVector4(finalColor); - sourcePixels[offsetX, offsetY] = packed; - } + applicator.Apply(amount, offsetX, offsetY); }); } } diff --git a/src/ImageSharp/GraphicsOptions.cs b/src/ImageSharp/GraphicsOptions.cs index c32103652b..f45582e1e6 100644 --- a/src/ImageSharp/GraphicsOptions.cs +++ b/src/ImageSharp/GraphicsOptions.cs @@ -60,7 +60,7 @@ namespace ImageSharp /// public float BlendPercentage { - get => this.blendPercentage ?? 1; + get => (this.blendPercentage ?? 1).Clamp(0, 1); set => this.blendPercentage = value; } diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs index 6b5126f720..25eb6a5c87 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs @@ -9,7 +9,7 @@ namespace ImageSharp.PixelFormats.PixelBlenders using System.Runtime.CompilerServices; /// - /// Collection of Porter Duff alpha blending functions + /// Collection of Porter Duff alpha blending functions applying an the 'Over' composition model. /// /// /// These functions are designed to be a general solution for all color cases, @@ -19,7 +19,7 @@ namespace ImageSharp.PixelFormats.PixelBlenders /// Note there are faster functions for when the backdrop color is known /// to be opaque /// - internal static class PorterDuffFunctions + internal static partial class PorterDuffFunctions { /// /// Source over backdrop diff --git a/src/ImageSharp/PixelFormats/Rgba32.Transforms.cs b/src/ImageSharp/PixelFormats/Rgba32.Transforms.cs deleted file mode 100644 index bd13a2de1a..0000000000 --- a/src/ImageSharp/PixelFormats/Rgba32.Transforms.cs +++ /dev/null @@ -1,254 +0,0 @@ -// -// 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; - - /// - /// Provides operators and composition algorithms. - /// - public partial struct Rgba32 - { - /// - /// Adds the second color to the first. - /// - /// The first source color. - /// The second source color. - /// - /// The . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgba32 operator +(Rgba32 left, Rgba32 right) - { - Vector4 add = left.ToVector4() + right.ToVector4(); - return PackNew(ref add); - } - - /// - /// Subtracts the second color from the first. - /// - /// The first source color. - /// The second source color. - /// - /// The . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgba32 operator -(Rgba32 left, Rgba32 right) - { - Vector4 sub = left.ToVector4() - right.ToVector4(); - return PackNew(ref sub); - } - - /// - /// The blending formula simply selects the source color. - /// - /// The backdrop color. - /// The source color. - /// - /// The . - /// - public static Rgba32 Normal(Rgba32 backdrop, Rgba32 source) - { - Vector4 normal = Vector4BlendTransforms.Normal(backdrop.ToVector4(), source.ToVector4()); - return PackNew(ref normal); - } - - /// - /// Blends two colors by multiplication. - /// - /// The source color is multiplied by the destination color and replaces the destination. - /// The resultant color is always at least as dark as either the source or destination color. - /// Multiplying any color with black results in black. Multiplying any color with white preserves the - /// original color. - /// - /// - /// The backdrop color. - /// The source color. - /// - /// The . - /// - public static Rgba32 Multiply(Rgba32 backdrop, Rgba32 source) - { - Vector4 multiply = Vector4BlendTransforms.Multiply(backdrop.ToVector4(), source.ToVector4()); - return PackNew(ref multiply); - } - - /// - /// Multiplies the complements of the backdrop and source color values, then complements the result. - /// - /// The result color is always at least as light as either of the two constituent colors. Screening any - /// color with white produces white; screening with black leaves the original color unchanged. - /// The effect is similar to projecting multiple photographic slides simultaneously onto a single screen. - /// - /// - /// The backdrop color. - /// The source color. - /// - /// The . - /// - public static Rgba32 Screen(Rgba32 backdrop, Rgba32 source) - { - Vector4 subtract = Vector4BlendTransforms.Screen(backdrop.ToVector4(), source.ToVector4()); - return PackNew(ref subtract); - } - - /// - /// Multiplies or screens the colors, depending on the source color value. The effect is similar to - /// shining a harsh spotlight on the backdrop. - /// - /// The backdrop color. - /// The source color. - /// - /// The . - /// - public static Rgba32 HardLight(Rgba32 backdrop, Rgba32 source) - { - Vector4 hardlight = Vector4BlendTransforms.HardLight(backdrop.ToVector4(), source.ToVector4()); - return PackNew(ref hardlight); - } - - /// - /// Multiplies or screens the colors, depending on the backdrop color value. - /// - /// Source colors overlay the backdrop while preserving its highlights and shadows. - /// The backdrop color is not replaced but is mixed with the source color to reflect the lightness or darkness - /// of the backdrop. - /// - /// - /// The backdrop color. - /// The source color. - /// - /// The . - /// - public static Rgba32 Overlay(Rgba32 backdrop, Rgba32 source) - { - Vector4 overlay = Vector4BlendTransforms.Overlay(backdrop.ToVector4(), source.ToVector4()); - return PackNew(ref overlay); - } - - /// - /// Selects the darker of the backdrop and source colors. - /// The backdrop is replaced with the source where the source is darker; otherwise, it is left unchanged. - /// - /// The backdrop color. - /// The source color. - /// - /// The . - /// - public static Rgba32 Darken(Rgba32 backdrop, Rgba32 source) - { - Vector4 darken = Vector4BlendTransforms.Darken(backdrop.ToVector4(), source.ToVector4()); - return PackNew(ref darken); - } - - /// - /// Selects the lighter of the backdrop and source colors. - /// The backdrop is replaced with the source where the source is lighter; otherwise, it is left unchanged. - /// - /// The backdrop color. - /// The source color. - /// - /// The . - /// - public static Rgba32 Lighten(Rgba32 backdrop, Rgba32 source) - { - Vector4 lighten = Vector4BlendTransforms.Lighten(backdrop.ToVector4(), source.ToVector4()); - return PackNew(ref lighten); - } - - /// - /// Darkens or lightens the colors, depending on the source color value. The effect is similar to shining - /// a diffused spotlight on the backdrop. - /// - /// The backdrop color. - /// The source color. - /// - /// The . - /// - public static Rgba32 SoftLight(Rgba32 backdrop, Rgba32 source) - { - Vector4 softlight = Vector4BlendTransforms.SoftLight(backdrop.ToVector4(), source.ToVector4()); - return PackNew(ref softlight); - } - - /// - /// Brightens the backdrop color to reflect the source color. Painting with black produces no changes. - /// - /// The backdrop color. - /// The source color. - /// - /// The . - /// - public static Rgba32 ColorDodge(Rgba32 backdrop, Rgba32 source) - { - Vector4 dodge = Vector4BlendTransforms.Dodge(backdrop.ToVector4(), source.ToVector4()); - return PackNew(ref dodge); - } - - /// - /// Darkens the backdrop color to reflect the source color. Painting with white produces no change. - /// - /// The backdrop color. - /// The source color. - /// - /// The . - /// - public static Rgba32 ColorBurn(Rgba32 backdrop, Rgba32 source) - { - Vector4 burn = Vector4BlendTransforms.Burn(backdrop.ToVector4(), source.ToVector4()); - return PackNew(ref burn); - } - - /// - /// Subtracts the darker of the two constituent colors from the lighter color. - /// Painting with white inverts the backdrop color; painting with black produces no change. - /// - /// The backdrop color. - /// The source color. - /// - /// The . - /// - public static Rgba32 Difference(Rgba32 backdrop, Rgba32 source) - { - Vector4 difference = Vector4BlendTransforms.Difference(backdrop.ToVector4(), source.ToVector4()); - return PackNew(ref difference); - } - - /// - /// Produces an effect similar to that of the mode but lower in contrast. Painting with white - /// inverts the backdrop color; painting with black produces no change - /// - /// The backdrop color. - /// The source color. - /// - /// The . - /// - public static Rgba32 Exclusion(Rgba32 backdrop, Rgba32 source) - { - Vector4 exclusion = Vector4BlendTransforms.Exclusion(backdrop.ToVector4(), source.ToVector4()); - return PackNew(ref exclusion); - } - - /// - /// Linearly interpolates from one color to another based on the given weighting. - /// - /// The first color value. - /// The second color value. - /// - /// A value between 0 and 1 indicating the weight of the second source vector. - /// At amount = 0, "from" is returned, at amount = 1, "to" is returned. - /// - /// - /// The - /// - public static Rgba32 Lerp(Rgba32 from, Rgba32 to, float amount) - { - Vector4 lerp = Vector4.Lerp(from.ToVector4(), to.ToVector4(), amount); - return PackNew(ref lerp); - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/RgbaVector.Transforms.cs b/src/ImageSharp/PixelFormats/RgbaVector.Transforms.cs deleted file mode 100644 index fec1aa346e..0000000000 --- a/src/ImageSharp/PixelFormats/RgbaVector.Transforms.cs +++ /dev/null @@ -1,251 +0,0 @@ -// -// 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; - - /// - /// Provides operators and composition algorithms. - /// - public partial struct RgbaVector - { - /// - /// Adds the second color to the first. - /// - /// The first source color. - /// The second source color. - /// - /// The . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static RgbaVector operator +(RgbaVector left, RgbaVector right) - { - return new RgbaVector(left.backingVector + right.backingVector); - } - - /// - /// Subtracts the second color from the first. - /// - /// The first source color. - /// The second source color. - /// - /// The . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static RgbaVector operator -(RgbaVector left, RgbaVector right) - { - return new RgbaVector(left.backingVector - right.backingVector); - } - - /// - /// The blending formula simply selects the source color. - /// - /// The backdrop color. - /// The source color. - /// - /// The . - /// - public static RgbaVector Normal(RgbaVector backdrop, RgbaVector source) - { - Vector4 normal = Vector4BlendTransforms.Normal(backdrop.backingVector, source.backingVector); - return new RgbaVector(normal); - } - - /// - /// Blends two colors by multiplication. - /// - /// The source color is multiplied by the destination color and replaces the destination. - /// The resultant color is always at least as dark as either the source or destination color. - /// Multiplying any color with black results in black. Multiplying any color with white preserves the - /// original color. - /// - /// - /// The backdrop color. - /// The source color. - /// - /// The . - /// - public static RgbaVector Multiply(RgbaVector backdrop, RgbaVector source) - { - Vector4 multiply = Vector4BlendTransforms.Multiply(backdrop.backingVector, source.backingVector); - return new RgbaVector(multiply); - } - - /// - /// Multiplies the complements of the backdrop and source color values, then complements the result. - /// - /// The result color is always at least as light as either of the two constituent colors. Screening any - /// color with white produces white; screening with black leaves the original color unchanged. - /// The effect is similar to projecting multiple photographic slides simultaneously onto a single screen. - /// - /// - /// The backdrop color. - /// The source color. - /// - /// The . - /// - public static RgbaVector Screen(RgbaVector backdrop, RgbaVector source) - { - Vector4 subtract = Vector4BlendTransforms.Screen(backdrop.backingVector, source.backingVector); - return new RgbaVector(subtract); - } - - /// - /// Multiplies or screens the colors, depending on the source color value. The effect is similar to - /// shining a harsh spotlight on the backdrop. - /// - /// The backdrop color. - /// The source color. - /// - /// The . - /// - public static RgbaVector HardLight(RgbaVector backdrop, RgbaVector source) - { - Vector4 hardlight = Vector4BlendTransforms.HardLight(backdrop.backingVector, source.backingVector); - return new RgbaVector(hardlight); - } - - /// - /// Multiplies or screens the colors, depending on the backdrop color value. - /// - /// Source colors overlay the backdrop while preserving its highlights and shadows. - /// The backdrop color is not replaced but is mixed with the source color to reflect the lightness or darkness - /// of the backdrop. - /// - /// - /// The backdrop color. - /// The source color. - /// - /// The . - /// - public static RgbaVector Overlay(RgbaVector backdrop, RgbaVector source) - { - Vector4 overlay = Vector4BlendTransforms.Overlay(backdrop.backingVector, source.backingVector); - return new RgbaVector(overlay); - } - - /// - /// Selects the darker of the backdrop and source colors. - /// The backdrop is replaced with the source where the source is darker; otherwise, it is left unchanged. - /// - /// The backdrop color. - /// The source color. - /// - /// The . - /// - public static RgbaVector Darken(RgbaVector backdrop, RgbaVector source) - { - Vector4 darken = Vector4BlendTransforms.Darken(backdrop.backingVector, source.backingVector); - return new RgbaVector(darken); - } - - /// - /// Selects the lighter of the backdrop and source colors. - /// The backdrop is replaced with the source where the source is lighter; otherwise, it is left unchanged. - /// - /// The backdrop color. - /// The source color. - /// - /// The . - /// - public static RgbaVector Lighten(RgbaVector backdrop, RgbaVector source) - { - Vector4 lighten = Vector4BlendTransforms.Lighten(backdrop.backingVector, source.backingVector); - return new RgbaVector(lighten); - } - - /// - /// Darkens or lightens the colors, depending on the source color value. The effect is similar to shining - /// a diffused spotlight on the backdrop. - /// - /// The backdrop color. - /// The source color. - /// - /// The . - /// - public static RgbaVector SoftLight(RgbaVector backdrop, RgbaVector source) - { - Vector4 softlight = Vector4BlendTransforms.SoftLight(backdrop.backingVector, source.backingVector); - return new RgbaVector(softlight); - } - - /// - /// Brightens the backdrop color to reflect the source color. Painting with black produces no changes. - /// - /// The backdrop color. - /// The source color. - /// - /// The . - /// - public static RgbaVector ColorDodge(RgbaVector backdrop, RgbaVector source) - { - Vector4 dodge = Vector4BlendTransforms.Dodge(backdrop.backingVector, source.backingVector); - return new RgbaVector(dodge); - } - - /// - /// Darkens the backdrop color to reflect the source color. Painting with white produces no change. - /// - /// The backdrop color. - /// The source color. - /// - /// The . - /// - public static RgbaVector ColorBurn(RgbaVector backdrop, RgbaVector source) - { - Vector4 burn = Vector4BlendTransforms.Burn(backdrop.backingVector, source.backingVector); - return new RgbaVector(burn); - } - - /// - /// Subtracts the darker of the two constituent colors from the lighter color. - /// Painting with white inverts the backdrop color; painting with black produces no change. - /// - /// The backdrop color. - /// The source color. - /// - /// The . - /// - public static RgbaVector Difference(RgbaVector backdrop, RgbaVector source) - { - Vector4 difference = Vector4BlendTransforms.Difference(backdrop.backingVector, source.backingVector); - return new RgbaVector(difference); - } - - /// - /// Produces an effect similar to that of the mode but lower in contrast. Painting with white - /// inverts the backdrop color; painting with black produces no change - /// - /// The backdrop color. - /// The source color. - /// - /// The . - /// - public static RgbaVector Exclusion(RgbaVector backdrop, RgbaVector source) - { - Vector4 exclusion = Vector4BlendTransforms.Exclusion(backdrop.backingVector, source.backingVector); - return new RgbaVector(exclusion); - } - - /// - /// Linearly interpolates from one color to another based on the given weighting. - /// - /// The first color value. - /// The second color value. - /// - /// A value between 0 and 1 indicating the weight of the second source vector. - /// At amount = 0, "from" is returned, at amount = 1, "to" is returned. - /// - /// - /// The - /// - public static RgbaVector Lerp(RgbaVector from, RgbaVector to, float amount) - { - return new RgbaVector(Vector4.Lerp(from.backingVector, to.backingVector, amount)); - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/Vector4BlendTransforms.cs b/src/ImageSharp/PixelFormats/Vector4BlendTransforms.cs deleted file mode 100644 index f066e9a816..0000000000 --- a/src/ImageSharp/PixelFormats/Vector4BlendTransforms.cs +++ /dev/null @@ -1,292 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.PixelFormats -{ - using System.Numerics; - - /// - /// Transform algorithms that match the equations defined in the W3C Compositing and Blending Level 1 specification. - /// - /// - internal class Vector4BlendTransforms - { - /// - /// The blending formula simply selects the source vector. - /// - /// The backdrop vector. - /// The source vector. - /// - /// The . - /// - public static Vector4 Normal(Vector4 backdrop, Vector4 source) - { - return new Vector4(source.X, source.Y, source.Z, source.W); - } - - /// - /// Blends two vectors by multiplication. - /// - /// The backdrop vector. - /// The source vector. - /// - /// The . - /// - public static Vector4 Multiply(Vector4 backdrop, Vector4 source) - { - Vector4 multiply = backdrop * source; - multiply.W = backdrop.W; - return multiply; - } - - /// - /// Multiplies the complements of the backdrop and source vector values, then complements the result. - /// - /// The backdrop vector. - /// The source vector. - /// - /// The . - /// - public static Vector4 Screen(Vector4 backdrop, Vector4 source) - { - Vector4 subtract = backdrop + source - (backdrop * source); - subtract.W = backdrop.W; - return subtract; - } - - /// - /// Multiplies or screens the colors, depending on the source vector value. - /// - /// The backdrop vector. - /// The source vector. - /// - /// The . - /// - public static Vector4 HardLight(Vector4 backdrop, Vector4 source) - { - return new Vector4(BlendOverlay(source.X, backdrop.X), BlendOverlay(source.Y, backdrop.Y), BlendOverlay(source.Z, backdrop.Z), backdrop.W); - } - - /// - /// Multiplies or screens the vectors, depending on the backdrop vector value. - /// - /// The backdrop vector. - /// The source vector. - /// - /// The . - /// - public static Vector4 Overlay(Vector4 backdrop, Vector4 source) - { - return new Vector4(BlendOverlay(backdrop.X, source.X), BlendOverlay(backdrop.Y, source.Y), BlendOverlay(backdrop.Z, source.Z), backdrop.W); - } - - /// - /// Selects the minimum of the backdrop and source vectors. - /// - /// The backdrop vector. - /// The source vector. - /// - /// The . - /// - public static Vector4 Darken(Vector4 backdrop, Vector4 source) - { - Vector4 result = Vector4.Min(backdrop, source); - result.W = backdrop.W; - return result; - } - - /// - /// Selects the max of the backdrop and source vector. - /// - /// The backdrop vector. - /// The source vector. - /// - /// The . - /// - public static Vector4 Lighten(Vector4 backdrop, Vector4 source) - { - Vector4 result = Vector4.Max(backdrop, source); - result.W = backdrop.W; - return result; - } - - /// - /// Selects the maximum or minimum of the vectors, depending on the source vector value. - /// - /// The backdrop vector. - /// The source vector. - /// - /// The . - /// - public static Vector4 SoftLight(Vector4 backdrop, Vector4 source) - { - return new Vector4(BlendSoftLight(backdrop.X, source.X), BlendSoftLight(backdrop.Y, source.Y), BlendSoftLight(backdrop.Z, source.Z), backdrop.W); - } - - /// - /// Increases the backdrop vector to reflect the source vector. - /// - /// The backdrop vector. - /// The source vector. - /// - /// The . - /// - public static Vector4 Dodge(Vector4 backdrop, Vector4 source) - { - return new Vector4(BlendDodge(backdrop.X, source.X), BlendDodge(backdrop.Y, source.Y), BlendDodge(backdrop.Z, source.Z), backdrop.W); - } - - /// - /// Decreases the backdrop vector to reflect the source vector. - /// - /// The backdrop vector. - /// The source vector. - /// - /// The . - /// - public static Vector4 Burn(Vector4 backdrop, Vector4 source) - { - return new Vector4(BlendBurn(backdrop.X, source.X), BlendBurn(backdrop.Y, source.Y), BlendBurn(backdrop.Z, source.Z), backdrop.W); - } - - /// - /// Subtracts the minimum of the two constituent vectors from the maximum vector. - /// - /// The backdrop vector. - /// The source vector. - /// - /// The . - /// - public static Vector4 Difference(Vector4 backdrop, Vector4 source) - { - Vector4 result = Vector4.Abs(backdrop - source); - result.W = backdrop.W; - return result; - } - - /// - /// Produces an effect similar to that of the mode but lower in magnitude. - /// - /// The backdrop vector. - /// The source vector. - /// - /// The . - /// - public static Vector4 Exclusion(Vector4 backdrop, Vector4 source) - { - return new Vector4(BlendExclusion(backdrop.X, source.X), BlendExclusion(backdrop.Y, source.Y), BlendExclusion(backdrop.Z, source.Z), backdrop.W); - } - - /// - /// Linearly interpolates from one vector to another based on the given weighting. - /// The two vectors are premultiplied before operating. - /// - /// The backdrop vector. - /// The source vector. - /// - /// A value between 0 and 1 indicating the weight of the second source vector. - /// At amount = 0, "from" is returned, at amount = 1, "to" is returned. - /// - /// - /// The - /// - public static Vector4 PremultipliedLerp(Vector4 backdrop, Vector4 source, float amount) - { - amount = amount.Clamp(0, 1); - - // Santize on zero alpha - if (MathF.Abs(backdrop.W) < Constants.Epsilon) - { - source.W *= amount; - return source; - } - - if (MathF.Abs(source.W) < Constants.Epsilon) - { - return backdrop; - } - - // Premultiply the source vector. - // Oddly premultiplying the background vector creates dark outlines when pixels - // Have low alpha values. - source = new Vector4(source.X, source.Y, source.Z, 1) * (source.W * amount); - - // This should be implementing the following formula - // https://en.wikipedia.org/wiki/Alpha_compositing - // Vout = Vs + Vb (1 - Vsa) - // Aout = Vsa + Vsb (1 - Vsa) - Vector3 inverseW = new Vector3(1 - source.W); - Vector3 xyzB = new Vector3(backdrop.X, backdrop.Y, backdrop.Z); - Vector3 xyzS = new Vector3(source.X, source.Y, source.Z); - - return new Vector4(xyzS + (xyzB * inverseW), source.W + (backdrop.W * (1 - source.W))); - } - - /// - /// Multiplies or screens the backdrop component, depending on the component value. - /// - /// The backdrop component. - /// The source component. - /// - /// The . - /// - private static float BlendOverlay(float b, float s) - { - return b <= .5F ? (2F * b * s) : (1F - (2F * (1F - b) * (1F - s))); - } - - /// - /// Darkens or lightens the backdrop component, depending on the source component value. - /// - /// The backdrop component. - /// The source component. - /// - /// The . - /// - private static float BlendSoftLight(float b, float s) - { - return s <= .5F ? ((2F * b * s) + (b * b * (1F - (2F * s)))) : (MathF.Sqrt(b) * ((2F * s) - 1F)) + (2F * b * (1F - s)); - } - - /// - /// Brightens the backdrop component to reflect the source component. - /// - /// The backdrop component. - /// The source component. - /// - /// The . - /// - private static float BlendDodge(float b, float s) - { - return MathF.Abs(s - 1F) < Constants.Epsilon ? s : MathF.Min(b / (1F - s), 1F); - } - - /// - /// Darkens the backdrop component to reflect the source component. - /// - /// The backdrop component. - /// The source component. - /// - /// The . - /// - private static float BlendBurn(float b, float s) - { - return MathF.Abs(s) < Constants.Epsilon ? s : MathF.Max(1F - ((1F - b) / s), 0F); - } - - /// - /// Darkens the backdrop component to reflect the source component. - /// - /// The backdrop component. - /// The source component. - /// - /// The . - /// - private static float BlendExclusion(float b, float s) - { - return b + s - (2F * b * s); - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/ColorMatrix/Lomograph.cs b/src/ImageSharp/Processing/ColorMatrix/Lomograph.cs index 3299add7f7..937dca9ba8 100644 --- a/src/ImageSharp/Processing/ColorMatrix/Lomograph.cs +++ b/src/ImageSharp/Processing/ColorMatrix/Lomograph.cs @@ -26,7 +26,7 @@ namespace ImageSharp public static Image Lomograph(this Image source) where TPixel : struct, IPixel { - return Lomograph(source, source.Bounds); + return Lomograph(source, source.Bounds, GraphicsOptions.Default); } /// @@ -41,7 +41,36 @@ namespace ImageSharp public static Image Lomograph(this Image source, Rectangle rectangle) where TPixel : struct, IPixel { - source.ApplyProcessor(new LomographProcessor(), rectangle); + return Lomograph(source, rectangle, GraphicsOptions.Default); + } + + /// + /// Alters the colors of the image recreating an old Lomograph camera effect. + /// + /// The pixel format. + /// The image this method extends. + /// The options effecting pixel blending. + /// The . + public static Image Lomograph(this Image source, GraphicsOptions options) + where TPixel : struct, IPixel + { + return Lomograph(source, source.Bounds, options); + } + + /// + /// Alters the colors of the image recreating an old Lomograph camera effect. + /// + /// The pixel format. + /// The image this method extends. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The options effecting pixel blending. + /// The . + public static Image Lomograph(this Image source, Rectangle rectangle, GraphicsOptions options) + where TPixel : struct, IPixel + { + source.ApplyProcessor(new LomographProcessor(options), rectangle); return source; } } diff --git a/src/ImageSharp/Processing/ColorMatrix/Polaroid.cs b/src/ImageSharp/Processing/ColorMatrix/Polaroid.cs index 194800ec72..f1a573c05d 100644 --- a/src/ImageSharp/Processing/ColorMatrix/Polaroid.cs +++ b/src/ImageSharp/Processing/ColorMatrix/Polaroid.cs @@ -26,7 +26,7 @@ namespace ImageSharp public static Image Polaroid(this Image source) where TPixel : struct, IPixel { - return Polaroid(source, source.Bounds); + return Polaroid(source, GraphicsOptions.Default); } /// @@ -41,7 +41,36 @@ namespace ImageSharp public static Image Polaroid(this Image source, Rectangle rectangle) where TPixel : struct, IPixel { - source.ApplyProcessor(new PolaroidProcessor(), rectangle); + return Polaroid(source, rectangle, GraphicsOptions.Default); + } + + /// + /// Alters the colors of the image recreating an old Polaroid camera effect. + /// + /// The pixel format. + /// The image this method extends. + /// The options effecting pixel blending. + /// The . + public static Image Polaroid(this Image source, GraphicsOptions options) + where TPixel : struct, IPixel + { + return Polaroid(source, source.Bounds, options); + } + + /// + /// Alters the colors of the image recreating an old Polaroid camera effect. + /// + /// The pixel format. + /// The image this method extends. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The options effecting pixel blending. + /// The . + public static Image Polaroid(this Image source, Rectangle rectangle, GraphicsOptions options) + where TPixel : struct, IPixel + { + source.ApplyProcessor(new PolaroidProcessor(options), rectangle); return source; } } diff --git a/src/ImageSharp/Processing/Effects/BackgroundColor.cs b/src/ImageSharp/Processing/Effects/BackgroundColor.cs index a1914fee32..f52cf1cb2a 100644 --- a/src/ImageSharp/Processing/Effects/BackgroundColor.cs +++ b/src/ImageSharp/Processing/Effects/BackgroundColor.cs @@ -16,6 +16,38 @@ namespace ImageSharp /// public static partial class ImageExtensions { + /// + /// Replaces the background color of image with the given one. + /// + /// The pixel format. + /// The image this method extends. + /// The color to set as the background. + /// The options effecting pixel blending. + /// The . + public static Image BackgroundColor(this Image source, TPixel color, GraphicsOptions options) + where TPixel : struct, IPixel + { + return BackgroundColor(source, color, source.Bounds, options); + } + + /// + /// Replaces the background color of image with the given one. + /// + /// The pixel format. + /// The image this method extends. + /// The color to set as the background. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The options effecting pixel blending. + /// The . + public static Image BackgroundColor(this Image source, TPixel color, Rectangle rectangle, GraphicsOptions options) + where TPixel : struct, IPixel + { + source.ApplyProcessor(new BackgroundColorProcessor(color, options), rectangle); + return source; + } + /// /// Replaces the background color of image with the given one. /// @@ -26,7 +58,7 @@ namespace ImageSharp public static Image BackgroundColor(this Image source, TPixel color) where TPixel : struct, IPixel { - return BackgroundColor(source, color, source.Bounds); + return BackgroundColor(source, color, GraphicsOptions.Default); } /// @@ -42,8 +74,7 @@ namespace ImageSharp public static Image BackgroundColor(this Image source, TPixel color, Rectangle rectangle) where TPixel : struct, IPixel { - source.ApplyProcessor(new BackgroundColorProcessor(color), rectangle); - return source; + return BackgroundColor(source, color, rectangle, GraphicsOptions.Default); } } } diff --git a/src/ImageSharp/Processing/Overlays/Vignette.cs b/src/ImageSharp/Processing/Overlays/Vignette.cs index f805dd07a0..2eaf2e1efa 100644 --- a/src/ImageSharp/Processing/Overlays/Vignette.cs +++ b/src/ImageSharp/Processing/Overlays/Vignette.cs @@ -25,7 +25,7 @@ namespace ImageSharp public static Image Vignette(this Image source) where TPixel : struct, IPixel { - return Vignette(source, NamedColors.Black, source.Bounds.Width * .5F, source.Bounds.Height * .5F, source.Bounds); + return Vignette(source, GraphicsOptions.Default); } /// @@ -38,7 +38,7 @@ namespace ImageSharp public static Image Vignette(this Image source, TPixel color) where TPixel : struct, IPixel { - return Vignette(source, color, source.Bounds.Width * .5F, source.Bounds.Height * .5F, source.Bounds); + return Vignette(source, color, GraphicsOptions.Default); } /// @@ -52,7 +52,7 @@ namespace ImageSharp public static Image Vignette(this Image source, float radiusX, float radiusY) where TPixel : struct, IPixel { - return Vignette(source, NamedColors.Black, radiusX, radiusY, source.Bounds); + return Vignette(source, radiusX, radiusY, GraphicsOptions.Default); } /// @@ -67,7 +67,7 @@ namespace ImageSharp public static Image Vignette(this Image source, Rectangle rectangle) where TPixel : struct, IPixel { - return Vignette(source, NamedColors.Black, 0, 0, rectangle); + return Vignette(source, rectangle, GraphicsOptions.Default); } /// @@ -85,7 +85,84 @@ namespace ImageSharp public static Image Vignette(this Image source, TPixel color, float radiusX, float radiusY, Rectangle rectangle) where TPixel : struct, IPixel { - VignetteProcessor processor = new VignetteProcessor(color) { RadiusX = radiusX, RadiusY = radiusY }; + return Vignette(source, color, radiusX, radiusY, rectangle, GraphicsOptions.Default); + } + + /// + /// Applies a radial vignette effect to an image. + /// + /// The pixel format. + /// The image this method extends. + /// The options effecting pixel blending. + /// The . + public static Image Vignette(this Image source, GraphicsOptions options) + where TPixel : struct, IPixel + { + return Vignette(source, NamedColors.Black, source.Bounds.Width * .5F, source.Bounds.Height * .5F, source.Bounds, options); + } + + /// + /// Applies a radial vignette effect to an image. + /// + /// The pixel format. + /// The image this method extends. + /// The color to set as the vignette. + /// The options effecting pixel blending. + /// The . + public static Image Vignette(this Image source, TPixel color, GraphicsOptions options) + where TPixel : struct, IPixel + { + return Vignette(source, color, source.Bounds.Width * .5F, source.Bounds.Height * .5F, source.Bounds, options); + } + + /// + /// Applies a radial vignette effect to an image. + /// + /// The pixel format. + /// The image this method extends. + /// The the x-radius. + /// The the y-radius. + /// The options effecting pixel blending. + /// The . + public static Image Vignette(this Image source, float radiusX, float radiusY, GraphicsOptions options) + where TPixel : struct, IPixel + { + return Vignette(source, NamedColors.Black, radiusX, radiusY, source.Bounds, options); + } + + /// + /// Applies a radial vignette effect to an image. + /// + /// The pixel format. + /// The image this method extends. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The options effecting pixel blending. + /// The . + public static Image Vignette(this Image source, Rectangle rectangle, GraphicsOptions options) + where TPixel : struct, IPixel + { + return Vignette(source, NamedColors.Black, 0, 0, rectangle, options); + } + + /// + /// Applies a radial vignette effect to an image. + /// + /// The pixel format. + /// The image this method extends. + /// The color to set as the vignette. + /// The the x-radius. + /// The the y-radius. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The options effecting pixel blending. + /// The . + public static Image Vignette(this Image source, TPixel color, float radiusX, float radiusY, Rectangle rectangle, GraphicsOptions options) + where TPixel : struct, IPixel + { + VignetteProcessor processor = new VignetteProcessor(color, options) { RadiusX = radiusX, RadiusY = radiusY }; source.ApplyProcessor(processor, rectangle); return source; } diff --git a/src/ImageSharp/Processing/Processors/ColorMatrix/LomographProcessor.cs b/src/ImageSharp/Processing/Processors/ColorMatrix/LomographProcessor.cs index 70b9979972..f6480c1837 100644 --- a/src/ImageSharp/Processing/Processors/ColorMatrix/LomographProcessor.cs +++ b/src/ImageSharp/Processing/Processors/ColorMatrix/LomographProcessor.cs @@ -18,6 +18,16 @@ namespace ImageSharp.Processing.Processors where TPixel : struct, IPixel { private static readonly TPixel VeryDarkGreen = ColorBuilder.FromRGBA(0, 10, 0, 255); + private GraphicsOptions options; + + /// + /// Initializes a new instance of the class. + /// + /// The options effecting blending and composition. + public LomographProcessor(GraphicsOptions options) + { + this.options = options; + } /// public override Matrix4x4 Matrix => new Matrix4x4() @@ -34,7 +44,7 @@ namespace ImageSharp.Processing.Processors /// protected override void AfterApply(ImageBase source, Rectangle sourceRectangle) { - new VignetteProcessor(VeryDarkGreen).Apply(source, sourceRectangle); + new VignetteProcessor(VeryDarkGreen, this.options).Apply(source, sourceRectangle); } } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/ColorMatrix/PolaroidProcessor.cs b/src/ImageSharp/Processing/Processors/ColorMatrix/PolaroidProcessor.cs index c06275314b..5df034add2 100644 --- a/src/ImageSharp/Processing/Processors/ColorMatrix/PolaroidProcessor.cs +++ b/src/ImageSharp/Processing/Processors/ColorMatrix/PolaroidProcessor.cs @@ -19,6 +19,16 @@ namespace ImageSharp.Processing.Processors { private static TPixel veryDarkOrange = ColorBuilder.FromRGB(102, 34, 0); private static TPixel lightOrange = ColorBuilder.FromRGBA(255, 153, 102, 178); + private GraphicsOptions options; + + /// + /// Initializes a new instance of the class. + /// + /// The options effecting blending and composition. + public PolaroidProcessor(GraphicsOptions options) + { + this.options = options; + } /// public override Matrix4x4 Matrix => new Matrix4x4() @@ -41,8 +51,8 @@ namespace ImageSharp.Processing.Processors /// protected override void AfterApply(ImageBase source, Rectangle sourceRectangle) { - new VignetteProcessor(veryDarkOrange).Apply(source, sourceRectangle); - new GlowProcessor(lightOrange, GraphicsOptions.Default) { Radius = source.Width / 4F }.Apply(source, sourceRectangle); + new VignetteProcessor(veryDarkOrange, this.options).Apply(source, sourceRectangle); + new GlowProcessor(lightOrange, this.options) { Radius = source.Width / 4F }.Apply(source, sourceRectangle); } } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Effects/BackgroundColorProcessor.cs b/src/ImageSharp/Processing/Processors/Effects/BackgroundColorProcessor.cs index 21973de3e4..511a810b27 100644 --- a/src/ImageSharp/Processing/Processors/Effects/BackgroundColorProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Effects/BackgroundColorProcessor.cs @@ -18,13 +18,17 @@ namespace ImageSharp.Processing.Processors internal class BackgroundColorProcessor : ImageProcessor where TPixel : struct, IPixel { + private readonly GraphicsOptions options; + /// /// Initializes a new instance of the class. /// /// The to set the background color to. - public BackgroundColorProcessor(TPixel color) + /// The options defining blending algorithum and amount. + public BackgroundColorProcessor(TPixel color, GraphicsOptions options) { this.Value = color; + this.options = options; } /// @@ -57,10 +61,19 @@ namespace ImageSharp.Processing.Processors startY = 0; } - Vector4 backgroundColor = this.Value.ToVector4(); + int width = maxX - minX; + using (Buffer colors = new Buffer(width)) + using (Buffer amount = new Buffer(width)) using (PixelAccessor sourcePixels = source.Lock()) { + for (int i = 0; i < width; i++) + { + colors[i] = this.Value; + amount[i] = this.options.BlendPercentage; + } + + PixelBlender blender = PixelOperations.Instance.GetPixelBlender(this.options.BlenderMode); Parallel.For( minY, maxY, @@ -68,26 +81,11 @@ namespace ImageSharp.Processing.Processors y => { int offsetY = y - startY; - for (int x = minX; x < maxX; x++) - { - int offsetX = x - startX; - Vector4 color = sourcePixels[offsetX, offsetY].ToVector4(); - float a = color.W; - - if (a < 1 && a > 0) - { - color = Vector4BlendTransforms.PremultipliedLerp(backgroundColor, color, .5F); - } - if (MathF.Abs(a) < Constants.Epsilon) - { - color = backgroundColor; - } + BufferSpan destination = sourcePixels.GetRowSpan(offsetY).Slice(minX - startX, width); - TPixel packed = default(TPixel); - packed.PackFromVector4(color); - sourcePixels[offsetX, offsetY] = packed; - } + // this switched color & destination in the 2nd and 3rd places because we are applying the target colour under the current one + blender.Blend(destination, colors, destination, amount); }); } } diff --git a/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs b/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs index 31e813564b..d698b543c4 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs @@ -18,13 +18,20 @@ namespace ImageSharp.Processing.Processors internal class VignetteProcessor : ImageProcessor where TPixel : struct, IPixel { + private readonly GraphicsOptions options; + private readonly PixelBlender blender; + /// /// Initializes a new instance of the class. /// /// The color of the vignette. - public VignetteProcessor(TPixel color) + /// The options effecting blending and composition. + public VignetteProcessor(TPixel color, GraphicsOptions options) { this.VignetteColor = color; + + this.options = options; + this.blender = PixelOperations.Instance.GetPixelBlender(this.options.BlenderMode); } /// @@ -72,23 +79,34 @@ namespace ImageSharp.Processing.Processors startY = 0; } + int width = maxX - minX; + using (Buffer rowColors = new Buffer(width)) using (PixelAccessor sourcePixels = source.Lock()) { + for (int i = 0; i < width; i++) + { + rowColors[i] = vignetteColor; + } + Parallel.For( minY, maxY, this.ParallelOptions, y => { - int offsetY = y - startY; - for (int x = minX; x < maxX; x++) + using (Buffer amounts = new Buffer(width)) { - int offsetX = x - startX; - float distance = Vector2.Distance(centre, new Vector2(offsetX, offsetY)); - Vector4 sourceColor = sourcePixels[offsetX, offsetY].ToVector4(); - TPixel packed = default(TPixel); - packed.PackFromVector4(Vector4BlendTransforms.PremultipliedLerp(sourceColor, vignetteColor.ToVector4(), .9F * (distance / maxDistance))); - sourcePixels[offsetX, offsetY] = packed; + int offsetY = y - startY; + int offsetX = minX - startX; + for (int i = 0; i < width; i++) + { + float distance = Vector2.Distance(centre, new Vector2(i + offsetX, offsetY)); + amounts[i] = (this.options.BlendPercentage * (.9F * (distance / maxDistance))).Clamp(0, 1); + } + + BufferSpan destination = sourcePixels.GetRowSpan(offsetY).Slice(offsetX, width); + + this.blender.Blend(destination, destination, rowColors, amounts); } }); } diff --git a/tests/ImageSharp.Benchmarks/Samplers/Glow.cs b/tests/ImageSharp.Benchmarks/Samplers/Glow.cs index 748bbf4fad..6daf120fac 100644 --- a/tests/ImageSharp.Benchmarks/Samplers/Glow.cs +++ b/tests/ImageSharp.Benchmarks/Samplers/Glow.cs @@ -122,12 +122,43 @@ namespace ImageSharp.Benchmarks float distance = Vector2.Distance(centre, new Vector2(offsetX, offsetY)); Vector4 sourceColor = sourcePixels[offsetX, offsetY].ToVector4(); TPixel packed = default(TPixel); - packed.PackFromVector4(Vector4BlendTransforms.PremultipliedLerp(sourceColor, glowColor.ToVector4(), 1 - (.95F * (distance / maxDistance)))); + packed.PackFromVector4(PremultipliedLerp(sourceColor, glowColor.ToVector4(), 1 - (.95F * (distance / maxDistance)))); sourcePixels[offsetX, offsetY] = packed; } }); } } + public static Vector4 PremultipliedLerp(Vector4 backdrop, Vector4 source, float amount) + { + amount = amount.Clamp(0, 1); + + // Santize on zero alpha + if (MathF.Abs(backdrop.W) < Constants.Epsilon) + { + source.W *= amount; + return source; + } + + if (MathF.Abs(source.W) < Constants.Epsilon) + { + return backdrop; + } + + // Premultiply the source vector. + // Oddly premultiplying the background vector creates dark outlines when pixels + // Have low alpha values. + source = new Vector4(source.X, source.Y, source.Z, 1) * (source.W * amount); + + // This should be implementing the following formula + // https://en.wikipedia.org/wiki/Alpha_compositing + // Vout = Vs + Vb (1 - Vsa) + // Aout = Vsa + Vsb (1 - Vsa) + Vector3 inverseW = new Vector3(1 - source.W); + Vector3 xyzB = new Vector3(backdrop.X, backdrop.Y, backdrop.Z); + Vector3 xyzS = new Vector3(source.X, source.Y, source.Z); + + return new Vector4(xyzS + (xyzB * inverseW), source.W + (backdrop.W * (1 - source.W))); + } } } } diff --git a/tests/ImageSharp.Tests/Colors/Rgba32TransformTests.cs b/tests/ImageSharp.Tests/Colors/Rgba32TransformTests.cs deleted file mode 100644 index 8d5e973b13..0000000000 --- a/tests/ImageSharp.Tests/Colors/Rgba32TransformTests.cs +++ /dev/null @@ -1,118 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests.Colors -{ - using ImageSharp.PixelFormats; - - using Xunit; - - /// - /// Tests the color transform algorithms. Test results match the output of CSS equivalents. - /// - /// - public class ColorTransformTests - { - /// - /// Orange backdrop - /// - private static readonly Rgba32 Backdrop = new Rgba32(204, 102, 0); - - /// - /// Blue source - /// - private static readonly Rgba32 Source = new Rgba32(0, 102, 153); - - [Fact] - public void Normal() - { - Rgba32 normal = Rgba32.Normal(Backdrop, Source); - Assert.True(normal == Source); - } - - [Fact] - public void Multiply() - { - Assert.True(Rgba32.Multiply(Backdrop, Rgba32.Black) == Rgba32.Black); - Assert.True(Rgba32.Multiply(Backdrop, Rgba32.White) == Backdrop); - - Rgba32 multiply = Rgba32.Multiply(Backdrop, Source); - Assert.True(multiply == new Rgba32(0, 41, 0)); - } - - [Fact] - public void Screen() - { - Assert.True(Rgba32.Screen(Backdrop, Rgba32.Black) == Backdrop); - Assert.True(Rgba32.Screen(Backdrop, Rgba32.White) == Rgba32.White); - - Rgba32 screen = Rgba32.Screen(Backdrop, Source); - Assert.True(screen == new Rgba32(204, 163, 153)); - } - - [Fact] - public void HardLight() - { - Rgba32 hardLight = Rgba32.HardLight(Backdrop, Source); - Assert.True(hardLight == new Rgba32(0, 82, 51)); - } - - [Fact] - public void Overlay() - { - Rgba32 overlay = Rgba32.Overlay(Backdrop, Source); - Assert.True(overlay == new Rgba32(153, 82, 0)); - } - - [Fact] - public void Darken() - { - Rgba32 darken = Rgba32.Darken(Backdrop, Source); - Assert.True(darken == new Rgba32(0, 102, 0)); - } - - [Fact] - public void Lighten() - { - Rgba32 lighten = Rgba32.Lighten(Backdrop, Source); - Assert.True(lighten == new Rgba32(204, 102, 153)); - } - - [Fact] - public void SoftLight() - { - Rgba32 softLight = Rgba32.SoftLight(Backdrop, Source); - Assert.True(softLight == new Rgba32(163, 90, 0)); - } - - [Fact] - public void ColorDodge() - { - Rgba32 colorDodge = Rgba32.ColorDodge(Backdrop, Source); - Assert.True(colorDodge == new Rgba32(204, 170, 0)); - } - - [Fact] - public void ColorBurn() - { - Rgba32 colorBurn = Rgba32.ColorBurn(Backdrop, Source); - Assert.True(colorBurn == new Rgba32(0, 0, 0)); - } - - [Fact] - public void Difference() - { - Rgba32 difference = Rgba32.Difference(Backdrop, Source); - Assert.True(difference == new Rgba32(204, 0, 153)); - } - - [Fact] - public void Exclusion() - { - Rgba32 exclusion = Rgba32.Exclusion(Backdrop, Source); - Assert.True(exclusion == new Rgba32(204, 122, 153)); - } - } -} diff --git a/tests/ImageSharp.Tests/Colors/RgbaVectorTransformTests.cs b/tests/ImageSharp.Tests/Colors/RgbaVectorTransformTests.cs deleted file mode 100644 index 81cbb63c41..0000000000 --- a/tests/ImageSharp.Tests/Colors/RgbaVectorTransformTests.cs +++ /dev/null @@ -1,120 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests.Colors -{ - using ImageSharp.PixelFormats; - using Xunit; - - /// - /// Tests the color transform algorithms. Test results match the output of CSS equivalents. - /// - /// - public class RgbaVectorTransformTests - { - private static readonly ApproximateFloatComparer FloatComparer = new ApproximateFloatComparer(0.01F); - - /// - /// Orange backdrop - /// - private static readonly RgbaVector Backdrop = new RgbaVector(204, 102, 0); - - /// - /// Blue source - /// - private static readonly RgbaVector Source = new RgbaVector(0, 102, 153); - - [Fact] - public void Normal() - { - RgbaVector normal = RgbaVector.Normal(Backdrop, Source); - Assert.True(normal == Source); - } - - // TODO: These tests keep sporadically breaking our builds. Fins out why they work locally but not on the CI. - // [Fact] - // public void Multiply() - // { - // Assert.Equal(RgbaVector.Multiply(Backdrop, RgbaVector.Black).ToVector4(), RgbaVector.Black.ToVector4(), FloatComparer); - // Assert.Equal(RgbaVector.Multiply(Backdrop, RgbaVector.White).ToVector4(), Backdrop.ToVector4(), FloatComparer); - - // RgbaVector multiply = RgbaVector.Multiply(Backdrop, Source); - // Assert.Equal(multiply.ToVector4(), new RgbaVector(0, 41, 0).ToVector4(), FloatComparer); - // } - - // [Fact] - // public void Screen() - // { - // Assert.Equal(RgbaVector.Screen(Backdrop, RgbaVector.Black).ToVector4(), Backdrop.ToVector4(), FloatComparer); - // Assert.Equal(RgbaVector.Screen(Backdrop, RgbaVector.White).ToVector4(), RgbaVector.White.ToVector4(), FloatComparer); - - // RgbaVector screen = RgbaVector.Screen(Backdrop, Source); - // Assert.Equal(screen.ToVector4(), new RgbaVector(204, 163, 153).ToVector4(), FloatComparer); - // } - - [Fact] - public void HardLight() - { - RgbaVector hardLight = RgbaVector.HardLight(Backdrop, Source); - Assert.Equal(hardLight.ToVector4(), new RgbaVector(0, 82, 51).ToVector4(), FloatComparer); - } - - [Fact] - public void Overlay() - { - RgbaVector overlay = RgbaVector.Overlay(Backdrop, Source); - Assert.Equal(overlay.ToVector4(), new RgbaVector(153, 82, 0).ToVector4(), FloatComparer); - } - - [Fact] - public void Darken() - { - RgbaVector darken = RgbaVector.Darken(Backdrop, Source); - Assert.Equal(darken.ToVector4(), new RgbaVector(0, 102, 0).ToVector4(), FloatComparer); - } - - [Fact] - public void Lighten() - { - RgbaVector lighten = RgbaVector.Lighten(Backdrop, Source); - Assert.Equal(lighten.ToVector4(), new RgbaVector(204, 102, 153).ToVector4(), FloatComparer); - } - - [Fact] - public void SoftLight() - { - RgbaVector softLight = RgbaVector.SoftLight(Backdrop, Source); - Assert.Equal(softLight.ToVector4(), new RgbaVector(163, 90, 0).ToVector4(), FloatComparer); - } - - [Fact] - public void ColorDodge() - { - RgbaVector colorDodge = RgbaVector.ColorDodge(Backdrop, Source); - Assert.Equal(colorDodge.ToVector4(), new RgbaVector(204, 170, 0).ToVector4(), FloatComparer); - } - - [Fact] - public void ColorBurn() - { - RgbaVector colorBurn = RgbaVector.ColorBurn(Backdrop, Source); - Assert.Equal(colorBurn.ToVector4(), new RgbaVector(0, 0, 0).ToVector4(), FloatComparer); - } - - [Fact] - public void Difference() - { - RgbaVector difference = RgbaVector.Difference(Backdrop, Source); - Assert.Equal(difference.ToVector4(), new RgbaVector(204, 0, 153).ToVector4(), FloatComparer); - } - - [Fact] - public void Exclusion() - { - RgbaVector exclusion = RgbaVector.Exclusion(Backdrop, Source); - Assert.Equal(exclusion.ToVector4(), new RgbaVector(204, 122, 153).ToVector4(), FloatComparer); - } - } -} diff --git a/tests/ImageSharp.Tests/Drawing/DrawImageEffectTest.cs b/tests/ImageSharp.Tests/Drawing/DrawImageEffectTest.cs deleted file mode 100644 index 885029fdff..0000000000 --- a/tests/ImageSharp.Tests/Drawing/DrawImageEffectTest.cs +++ /dev/null @@ -1,46 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests -{ - using System.IO; - using ImageSharp.Drawing; - using ImageSharp.PixelFormats; - using Xunit; - - public class DrawImageEffectTest : FileTestBase - { - [Fact] - public void ImageShouldApplyDrawImageFilter() - { - string path = this.CreateOutputDirectory("Drawing", "DrawImageEffect"); - - PixelBlenderMode[] modes = (PixelBlenderMode[])System.Enum.GetValues(typeof(PixelBlenderMode)); - - using (Image blend = TestFile.Create(TestImages.Png.Blur).CreateImage()) - { - foreach (TestFile file in Files) - { - using (Image image = file.CreateImage()) - { - foreach (PixelBlenderMode mode in modes) - { - using (FileStream output = File.OpenWrite($"{path}/{mode}.{file.FileName}")) - { - Size size = new Size(image.Width / 2, image.Height / 2); - Point loc = new Point(image.Width / 4, image.Height / 4); - - image.DrawImage(blend, size, loc, new GraphicsOptions() { - BlenderMode = mode, - BlendPercentage = .75f - }).Save(output); - } - } - } - } - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Drawing/DrawImageTest.cs b/tests/ImageSharp.Tests/Drawing/DrawImageTest.cs index f87a6170d9..030034a8f2 100644 --- a/tests/ImageSharp.Tests/Drawing/DrawImageTest.cs +++ b/tests/ImageSharp.Tests/Drawing/DrawImageTest.cs @@ -6,30 +6,42 @@ namespace ImageSharp.Tests { using System.IO; - + using System.Linq; + using ImageSharp.PixelFormats; using Xunit; public class DrawImageTest : FileTestBase { - [Fact] - public void ImageShouldApplyDrawImageFilter() - { - string path = this.CreateOutputDirectory("Drawing", "DrawImage"); + private const PixelTypes PixelTypes = Tests.PixelTypes.StandardImageClass; + + public static readonly string[] TestFiles = { + TestImages.Jpeg.Baseline.Calliphora, + TestImages.Bmp.Car, + TestImages.Png.Splash, + TestImages.Gif.Rings + }; - using (Image blend = TestFile.Create(TestImages.Bmp.Car).CreateImage()) + object[][] Modes = System.Enum.GetValues(typeof(PixelBlenderMode)).Cast().Select(x => new object[] { x }).ToArray(); + + [Theory] + [WithFileCollection(nameof(TestFiles), PixelTypes, PixelBlenderMode.Normal)] + [WithFileCollection(nameof(TestFiles), PixelTypes, PixelBlenderMode.Multiply)] + [WithFileCollection(nameof(TestFiles), PixelTypes, PixelBlenderMode.Add)] + [WithFileCollection(nameof(TestFiles), PixelTypes, PixelBlenderMode.Substract)] + [WithFileCollection(nameof(TestFiles), PixelTypes, PixelBlenderMode.Screen)] + [WithFileCollection(nameof(TestFiles), PixelTypes, PixelBlenderMode.Darken)] + [WithFileCollection(nameof(TestFiles), PixelTypes, PixelBlenderMode.Lighten)] + [WithFileCollection(nameof(TestFiles), PixelTypes, PixelBlenderMode.Overlay)] + [WithFileCollection(nameof(TestFiles), PixelTypes, PixelBlenderMode.HardLight)] + public void ImageShouldApplyDrawImage(TestImageProvider provider, PixelBlenderMode mode) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + using (Image blend = Image.Load(TestFile.Create(TestImages.Bmp.Car).Bytes)) { - foreach (TestFile file in Files) - { - using (Image image = file.CreateImage()) - { - using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) - { - image.DrawImage(blend, .75f, new Size(image.Width / 2, image.Height / 2), new Point(image.Width / 4, image.Height / 4)) - .Save(output); - } - } - } + image.DrawImage(blend, mode, .75f, new Size(image.Width / 2, image.Height / 2), new Point(image.Width / 4, image.Height / 4)) + .DebugSave(provider, new { mode }); } } } -} \ No newline at end of file +}