diff --git a/src/ImageProcessorCore/Filters/Blend.cs b/src/ImageProcessorCore/Filters/Blend.cs new file mode 100644 index 000000000..96f8f60cd --- /dev/null +++ b/src/ImageProcessorCore/Filters/Blend.cs @@ -0,0 +1,62 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// ------------------------------------------------------------------------------------------------------------------- + +namespace ImageProcessorCore +{ + using Processors; + + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// Combines the given image together with the current one by blending their pixels. + /// + /// The image this method extends. + /// The image to blend with the currently processing image. + /// The pixel format. + /// The packed format. long, float. + /// The opacity of the image image to blend. Must be between 0 and 100. + /// A delegate which is called as progress is made processing the image. + /// The . + public static Image Blend(this Image source, ImageBase image, int percent = 50, ProgressEventHandler progressHandler = null) + where T : IPackedVector + where TP : struct + { + return Blend(source, image, percent, source.Bounds, progressHandler); + } + + /// + /// Combines the given image together with the current one by blending their pixels. + /// + /// The image this method extends. + /// The image to blend with the currently processing image. + /// The pixel format. + /// The packed format. 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. + /// + /// A delegate which is called as progress is made processing the image. + /// The . + public static Image Blend(this Image source, ImageBase image, int percent, Rectangle rectangle, ProgressEventHandler progressHandler = null) + where T : IPackedVector + where TP : struct + { + BlendProcessor processor = new BlendProcessor(image, percent); + processor.OnProgress += progressHandler; + + try + { + return source.Process(rectangle, processor); + } + finally + { + processor.OnProgress -= progressHandler; + } + } + } +} diff --git a/src/ImageProcessorCore/Filters/Processors/BlendProcessor.cs b/src/ImageProcessorCore/Filters/Processors/BlendProcessor.cs new file mode 100644 index 000000000..56c754729 --- /dev/null +++ b/src/ImageProcessorCore/Filters/Processors/BlendProcessor.cs @@ -0,0 +1,93 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Processors +{ + using System.Numerics; + using System.Threading.Tasks; + + /// + /// Combines two images together by blending the pixels. + /// + /// The pixel format. + /// The packed format. long, float. + public class BlendProcessor : ImageProcessor + where T : IPackedVector + where TP : 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 opacity of the image to blend. Between 0 and 100. + public BlendProcessor(ImageBase image, int alpha = 100) + { + Guard.MustBeBetweenOrEqualTo(alpha, 0, 100, nameof(alpha)); + this.blend = image; + this.Value = alpha; + } + + /// + /// Gets the alpha percentage value. + /// + public int Value { get; } + + /// + protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) + { + int sourceY = sourceRectangle.Y; + int sourceBottom = sourceRectangle.Bottom; + int startX = sourceRectangle.X; + int endX = sourceRectangle.Right; + Rectangle bounds = this.blend.Bounds; + float alpha = this.Value / 100f; + + using (IPixelAccessor toBlendPixels = this.blend.Lock()) + using (IPixelAccessor sourcePixels = source.Lock()) + using (IPixelAccessor targetPixels = target.Lock()) + { + Parallel.For( + startY, + endY, + y => + { + if (y >= sourceY && y < sourceBottom) + { + for (int x = startX; x < endX; x++) + { + Vector4 color = sourcePixels[x, y].ToVector4(); + + if (bounds.Contains(x, y)) + { + Vector4 blendedColor = toBlendPixels[x, y].ToVector4(); + + if (blendedColor.W > 0) + { + // Lerping colors is dependent on the alpha of the blended color + float alphaFactor = alpha > 0 ? alpha : blendedColor.W; + color = Vector4.Lerp(color, blendedColor, alphaFactor); + } + } + + T packed = default(T); + packed.PackVector(color); + targetPixels[x, y] = packed; + } + + this.OnRowProcessed(); + } + }); + } + } + } +} diff --git a/tests/ImageProcessorCore.Tests/Processors/Filters/BlendTest.cs b/tests/ImageProcessorCore.Tests/Processors/Filters/BlendTest.cs new file mode 100644 index 000000000..2aa6ce044 --- /dev/null +++ b/tests/ImageProcessorCore.Tests/Processors/Filters/BlendTest.cs @@ -0,0 +1,44 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Tests +{ + using System.IO; + + using Xunit; + + public class BlendTest : FileTestBase + { + [Fact] + public void ImageShouldApplyBlendFilter() + { + const string path = "TestOutput/Blend"; + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path); + } + + Image blend; + using (FileStream stream = File.OpenRead("TestImages/Formats/Bmp/Car.bmp")) + { + blend = new Image(stream); + } + + foreach (string file in Files) + { + using (FileStream stream = File.OpenRead(file)) + { + string filename = Path.GetFileName(file); + Image image = new Image(stream); + using (FileStream output = File.OpenWrite($"{path}/{filename}")) + { + image.Blend(blend) + .Save(output); + } + } + } + } + } +} \ No newline at end of file