Browse Source

Merge branch 'pr/200--wip-duff-porter-general-pixel-function-for-drawing-images' into tocsoft/compositing

af/merge-core
Scott Williams 9 years ago
parent
commit
5c333a9967
  1. 160
      src/ImageSharp.Drawing/DrawImage.cs
  2. 102
      src/ImageSharp.Drawing/Processors/DrawImageEffectProcessor.cs
  3. 58
      src/ImageSharp/PixelFormats/PixelTransformMode.cs
  4. 57
      src/ImageSharp/PixelFormats/PixelTransformModeExtensions.cs
  5. 267
      src/ImageSharp/PixelFormats/PorterDuffFunctions.cs
  6. 42
      tests/ImageSharp.Tests/Drawing/DrawImageEffectTest.cs

160
src/ImageSharp.Drawing/DrawImage.cs

@ -1,57 +1,105 @@
// <copyright file="DrawImage.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp
{
using Drawing.Processors;
using ImageSharp.PixelFormats;
/// <summary>
/// Extension methods for the <see cref="Image"/> type.
/// </summary>
public static partial class ImageExtensions
{
/// <summary>
/// Draws the given image together with the current one by blending their pixels.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="image">The image to blend with the currently processing image.</param>
/// <param name="percent">The opacity of the image image to blend. Must be between 0 and 100.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Blend<TPixel>(this Image<TPixel> source, Image<TPixel> image, int percent = 50)
where TPixel : struct, IPixel<TPixel>
{
return DrawImage(source, image, percent, default(Size), default(Point));
}
/// <summary>
/// Draws the given image together with the current one by blending their pixels.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="image">The image to blend with the currently processing image.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="percent">The opacity of the image image to blend. Must be between 0 and 100.</param>
/// <param name="size">The size to draw the blended image.</param>
/// <param name="location">The location to draw the blended image.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> DrawImage<TPixel>(this Image<TPixel> source, Image<TPixel> image, int percent, Size size, Point location)
where TPixel : struct, IPixel<TPixel>
{
if (size == default(Size))
{
size = new Size(image.Width, image.Height);
}
if (location == default(Point))
{
location = Point.Empty;
}
source.ApplyProcessor(new DrawImageProcessor<TPixel>(image, size, location, percent), source.Bounds);
return source;
}
}
// <copyright file="DrawImage.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp
{
using System;
using Drawing.Processors;
using ImageSharp.PixelFormats;
/// <summary>
/// Extension methods for the <see cref="Image"/> type.
/// </summary>
public static partial class ImageExtensions
{
/// <summary>
/// Draws the given image together with the current one by blending their pixels.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="image">The image to blend with the currently processing image.</param>
/// <param name="percent">The opacity of the image image to blend. Must be between 0 and 100.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Blend<TPixel>(this Image<TPixel> source, Image<TPixel> image, int percent = 50)
where TPixel : struct, IPixel<TPixel>
{
return DrawImage(source, image, percent, default(Size), default(Point));
}
/// <summary>
/// Draws the given image together with the current one by blending their pixels.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="image">The image to blend with the currently processing image.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="percent">The opacity of the image image to blend. Must be between 0 and 100.</param>
/// <param name="size">The size to draw the blended image.</param>
/// <param name="location">The location to draw the blended image.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> DrawImage<TPixel>(this Image<TPixel> source, Image<TPixel> image, int percent, Size size, Point location)
where TPixel : struct, IPixel<TPixel>
{
if (size == default(Size))
{
size = new Size(image.Width, image.Height);
}
if (location == default(Point))
{
location = Point.Empty;
}
source.ApplyProcessor(new DrawImageProcessor<TPixel>(image, size, location, percent), source.Bounds);
return source;
}
/// <summary>
/// Draws the given image together with the current one by blending their pixels.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="image">The image to blend with the currently processing image.</param>
/// <param name="mode">Pixel function effect to apply on every pixel</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="percent">The opacity of the image image to blend. Must be between 0 and 100.</param>
/// <param name="size">The size to draw the blended image.</param>
/// <param name="location">The location to draw the blended image.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> DrawImage<TPixel>(this Image<TPixel> source, Image<TPixel> image, PixelTransformMode mode, int percent, Size size, Point location)
where TPixel : struct, IPixel<TPixel>
{
Func<TPixel, TPixel, float, TPixel> pixelFunc = mode.GetPixelFunction<TPixel>();
return DrawImage(source, image, pixelFunc, percent, size, location);
}
/// <summary>
/// Draws the given image together with the current one by blending their pixels.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="image">The image to blend with the currently processing image.</param>
/// <param name="pixelFunc">Pixel function effect to apply on every pixel</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="percent">The opacity of the image image to blend. Must be between 0 and 100.</param>
/// <param name="size">The size to draw the blended image.</param>
/// <param name="location">The location to draw the blended image.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> DrawImage<TPixel>(this Image<TPixel> source, Image<TPixel> image, Func<TPixel, TPixel, float, TPixel> pixelFunc, int percent, Size size, Point location)
where TPixel : struct, IPixel<TPixel>
{
if (size == default(Size))
{
size = new Size(image.Width, image.Height);
}
if (location == default(Point))
{
location = Point.Empty;
}
source.ApplyProcessor(new DrawImageEffectProcessor<TPixel>(image, size, location, pixelFunc, percent), source.Bounds);
return source;
}
}
}

102
src/ImageSharp.Drawing/Processors/DrawImageEffectProcessor.cs

@ -0,0 +1,102 @@
// <copyright file="DrawImageEffectProcessor.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Drawing.Processors
{
using System;
using System.Numerics;
using System.Threading.Tasks;
using ImageSharp.PixelFormats;
using ImageSharp.Processing;
/// <summary>
/// Combines two images together by blending the pixels.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
internal class DrawImageEffectProcessor<TPixel> : ImageProcessor<TPixel>
where TPixel : struct, IPixel<TPixel>
{
/// <summary>
/// Initializes a new instance of the <see cref="DrawImageEffectProcessor{TPixel}"/> class.
/// </summary>
/// <param name="image">The image to blend with the currently processing image.</param>
/// <param name="size">The size to draw the blended image.</param>
/// <param name="location">The location to draw the blended image.</param>
/// <param name="pixelFunction">Pixel function effect to apply on every pixel</param>
/// <param name="alpha">The opacity of the image to blend. Between 0 and 100.</param>
public DrawImageEffectProcessor(Image<TPixel> image, Size size, Point location, Func<TPixel, TPixel, float, TPixel> 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;
}
/// <summary>
/// Gets the image to blend.
/// </summary>
public Image<TPixel> Image { get; private set; }
/// <summary>
/// Gets The function effect to apply on a per pixel basis
/// </summary>
public Func<TPixel, TPixel, float, TPixel> PixelFunction { get; private set; }
/// <summary>
/// Gets the alpha percentage value.
/// </summary>
public int Alpha { get; }
/// <summary>
/// Gets the size to draw the blended image.
/// </summary>
public Size Size { get; }
/// <summary>
/// Gets the location to draw the blended image.
/// </summary>
public Point Location { get; }
/// <inheritdoc/>
protected override void OnApply(ImageBase<TPixel> 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<TPixel> sourcePixels = this.Image.Lock())
using (PixelAccessor<TPixel> 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);
}
});
}
}
}
}

58
src/ImageSharp/PixelFormats/PixelTransformMode.cs

@ -0,0 +1,58 @@
// <copyright file="PixelTransformMode.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.PixelFormats
{
/// <summary>
/// Porter Duff Blending composition modes
/// </summary>
public enum PixelTransformMode
{
/// <summary>
/// Default blending mode, also known as "Normal" or "Alpha Blending"
/// </summary>
Normal,
/// <summary>
/// Backdrop + Source
/// </summary>
Multiply,
/// <summary>
/// Backdrop + Source
/// </summary>
Add,
/// <summary>
/// Backdrop - Source
/// </summary>
Substract,
/// <summary>
/// Screen effect
/// </summary>
Screen,
/// <summary>
/// Darken effect
/// </summary>
Darken,
/// <summary>
/// Lighten effect
/// </summary>
Lighten,
/// <summary>
/// Overlay effect
/// </summary>
Overlay,
/// <summary>
/// Hard light effect
/// </summary>
HardLight
}
}

57
src/ImageSharp/PixelFormats/PixelTransformModeExtensions.cs

@ -0,0 +1,57 @@
// <copyright file="PixelTransformModeExtensions.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp
{
using System;
using System.Collections.Generic;
using System.Text;
using PixelFormats;
/// <summary>
/// Extensions to retrieve the appropiate pixel transformation functions for <see cref="PixelTransformMode"/>
/// </summary>
public static class PixelTransformModeExtensions
{
/// <summary>
/// Gets a pixel transformation function
/// </summary>
/// <typeparam name="TPixel">The pixel format used for both Backdrop and Source</typeparam>
/// <param name="mode">The Duff Porter mode</param>
/// <returns>A function that transforms a Backdrop and Source colors into a final color</returns>
public static Func<TPixel, TPixel, float, TPixel> GetPixelFunction<TPixel>(this PixelTransformMode mode)
where TPixel : IPixel
{
return mode.GetPixelFunction<TPixel, TPixel>();
}
/// <summary>
/// Gets a pixel transformation function
/// </summary>
/// <typeparam name="TBckPixel">The pixel format used for Backdrop and Output</typeparam>
/// <typeparam name="TSrcPixel">The pixel format used for Source</typeparam>
/// <param name="mode">The Duff Porter mode</param>
/// <returns>A function that transforms a Backdrop and Source colors into a final color</returns>
public static Func<TBckPixel, TSrcPixel, float, TBckPixel> GetPixelFunction<TBckPixel, TSrcPixel>(this PixelTransformMode mode)
where TBckPixel : IPixel
where TSrcPixel : IPixel
{
switch (mode)
{
case PixelTransformMode.Normal: return PorterDuffFunctions<TBckPixel, TSrcPixel>.NormalBlendFunction;
case PixelTransformMode.Multiply: return PorterDuffFunctions<TBckPixel, TSrcPixel>.MultiplyFunction;
case PixelTransformMode.Add: return PorterDuffFunctions<TBckPixel, TSrcPixel>.AddFunction;
case PixelTransformMode.Substract: return PorterDuffFunctions<TBckPixel, TSrcPixel>.SubstractFunction;
case PixelTransformMode.Screen: return PorterDuffFunctions<TBckPixel, TSrcPixel>.ScreenFunction;
case PixelTransformMode.Darken: return PorterDuffFunctions<TBckPixel, TSrcPixel>.DarkenFunction;
case PixelTransformMode.Lighten: return PorterDuffFunctions<TBckPixel, TSrcPixel>.LightenFunction;
case PixelTransformMode.Overlay: return PorterDuffFunctions<TBckPixel, TSrcPixel>.OverlayFunction;
case PixelTransformMode.HardLight: return PorterDuffFunctions<TBckPixel, TSrcPixel>.HardLightFunction;
default: throw new NotImplementedException(nameof(mode));
}
}
}
}

267
src/ImageSharp/PixelFormats/PorterDuffFunctions.cs

@ -0,0 +1,267 @@
// <copyright file="PorterDuffFunctions.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.PixelFormats
{
using System.Numerics;
using System.Runtime.CompilerServices;
/// <summary>
/// Collection of Porter Duff alpha blending functions
/// </summary>
/// <typeparam name="TBckPixel">Backdrop Pixel Format</typeparam>
/// <typeparam name="TsrcPixel">Source Pixel Format</typeparam>
/// <remarks>
/// 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
/// </remarks>
internal static class PorterDuffFunctions<TBckPixel, TsrcPixel>
where TBckPixel : IPixel
where TsrcPixel : IPixel
{
/// <summary>
/// Source over backdrop
/// </summary>
/// <param name="backdrop">Backgrop color</param>
/// <param name="source">Source color</param>
/// <param name="opacity">Opacity applied to Source Alpha</param>
/// <returns>Output color</returns>
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);
}
/// <summary>
/// Source multiplied by backdrop
/// </summary>
/// <param name="backdrop">Backgrop color</param>
/// <param name="source">Source color</param>
/// <param name="opacity">Opacity applied to Source Alpha</param>
/// <returns>Output color</returns>
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);
}
/// <summary>
/// Source added to backdrop
/// </summary>
/// <param name="backdrop">Backgrop color</param>
/// <param name="source">Source color</param>
/// <param name="opacity">Opacity applied to Source Alpha</param>
/// <returns>Output color</returns>
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));
}
/// <summary>
/// Source substracted from backdrop
/// </summary>
/// <param name="backdrop">Backgrop color</param>
/// <param name="source">Source color</param>
/// <param name="opacity">Opacity applied to Source Alpha</param>
/// <returns>Output color</returns>
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));
}
/// <summary>
/// Complement of source multiplied by the complement of backdrop
/// </summary>
/// <param name="backdrop">Backgrop color</param>
/// <param name="source">Source color</param>
/// <param name="opacity">Opacity applied to Source Alpha</param>
/// <returns>Output color</returns>
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)));
}
/// <summary>
/// Per element, chooses the smallest value of source and backdrop
/// </summary>
/// <param name="backdrop">Backgrop color</param>
/// <param name="source">Source color</param>
/// <param name="opacity">Opacity applied to Source Alpha</param>
/// <returns>Output color</returns>
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));
}
/// <summary>
/// Per element, chooses the largest value of source and backdrop
/// </summary>
/// <param name="backdrop">Backgrop color</param>
/// <param name="source">Source color</param>
/// <param name="opacity">Opacity applied to Source Alpha</param>
/// <returns>Output color</returns>
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));
}
/// <summary>
/// Overlays source over backdrop
/// </summary>
/// <param name="backdrop">Backgrop color</param>
/// <param name="source">Source color</param>
/// <param name="opacity">Opacity applied to Source Alpha</param>
/// <returns>Output color</returns>
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)));
}
/// <summary>
/// Hard light effect
/// </summary>
/// <param name="backdrop">Backgrop color</param>
/// <param name="source">Source color</param>
/// <param name="opacity">Opacity applied to Source Alpha</param>
/// <returns>Output color</returns>
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)));
}
/// <summary>
/// Helper function for Overlay andHardLight modes
/// </summary>
/// <param name="backdrop">Backdrop color element</param>
/// <param name="source">Source color element</param>
/// <returns>Overlay value</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static float OverlayValueFunction(float backdrop, float source)
{
return backdrop <= 0.5f ? (2 * backdrop * source) : 1 - ((2 * (1 - source)) * (1 - backdrop));
}
/// <summary>
/// General composition function for all modes, with a general solution for alpha channel
/// </summary>
/// <param name="backdrop">Original backgrop color</param>
/// <param name="source">Original source color</param>
/// <param name="xform">Desired transformed color, without taking Alpha channel in account</param>
/// <returns>The final color</returns>
[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;
}
}
}

42
tests/ImageSharp.Tests/Drawing/DrawImageEffectTest.cs

@ -0,0 +1,42 @@
// <copyright file="DrawImageEffectTest.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
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);
}
}
}
}
}
}
}
}
Loading…
Cancel
Save