From 7f473cb0cd75fcb1cf6bb195bd14d9c9dc987e2e Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 7 Dec 2016 01:26:33 +1100 Subject: [PATCH] Enhance Blend to use point and Size Fix #14 --- Settings.StyleCop | 1 + .../Colors/Vector4BlendTransforms.cs | 22 +++++ src/ImageSharp/Filters/Overlays/Blend.cs | 23 +++-- .../Processors/Overlays/BlendProcessor.cs | 84 +++++++++---------- src/ImageSharp/Numerics/Rectangle.cs | 5 ++ .../Processors/Filters/BlendTest.cs | 9 +- 6 files changed, 90 insertions(+), 54 deletions(-) diff --git a/Settings.StyleCop b/Settings.StyleCop index b3cd5ee7e..dbff3379a 100644 --- a/Settings.StyleCop +++ b/Settings.StyleCop @@ -35,6 +35,7 @@ Paeth th desensitivity + premultiplied diff --git a/src/ImageSharp/Colors/Vector4BlendTransforms.cs b/src/ImageSharp/Colors/Vector4BlendTransforms.cs index 4851baedf..ca8ac55c0 100644 --- a/src/ImageSharp/Colors/Vector4BlendTransforms.cs +++ b/src/ImageSharp/Colors/Vector4BlendTransforms.cs @@ -185,6 +185,28 @@ namespace ImageSharp 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 by their W component 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); + 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); + } + /// /// Multiplies or screens the color component, depending on the component value. /// diff --git a/src/ImageSharp/Filters/Overlays/Blend.cs b/src/ImageSharp/Filters/Overlays/Blend.cs index 448fb7aa0..4a1fc534d 100644 --- a/src/ImageSharp/Filters/Overlays/Blend.cs +++ b/src/ImageSharp/Filters/Overlays/Blend.cs @@ -21,11 +21,11 @@ namespace ImageSharp /// The image to blend with the currently processing image. /// The opacity of the image image to blend. Must be between 0 and 100. /// The . - public static Image Blend(this Image source, ImageBase image, int percent = 50) + public static Image Blend(this Image source, Image image, int percent = 50) where TColor : struct, IPackedPixel where TPacked : struct { - return Blend(source, image, percent, source.Bounds); + return Blend(source, image, percent, default(Size), default(Point)); } /// @@ -36,15 +36,24 @@ namespace ImageSharp /// The pixel format. /// The packed format. uint, long, float. /// The opacity of the image image to blend. Must be between 0 and 100. - /// - /// The structure that specifies the portion of the image object to alter. - /// + /// The size to draw the blended image. + /// The location to draw the blended image. /// The . - public static Image Blend(this Image source, ImageBase image, int percent, Rectangle rectangle) + public static Image Blend(this Image source, Image image, int percent, Size size, Point location) where TColor : struct, IPackedPixel where TPacked : struct { - return source.Process(rectangle, new BlendProcessor(image, percent)); + if (size == default(Size)) + { + size = new Size(image.Width, image.Height); + } + + if (location == default(Point)) + { + location = Point.Empty; + } + + return source.Process(source.Bounds, new BlendProcessor(image, size, location, percent)); } } } \ No newline at end of file diff --git a/src/ImageSharp/Filters/Processors/Overlays/BlendProcessor.cs b/src/ImageSharp/Filters/Processors/Overlays/BlendProcessor.cs index 84e49f596..3592c7921 100644 --- a/src/ImageSharp/Filters/Processors/Overlays/BlendProcessor.cs +++ b/src/ImageSharp/Filters/Processors/Overlays/BlendProcessor.cs @@ -18,58 +18,60 @@ namespace ImageSharp.Processors where TColor : struct, IPackedPixel where TPacked : struct { - /// - /// The image to blend. - /// - private readonly ImageBase blend; - /// /// Initializes a new instance of the class. /// - /// - /// The image to blend with the currently processing image. - /// Disposal of this image is the responsibility of the developer. - /// + /// The image to blend with the currently processing image. + /// The size to draw the blended image. + /// The location to draw the blended image. /// The opacity of the image to blend. Between 0 and 100. - public BlendProcessor(ImageBase image, int alpha = 100) + public BlendProcessor(Image image, Size size, Point location, int alpha = 100) { Guard.MustBeBetweenOrEqualTo(alpha, 0, 100, nameof(alpha)); - this.blend = image; - this.Value = alpha; + this.Image = image; + this.Size = size; + this.Alpha = alpha; + this.Location = location; } + /// + /// Gets the image to blend. + /// + public Image Image { get; private set; } + /// /// Gets the alpha percentage value. /// - public int Value { get; } + public int Alpha { get; } + + /// + /// Gets the size to draw the blended image. + /// + public Size Size { get; } + + /// + /// Gets the location to draw the blended image. + /// + public Point Location { get; } /// protected override void Apply(ImageBase source, Rectangle sourceRectangle, int startY, int endY) { - int startX = sourceRectangle.X; - int endX = sourceRectangle.Right; - Rectangle bounds = this.blend.Bounds; - - // Align start/end positions. - int minX = Math.Max(0, startX); - int maxX = Math.Min(source.Width, endX); - int minY = Math.Max(0, startY); - int maxY = Math.Min(source.Height, endY); - - // Reset offset if necessary. - if (minX > 0) + if (this.Image.Bounds.Size != this.Size) { - startX = 0; + this.Image = this.Image.Resize(this.Size.Width, this.Size.Height); } - if (minY > 0) - { - startY = 0; - } + // Align start/end positions. + 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); + int minY = Math.Max(this.Location.Y, startY); + int maxY = Math.Min(this.Location.Y + bounds.Height, endY); - float alpha = this.Value / 100F; + float alpha = this.Alpha / 100F; - using (PixelAccessor toBlendPixels = this.blend.Lock()) + using (PixelAccessor toBlendPixels = this.Image.Lock()) using (PixelAccessor sourcePixels = source.Lock()) { Parallel.For( @@ -78,26 +80,20 @@ namespace ImageSharp.Processors this.ParallelOptions, y => { - int offsetY = y - startY; for (int x = minX; x < maxX; x++) { - int offsetX = x - startX; - Vector4 color = sourcePixels[offsetX, offsetY].ToVector4(); + Vector4 color = sourcePixels[x, y].ToVector4(); + Vector4 blendedColor = toBlendPixels[x - minX, y - minY].ToVector4(); - if (bounds.Contains(offsetX, offsetY)) + if (blendedColor.W > 0) { - Vector4 blendedColor = toBlendPixels[offsetX, offsetY].ToVector4(); - - if (blendedColor.W > 0) - { - // Lerping colors is dependent on the alpha of the blended color - color = Vector4.Lerp(color, blendedColor, alpha > 0 ? alpha : blendedColor.W); - } + // Lerping colors is dependent on the alpha of the blended color + color = Vector4BlendTransforms.PremultipliedLerp(color, blendedColor, alpha); } TColor packed = default(TColor); packed.PackFromVector4(color); - sourcePixels[offsetX, offsetY] = packed; + sourcePixels[x, y] = packed; } }); } diff --git a/src/ImageSharp/Numerics/Rectangle.cs b/src/ImageSharp/Numerics/Rectangle.cs index be457fc5a..3f6267a46 100644 --- a/src/ImageSharp/Numerics/Rectangle.cs +++ b/src/ImageSharp/Numerics/Rectangle.cs @@ -127,6 +127,11 @@ namespace ImageSharp } } + /// + /// Gets the size of this . + /// + public Size Size => new Size(this.Width, this.Height); + /// /// Gets a value indicating whether this is empty. /// diff --git a/tests/ImageSharp.Tests/Processors/Filters/BlendTest.cs b/tests/ImageSharp.Tests/Processors/Filters/BlendTest.cs index 653524e20..8c3bf932f 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/BlendTest.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/BlendTest.cs @@ -6,6 +6,7 @@ namespace ImageSharp.Tests { using System.IO; + using System.Linq; using Xunit; @@ -16,7 +17,9 @@ namespace ImageSharp.Tests { string path = CreateOutputDirectory("Blend"); - Image blend; + Image blend;// = new Image(400, 400); + // blend.BackgroundColor(Color.RebeccaPurple); + using (FileStream stream = File.OpenRead(TestImages.Bmp.Car)) { blend = new Image(stream); @@ -28,8 +31,8 @@ namespace ImageSharp.Tests using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) { - image.Blend(blend) - .Save(output); + image.Blend(blend, 75, new Size(image.Width / 2, image.Height / 2), new Point(image.Width / 4, image.Height / 4)) + .Save(output); } } }