diff --git a/src/ImageSharp/Colors/Vector4BlendTransforms.cs b/src/ImageSharp/Colors/Vector4BlendTransforms.cs index ca8ac55c02..b1d6ebb8c9 100644 --- a/src/ImageSharp/Colors/Vector4BlendTransforms.cs +++ b/src/ImageSharp/Colors/Vector4BlendTransforms.cs @@ -17,7 +17,7 @@ namespace ImageSharp /// /// The epsilon for comparing floating point numbers. /// - private const float Epsilon = 0.001F; + private const float Epsilon = 0.0001F; /// /// The blending formula simply selects the source vector. @@ -187,7 +187,7 @@ namespace ImageSharp /// /// Linearly interpolates from one vector to another based on the given weighting. - /// The two vectors are premultiplied by their W component before operating. + /// The two vectors are premultiplied before operating. /// /// The backdrop vector. /// The source vector. @@ -195,20 +195,43 @@ namespace ImageSharp /// 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); - backdrop = backdrop * new Vector4(backdrop.X, backdrop.Y, backdrop.Z, 1) * backdrop.W; - source = source * new Vector4(source.X, source.Y, source.Z, 1) * source.W; - return Vector4.Lerp(backdrop, source, amount) / new Vector4(source.W, source.W, source.W, 1); + // Santize on zero alpha + if (Math.Abs(backdrop.W) < Epsilon) + { + source.W *= amount; + return source; + } + + if (Math.Abs(source.W) < 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 color component, depending on the component value. + /// Multiplies or screens the backdrop component, depending on the component value. /// /// The backdrop component. /// The source component. diff --git a/src/ImageSharp/Filters/Processors/Effects/BackgroundColorProcessor.cs b/src/ImageSharp/Filters/Processors/Effects/BackgroundColorProcessor.cs index ab077d6987..cc81179f80 100644 --- a/src/ImageSharp/Filters/Processors/Effects/BackgroundColorProcessor.cs +++ b/src/ImageSharp/Filters/Processors/Effects/BackgroundColorProcessor.cs @@ -79,7 +79,7 @@ namespace ImageSharp.Processors if (a < 1 && a > 0) { - color = Vector4.Lerp(color, backgroundColor, .5F); + color = Vector4BlendTransforms.PremultipliedLerp(backgroundColor, color, .5F); } if (Math.Abs(a) < Epsilon) diff --git a/src/ImageSharp/Filters/Processors/Overlays/BlendProcessor.cs b/src/ImageSharp/Filters/Processors/Overlays/BlendProcessor.cs index 3592c7921e..e764879763 100644 --- a/src/ImageSharp/Filters/Processors/Overlays/BlendProcessor.cs +++ b/src/ImageSharp/Filters/Processors/Overlays/BlendProcessor.cs @@ -82,17 +82,14 @@ namespace ImageSharp.Processors { for (int x = minX; x < maxX; x++) { - Vector4 color = sourcePixels[x, y].ToVector4(); - Vector4 blendedColor = toBlendPixels[x - minX, y - minY].ToVector4(); + Vector4 backgroundVector = sourcePixels[x, y].ToVector4(); + Vector4 sourceVector = toBlendPixels[x - minX, y - minY].ToVector4(); - if (blendedColor.W > 0) - { - // Lerping colors is dependent on the alpha of the blended color - color = Vector4BlendTransforms.PremultipliedLerp(color, blendedColor, alpha); - } + // Lerping colors is dependent on the alpha of the blended color + backgroundVector = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, alpha); TColor packed = default(TColor); - packed.PackFromVector4(color); + packed.PackFromVector4(backgroundVector); sourcePixels[x, y] = packed; } }); diff --git a/src/ImageSharp/Filters/Processors/Overlays/GlowProcessor.cs b/src/ImageSharp/Filters/Processors/Overlays/GlowProcessor.cs index 496a3297d0..6b1fe40c2c 100644 --- a/src/ImageSharp/Filters/Processors/Overlays/GlowProcessor.cs +++ b/src/ImageSharp/Filters/Processors/Overlays/GlowProcessor.cs @@ -77,15 +77,11 @@ namespace ImageSharp.Processors for (int x = minX; x < maxX; x++) { int offsetX = x - startX; - if (ellipse.Contains(offsetX, offsetY)) - { - // TODO: Premultiply? - float distance = Vector2.Distance(centre, new Vector2(offsetX, offsetY)); - Vector4 sourceColor = sourcePixels[offsetX, offsetY].ToVector4(); - TColor packed = default(TColor); - packed.PackFromVector4(Vector4.Lerp(glowColor.ToVector4(), sourceColor, distance / maxDistance)); - sourcePixels[offsetX, offsetY] = packed; - } + float distance = Vector2.Distance(centre, new Vector2(offsetX, offsetY)); + Vector4 sourceColor = sourcePixels[offsetX, offsetY].ToVector4(); + TColor packed = default(TColor); + packed.PackFromVector4(Vector4BlendTransforms.PremultipliedLerp(sourceColor, glowColor.ToVector4(), 1 - (.95F * (distance / maxDistance)))); + sourcePixels[offsetX, offsetY] = packed; } }); } diff --git a/src/ImageSharp/Filters/Processors/Overlays/VignetteProcessor.cs b/src/ImageSharp/Filters/Processors/Overlays/VignetteProcessor.cs index 85c4af51a7..cdc8019140 100644 --- a/src/ImageSharp/Filters/Processors/Overlays/VignetteProcessor.cs +++ b/src/ImageSharp/Filters/Processors/Overlays/VignetteProcessor.cs @@ -86,7 +86,7 @@ namespace ImageSharp.Processors float distance = Vector2.Distance(centre, new Vector2(offsetX, offsetY)); Vector4 sourceColor = sourcePixels[offsetX, offsetY].ToVector4(); TColor packed = default(TColor); - packed.PackFromVector4(Vector4.Lerp(vignetteColor.ToVector4(), sourceColor, 1 - (.9F * (distance / maxDistance)))); + packed.PackFromVector4(Vector4BlendTransforms.PremultipliedLerp(sourceColor, vignetteColor.ToVector4(), .9F * (distance / maxDistance))); sourcePixels[offsetX, offsetY] = packed; } });