diff --git a/src/ImageSharp.Drawing/DrawImage.cs b/src/ImageSharp.Drawing/DrawImage.cs
index 6a4f49337c..52c275595b 100644
--- a/src/ImageSharp.Drawing/DrawImage.cs
+++ b/src/ImageSharp.Drawing/DrawImage.cs
@@ -1,57 +1,105 @@
-//
-// Copyright (c) James Jackson-South and contributors.
-// Licensed under the Apache License, Version 2.0.
-//
-
-namespace ImageSharp
-{
- using Drawing.Processors;
- using ImageSharp.PixelFormats;
-
- ///
- /// Extension methods for the type.
- ///
- public static partial class ImageExtensions
- {
- ///
- /// Draws the given image together with the current one by blending their pixels.
- ///
- /// The pixel format.
- /// The image this method extends.
- /// 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, Image image, int percent = 50)
- where TPixel : struct, IPixel
- {
- return DrawImage(source, image, percent, default(Size), default(Point));
- }
-
- ///
- /// Draws 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 opacity of the image image to blend. Must be between 0 and 100.
- /// The size to draw the blended image.
- /// The location to draw the blended image.
- /// The .
- public static Image DrawImage(this Image source, Image image, int percent, Size size, Point location)
- where TPixel : struct, IPixel
- {
- if (size == default(Size))
- {
- size = new Size(image.Width, image.Height);
- }
-
- if (location == default(Point))
- {
- location = Point.Empty;
- }
-
- source.ApplyProcessor(new DrawImageProcessor(image, size, location, percent), source.Bounds);
- return source;
- }
- }
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+ using Drawing.Processors;
+ using ImageSharp.PixelFormats;
+
+ ///
+ /// Extension methods for the type.
+ ///
+ public static partial class ImageExtensions
+ {
+ ///
+ /// Draws the given image together with the current one by blending their pixels.
+ ///
+ /// The pixel format.
+ /// The image this method extends.
+ /// 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, Image image, int percent = 50)
+ where TPixel : struct, IPixel
+ {
+ return DrawImage(source, image, percent, default(Size), default(Point));
+ }
+
+ ///
+ /// Draws 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 opacity of the image image to blend. Must be between 0 and 100.
+ /// The size to draw the blended image.
+ /// The location to draw the blended image.
+ /// The .
+ public static Image DrawImage(this Image source, Image image, int percent, Size size, Point location)
+ where TPixel : struct, IPixel
+ {
+ if (size == default(Size))
+ {
+ size = new Size(image.Width, image.Height);
+ }
+
+ if (location == default(Point))
+ {
+ location = Point.Empty;
+ }
+
+ source.ApplyProcessor(new DrawImageProcessor(image, size, location, percent), source.Bounds);
+ return source;
+ }
+
+ ///
+ /// Draws 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.
+ /// Pixel function effect to apply on every pixel
+ /// The pixel format.
+ /// The opacity of the image image to blend. Must be between 0 and 100.
+ /// The size to draw the blended image.
+ /// The location to draw the blended image.
+ /// The .
+ public static Image DrawImage(this Image source, Image image, PixelTransformMode mode, int percent, Size size, Point location)
+ where TPixel : struct, IPixel
+ {
+ Func pixelFunc = mode.GetPixelFunction();
+
+ return DrawImage(source, image, pixelFunc, percent, size, location);
+ }
+
+ ///
+ /// Draws 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.
+ /// Pixel function effect to apply on every pixel
+ /// The pixel format.
+ /// The opacity of the image image to blend. Must be between 0 and 100.
+ /// The size to draw the blended image.
+ /// The location to draw the blended image.
+ /// The .
+ public static Image DrawImage(this Image source, Image image, Func pixelFunc, int percent, Size size, Point location)
+ where TPixel : struct, IPixel
+ {
+ if (size == default(Size))
+ {
+ size = new Size(image.Width, image.Height);
+ }
+
+ if (location == default(Point))
+ {
+ location = Point.Empty;
+ }
+
+ source.ApplyProcessor(new DrawImageEffectProcessor(image, size, location, pixelFunc, percent), source.Bounds);
+ return source;
+ }
+ }
}
\ No newline at end of file
diff --git a/src/ImageSharp.Drawing/Processors/DrawImageEffectProcessor.cs b/src/ImageSharp.Drawing/Processors/DrawImageEffectProcessor.cs
new file mode 100644
index 0000000000..4ef8800f92
--- /dev/null
+++ b/src/ImageSharp.Drawing/Processors/DrawImageEffectProcessor.cs
@@ -0,0 +1,102 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Drawing.Processors
+{
+ using System;
+ using System.Numerics;
+ using System.Threading.Tasks;
+ using ImageSharp.PixelFormats;
+ using ImageSharp.Processing;
+
+ ///
+ /// Combines two images together by blending the pixels.
+ ///
+ /// The pixel format.
+ internal class DrawImageEffectProcessor : ImageProcessor
+ where TPixel : struct, IPixel
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The image to blend with the currently processing image.
+ /// The size to draw the blended image.
+ /// The location to draw the blended image.
+ /// Pixel function effect to apply on every pixel
+ /// The opacity of the image to blend. Between 0 and 100.
+ public DrawImageEffectProcessor(Image image, Size size, Point location, Func pixelFunction, int alpha = 100)
+ {
+ Guard.MustBeBetweenOrEqualTo(alpha, 0, 100, nameof(alpha));
+ this.Image = image;
+ this.PixelFunction = pixelFunction;
+ this.Size = size;
+ this.Location = location;
+ this.Alpha = alpha;
+ }
+
+ ///
+ /// Gets the image to blend.
+ ///
+ public Image Image { get; private set; }
+
+ ///
+ /// Gets The function effect to apply on a per pixel basis
+ ///
+ public Func PixelFunction { get; private set; }
+
+ ///
+ /// Gets the alpha percentage value.
+ ///
+ 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 OnApply(ImageBase target, Rectangle sourceRectangle)
+ {
+ if (this.Image.Bounds.Size != this.Size)
+ {
+ // should Resize be moved to core?
+ this.Image = this.Image.Resize(this.Size.Width, this.Size.Height);
+ }
+
+ // 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, sourceRectangle.Y);
+ int maxY = Math.Min(this.Location.Y + bounds.Height, sourceRectangle.Bottom);
+
+ float alpha = this.Alpha / 100F;
+
+ using (PixelAccessor sourcePixels = this.Image.Lock())
+ using (PixelAccessor targetPixels = target.Lock())
+ {
+ Parallel.For(
+ minY,
+ maxY,
+ this.ParallelOptions,
+ y =>
+ {
+ for (int x = minX; x < maxX; x++)
+ {
+ TPixel targetColor = targetPixels[x, y];
+ TPixel sourceColor = sourcePixels[x - minX, y - minY];
+
+ targetPixels[x, y] = this.PixelFunction(targetColor, sourceColor, alpha);
+ }
+ });
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/PixelFormats/PixelTransformMode.cs b/src/ImageSharp/PixelFormats/PixelTransformMode.cs
new file mode 100644
index 0000000000..159b275158
--- /dev/null
+++ b/src/ImageSharp/PixelFormats/PixelTransformMode.cs
@@ -0,0 +1,58 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.PixelFormats
+{
+ ///
+ /// Porter Duff Blending composition modes
+ ///
+ public enum PixelTransformMode
+ {
+ ///
+ /// Default blending mode, also known as "Normal" or "Alpha Blending"
+ ///
+ Normal,
+
+ ///
+ /// Backdrop + Source
+ ///
+ Multiply,
+
+ ///
+ /// Backdrop + Source
+ ///
+ Add,
+
+ ///
+ /// Backdrop - Source
+ ///
+ Substract,
+
+ ///
+ /// Screen effect
+ ///
+ Screen,
+
+ ///
+ /// Darken effect
+ ///
+ Darken,
+
+ ///
+ /// Lighten effect
+ ///
+ Lighten,
+
+ ///
+ /// Overlay effect
+ ///
+ Overlay,
+
+ ///
+ /// Hard light effect
+ ///
+ HardLight
+ }
+}
diff --git a/src/ImageSharp/PixelFormats/PixelTransformModeExtensions.cs b/src/ImageSharp/PixelFormats/PixelTransformModeExtensions.cs
new file mode 100644
index 0000000000..4ecddfded0
--- /dev/null
+++ b/src/ImageSharp/PixelFormats/PixelTransformModeExtensions.cs
@@ -0,0 +1,57 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Text;
+ using PixelFormats;
+
+ ///
+ /// Extensions to retrieve the appropiate pixel transformation functions for
+ ///
+ public static class PixelTransformModeExtensions
+ {
+ ///
+ /// Gets a pixel transformation function
+ ///
+ /// The pixel format used for both Backdrop and Source
+ /// The Duff Porter mode
+ /// A function that transforms a Backdrop and Source colors into a final color
+ public static Func GetPixelFunction(this PixelTransformMode mode)
+ where TPixel : IPixel
+ {
+ return mode.GetPixelFunction();
+ }
+
+ ///
+ /// Gets a pixel transformation function
+ ///
+ /// The pixel format used for Backdrop and Output
+ /// The pixel format used for Source
+ /// The Duff Porter mode
+ /// A function that transforms a Backdrop and Source colors into a final color
+ public static Func GetPixelFunction(this PixelTransformMode mode)
+ where TBckPixel : IPixel
+ where TSrcPixel : IPixel
+ {
+ switch (mode)
+ {
+ case PixelTransformMode.Normal: return PorterDuffFunctions.NormalBlendFunction;
+ case PixelTransformMode.Multiply: return PorterDuffFunctions.MultiplyFunction;
+ case PixelTransformMode.Add: return PorterDuffFunctions.AddFunction;
+ case PixelTransformMode.Substract: return PorterDuffFunctions.SubstractFunction;
+ case PixelTransformMode.Screen: return PorterDuffFunctions.ScreenFunction;
+ case PixelTransformMode.Darken: return PorterDuffFunctions.DarkenFunction;
+ case PixelTransformMode.Lighten: return PorterDuffFunctions.LightenFunction;
+ case PixelTransformMode.Overlay: return PorterDuffFunctions.OverlayFunction;
+ case PixelTransformMode.HardLight: return PorterDuffFunctions.HardLightFunction;
+
+ default: throw new NotImplementedException(nameof(mode));
+ }
+ }
+ }
+}
diff --git a/src/ImageSharp/PixelFormats/PorterDuffFunctions.cs b/src/ImageSharp/PixelFormats/PorterDuffFunctions.cs
new file mode 100644
index 0000000000..1c9f6b8425
--- /dev/null
+++ b/src/ImageSharp/PixelFormats/PorterDuffFunctions.cs
@@ -0,0 +1,267 @@
+//
+// 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;
+
+ ///
+ /// Collection of Porter Duff alpha blending functions
+ ///
+ /// Backdrop Pixel Format
+ /// Source Pixel Format
+ ///
+ /// These functions are designed to be a general solution for all color cases,
+ /// that is, they take in account the alpha value of both the backdrop
+ /// and source, and there's no need to alpha-premultiply neither the backdrop
+ /// nor the source.
+ /// Note there are faster functions for when the backdrop color is known
+ /// to be opaque
+ ///
+ internal static class PorterDuffFunctions
+ where TBckPixel : IPixel
+ where TsrcPixel : IPixel
+ {
+ ///
+ /// Source over backdrop
+ ///
+ /// Backgrop color
+ /// Source color
+ /// Opacity applied to Source Alpha
+ /// Output color
+ public static TBckPixel NormalBlendFunction(TBckPixel backdrop, TsrcPixel source, float opacity)
+ {
+ Vector4 l = source.ToVector4();
+ l.W *= opacity;
+ if (l.W == 0)
+ {
+ return backdrop;
+ }
+
+ Vector4 b = backdrop.ToVector4();
+
+ return Compose(b, l, l);
+ }
+
+ ///
+ /// Source multiplied by backdrop
+ ///
+ /// Backgrop color
+ /// Source color
+ /// Opacity applied to Source Alpha
+ /// Output color
+ public static TBckPixel MultiplyFunction(TBckPixel backdrop, TsrcPixel source, float opacity)
+ {
+ Vector4 l = source.ToVector4();
+ l.W *= opacity;
+ if (l.W == 0)
+ {
+ return backdrop;
+ }
+
+ Vector4 b = backdrop.ToVector4();
+
+ return Compose(b, l, b * l);
+ }
+
+ ///
+ /// Source added to backdrop
+ ///
+ /// Backgrop color
+ /// Source color
+ /// Opacity applied to Source Alpha
+ /// Output color
+ public static TBckPixel AddFunction(TBckPixel backdrop, TsrcPixel source, float opacity)
+ {
+ Vector4 l = source.ToVector4();
+ l.W *= opacity;
+ if (l.W == 0)
+ {
+ return backdrop;
+ }
+
+ Vector4 b = backdrop.ToVector4();
+
+ return Compose(b, l, Vector4.Min(Vector4.One, b + l));
+ }
+
+ ///
+ /// Source substracted from backdrop
+ ///
+ /// Backgrop color
+ /// Source color
+ /// Opacity applied to Source Alpha
+ /// Output color
+ public static TBckPixel SubstractFunction(TBckPixel backdrop, TsrcPixel source, float opacity)
+ {
+ Vector4 l = source.ToVector4();
+ l.W *= opacity;
+ if (l.W == 0)
+ {
+ return backdrop;
+ }
+
+ Vector4 b = backdrop.ToVector4();
+
+ return Compose(b, l, Vector4.Max(Vector4.Zero, b - l));
+ }
+
+ ///
+ /// Complement of source multiplied by the complement of backdrop
+ ///
+ /// Backgrop color
+ /// Source color
+ /// Opacity applied to Source Alpha
+ /// Output color
+ public static TBckPixel ScreenFunction(TBckPixel backdrop, TsrcPixel source, float opacity)
+ {
+ Vector4 l = source.ToVector4();
+ l.W *= opacity;
+ if (l.W == 0)
+ {
+ return backdrop;
+ }
+
+ Vector4 b = backdrop.ToVector4();
+
+ return Compose(b, l, Vector4.One - ((Vector4.One - b) * (Vector4.One - l)));
+ }
+
+ ///
+ /// Per element, chooses the smallest value of source and backdrop
+ ///
+ /// Backgrop color
+ /// Source color
+ /// Opacity applied to Source Alpha
+ /// Output color
+ public static TBckPixel DarkenFunction(TBckPixel backdrop, TsrcPixel source, float opacity)
+ {
+ Vector4 l = source.ToVector4();
+ l.W *= opacity;
+ if (l.W == 0)
+ {
+ return backdrop;
+ }
+
+ Vector4 b = backdrop.ToVector4();
+
+ return Compose(b, l, Vector4.Min(b, l));
+ }
+
+ ///
+ /// Per element, chooses the largest value of source and backdrop
+ ///
+ /// Backgrop color
+ /// Source color
+ /// Opacity applied to Source Alpha
+ /// Output color
+ public static TBckPixel LightenFunction(TBckPixel backdrop, TsrcPixel source, float opacity)
+ {
+ Vector4 l = source.ToVector4();
+ l.W *= opacity;
+ if (l.W == 0)
+ {
+ return backdrop;
+ }
+
+ Vector4 b = backdrop.ToVector4();
+
+ return Compose(b, l, Vector4.Max(b, l));
+ }
+
+ ///
+ /// Overlays source over backdrop
+ ///
+ /// Backgrop color
+ /// Source color
+ /// Opacity applied to Source Alpha
+ /// Output color
+ public static TBckPixel OverlayFunction(TBckPixel backdrop, TsrcPixel source, float opacity)
+ {
+ Vector4 l = source.ToVector4();
+ l.W *= opacity;
+ if (l.W == 0)
+ {
+ return backdrop;
+ }
+
+ Vector4 b = backdrop.ToVector4();
+
+ float cr = OverlayValueFunction(b.X, l.X);
+ float cg = OverlayValueFunction(b.Y, l.Y);
+ float cb = OverlayValueFunction(b.Z, l.Z);
+
+ return Compose(b, l, Vector4.Min(Vector4.One, new Vector4(cr, cg, cb, 0)));
+ }
+
+ ///
+ /// Hard light effect
+ ///
+ /// Backgrop color
+ /// Source color
+ /// Opacity applied to Source Alpha
+ /// Output color
+ public static TBckPixel HardLightFunction(TBckPixel backdrop, TsrcPixel source, float opacity)
+ {
+ Vector4 l = source.ToVector4();
+ l.W *= opacity;
+ if (l.W == 0)
+ {
+ return backdrop;
+ }
+
+ Vector4 b = backdrop.ToVector4();
+
+ float cr = OverlayValueFunction(l.X, b.X);
+ float cg = OverlayValueFunction(l.Y, b.Y);
+ float cb = OverlayValueFunction(l.Z, b.Z);
+
+ return Compose(b, l, Vector4.Min(Vector4.One, new Vector4(cr, cg, cb, 0)));
+ }
+
+ ///
+ /// Helper function for Overlay andHardLight modes
+ ///
+ /// Backdrop color element
+ /// Source color element
+ /// Overlay value
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static float OverlayValueFunction(float backdrop, float source)
+ {
+ return backdrop <= 0.5f ? (2 * backdrop * source) : 1 - ((2 * (1 - source)) * (1 - backdrop));
+ }
+
+ ///
+ /// General composition function for all modes, with a general solution for alpha channel
+ ///
+ /// Original backgrop color
+ /// Original source color
+ /// Desired transformed color, without taking Alpha channel in account
+ /// The final color
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static TBckPixel Compose(Vector4 backdrop, Vector4 source, Vector4 xform)
+ {
+ DebugGuard.MustBeGreaterThan(source.W, 0, nameof(source.W));
+
+ // calculate weights
+ float xw = backdrop.W * source.W;
+ float bw = backdrop.W - xw;
+ float sw = source.W - xw;
+
+ // calculate final alpha
+ float a = xw + bw + sw;
+
+ // calculate final value
+ xform = ((xform * xw) + (backdrop * bw) + (source * sw)) / a;
+ xform.W = a;
+
+ TBckPixel packed = default(TBckPixel);
+ packed.PackFromVector4(xform);
+
+ return packed;
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/ImageSharp.Tests/Drawing/DrawImageEffectTest.cs b/tests/ImageSharp.Tests/Drawing/DrawImageEffectTest.cs
new file mode 100644
index 0000000000..78f0a08702
--- /dev/null
+++ b/tests/ImageSharp.Tests/Drawing/DrawImageEffectTest.cs
@@ -0,0 +1,42 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Tests
+{
+ using System.IO;
+ using ImageSharp.PixelFormats;
+ using Xunit;
+
+ public class DrawImageEffectTest : FileTestBase
+ {
+ [Fact]
+ public void ImageShouldApplyDrawImageFilter()
+ {
+ string path = this.CreateOutputDirectory("Drawing", "DrawImageEffect");
+
+ PixelTransformMode[] modes = (PixelTransformMode[])System.Enum.GetValues(typeof(PixelTransformMode));
+
+ using (Image blend = TestFile.Create(TestImages.Png.Blur).CreateImage())
+ {
+ foreach (TestFile file in Files)
+ {
+ using (Image image = file.CreateImage())
+ {
+ foreach (PixelTransformMode 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, mode, 75, size, loc).Save(output);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file