From cce2265f7ec1560ed5567d549f634f63e8ebbf4d Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 30 Jul 2016 10:53:14 +1000 Subject: [PATCH] Blend Former-commit-id: ff1aec34e42b49f728dfd7f370f709c48be34762 Former-commit-id: ee75403396e61e34b0d97760ee36634973d5a5ec Former-commit-id: 326da67dbfdb92272ab63c08e30a9e85904f7e60 --- src/ImageProcessorCore/Filters/Blend.cs | 62 +++++++++++++ .../Filters/Processors/BlendProcessor.cs | 93 +++++++++++++++++++ .../Processors/Filters/BlendTest.cs | 44 +++++++++ 3 files changed, 199 insertions(+) create mode 100644 src/ImageProcessorCore/Filters/Blend.cs create mode 100644 src/ImageProcessorCore/Filters/Processors/BlendProcessor.cs create mode 100644 tests/ImageProcessorCore.Tests/Processors/Filters/BlendTest.cs 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