Browse Source

Merge pull request #679 from vpenades/master

Split PixelBlendMode into PixelColorBlendingMode and PixelAlphaCompositionMode
pull/682/head
James Jackson-South 8 years ago
committed by GitHub
parent
commit
52db9dba62
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      src/ImageSharp.Drawing/Processing/BrushApplicator.cs
  2. 85
      src/ImageSharp.Drawing/Processing/DrawImageExtensions.cs
  3. 70
      src/ImageSharp.Drawing/Processing/Processors/Drawing/DrawImageProcessor.cs
  4. 16
      src/ImageSharp.Drawing/Processing/Processors/Drawing/FillProcessor.cs
  5. 22
      src/ImageSharp.Drawing/Processing/TextGraphicsOptions.cs
  6. 113
      src/ImageSharp/GraphicsOptions.cs
  7. 63
      src/ImageSharp/PixelFormats/PixelAlphaCompositionMode.cs
  8. 56
      src/ImageSharp/PixelFormats/PixelColorBlendingMode.cs
  9. 265
      src/ImageSharp/PixelFormats/PixelOperations{TPixel}.PixelBenders.cs
  10. 2
      src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor.cs
  11. 2
      src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs
  12. 4
      src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs
  13. 32
      tests/ImageSharp.Tests/Drawing/DrawImageTest.cs
  14. 68
      tests/ImageSharp.Tests/Drawing/FillSolidBrushTests.cs
  15. 65
      tests/ImageSharp.Tests/Drawing/SolidFillBlendedShapesTests.cs
  16. 101
      tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffCompositorTests.cs
  17. 74
      tests/ImageSharp.Tests/PixelFormats/PixelOperationsTests.Blender.cs
  18. 11
      tests/ImageSharp.Tests/PixelFormats/PixelOperationsTests.cs
  19. 2
      tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs
  20. 2
      tests/Images/External

2
src/ImageSharp.Drawing/Processing/BrushApplicator.cs

@ -28,7 +28,7 @@ namespace SixLabors.ImageSharp.Processing
{ {
this.Target = target; this.Target = target;
this.Options = options; this.Options = options;
this.Blender = PixelOperations<TPixel>.Instance.GetPixelBlender(options.BlenderMode); this.Blender = PixelOperations<TPixel>.Instance.GetPixelBlender(options);
} }
/// <summary> /// <summary>

85
src/ImageSharp.Drawing/Processing/DrawImageExtensions.cs

@ -22,7 +22,7 @@ namespace SixLabors.ImageSharp.Processing
/// <returns>The <see cref="Image{TPixel}"/>.</returns> /// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> DrawImage<TPixel>(this IImageProcessingContext<TPixel> source, Image<TPixel> image, float opacity) public static IImageProcessingContext<TPixel> DrawImage<TPixel>(this IImageProcessingContext<TPixel> source, Image<TPixel> image, float opacity)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
=> source.ApplyProcessor(new DrawImageProcessor<TPixel>(image, opacity)); => source.ApplyProcessor(new DrawImageProcessor<TPixel>(image, Point.Empty, GraphicsOptions.Default.ColorBlendingMode, GraphicsOptions.Default.AlphaCompositionMode, opacity));
/// <summary> /// <summary>
/// Draws the given image together with the current one by blending their pixels. /// Draws the given image together with the current one by blending their pixels.
@ -30,63 +30,92 @@ namespace SixLabors.ImageSharp.Processing
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param> /// <param name="source">The image this method extends.</param>
/// <param name="image">The image to blend with the currently processing image.</param> /// <param name="image">The image to blend with the currently processing image.</param>
/// <param name="blender">The blending mode.</param> /// <param name="colorBlending">The blending mode.</param>
/// <param name="opacity">The opacity of the image to blend. Must be between 0 and 1.</param> /// <param name="opacity">The opacity of the image to blend. Must be between 0 and 1.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns> /// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> DrawImage<TPixel>(this IImageProcessingContext<TPixel> source, Image<TPixel> image, PixelBlenderMode blender, float opacity) public static IImageProcessingContext<TPixel> DrawImage<TPixel>(this IImageProcessingContext<TPixel> source, Image<TPixel> image, PixelColorBlendingMode colorBlending, float opacity)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
=> source.ApplyProcessor(new DrawImageProcessor<TPixel>(image, opacity, blender)); => source.ApplyProcessor(new DrawImageProcessor<TPixel>(image, Point.Empty, colorBlending, GraphicsOptions.Default.AlphaCompositionMode, opacity));
/// <summary> /// <summary>
/// Draws the given image together with the current one by blending their pixels. /// Draws the given image together with the current one by blending their pixels.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param> /// <param name="source">The image this method extends.</param>
/// <param name="options">The options, including the blending type and blending amount.</param>
/// <param name="image">The image to blend with the currently processing image.</param> /// <param name="image">The image to blend with the currently processing image.</param>
/// <param name="colorBlending">The color blending mode.</param>
/// <param name="alphaComposition">The alpha composition mode.</param>
/// <param name="opacity">The opacity of the image to blend. Must be between 0 and 1.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns> /// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> DrawImage<TPixel>(this IImageProcessingContext<TPixel> source, GraphicsOptions options, Image<TPixel> image) public static IImageProcessingContext<TPixel> DrawImage<TPixel>(this IImageProcessingContext<TPixel> source, Image<TPixel> image, PixelColorBlendingMode colorBlending, PixelAlphaCompositionMode alphaComposition, float opacity)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
=> source.ApplyProcessor(new DrawImageProcessor<TPixel>(image, options)); => source.ApplyProcessor(new DrawImageProcessor<TPixel>(image, Point.Empty, colorBlending, alphaComposition, opacity));
/// <summary> /// <summary>
/// Draws the given image together with the current one by blending their pixels. /// Draws the given image together with the current one by blending their pixels.
/// </summary> /// </summary>
/// <param name="source">The image this method extends.</param> /// <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="image">The image to blend with the currently processing image.</param>
/// <param name="options">The options, including the blending type and blending amount.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> DrawImage<TPixel>(this IImageProcessingContext<TPixel> source, Image<TPixel> image, GraphicsOptions options)
where TPixel : struct, IPixel<TPixel>
=> source.ApplyProcessor(new DrawImageProcessor<TPixel>(image, Point.Empty, options.ColorBlendingMode, options.AlphaCompositionMode, options.BlendPercentage));
/// <summary>
/// Draws the given image together with the current one by blending their pixels.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="opacity">The opacity of the image to blend. Must be between 0 and 1.</param> /// <param name="source">The image this method extends.</param>
/// <param name="image">The image to blend with the currently processing image.</param>
/// <param name="location">The location to draw the blended image.</param> /// <param name="location">The location to draw the blended image.</param>
/// <param name="opacity">The opacity of the image to blend. Must be between 0 and 1.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns> /// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> DrawImage<TPixel>(this IImageProcessingContext<TPixel> source, Image<TPixel> image, float opacity, Point location) public static IImageProcessingContext<TPixel> DrawImage<TPixel>(this IImageProcessingContext<TPixel> source, Image<TPixel> image, Point location, float opacity)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
=> source.ApplyProcessor(new DrawImageProcessor<TPixel>(image, location, opacity)); => source.ApplyProcessor(new DrawImageProcessor<TPixel>(image, location, GraphicsOptions.Default.ColorBlendingMode, GraphicsOptions.Default.AlphaCompositionMode, opacity));
/// <summary> /// <summary>
/// Draws the given image together with the current one by blending their pixels. /// Draws the given image together with the current one by blending their pixels.
/// </summary> /// </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> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="blender">The type of bending to apply.</param> /// <param name="source">The image this method extends.</param>
/// <param name="opacity">The opacity of the image to blend. Must be between 0 and 1.</param> /// <param name="image">The image to blend with the currently processing image.</param>
/// <param name="location">The location to draw the blended image.</param> /// <param name="location">The location to draw the blended image.</param>
/// <param name="colorBlending">The color blending to apply.</param>
/// <param name="opacity">The opacity of the image to blend. Must be between 0 and 1.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns> /// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> DrawImage<TPixel>(this IImageProcessingContext<TPixel> source, Image<TPixel> image, PixelBlenderMode blender, float opacity, Point location) public static IImageProcessingContext<TPixel> DrawImage<TPixel>(this IImageProcessingContext<TPixel> source, Image<TPixel> image, Point location, PixelColorBlendingMode colorBlending, float opacity)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
=> source.ApplyProcessor(new DrawImageProcessor<TPixel>(image, location, opacity, blender)); => source.ApplyProcessor(new DrawImageProcessor<TPixel>(image, location, colorBlending, GraphicsOptions.Default.AlphaCompositionMode, opacity));
/// <summary> /// <summary>
/// Draws the given image together with the current one by blending their pixels. /// Draws the given image together with the current one by blending their pixels.
/// </summary> /// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="options">The options containing the blend mode and opacity.</param>
/// <param name="image">The image to blend with the currently processing image.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <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="location">The location to draw the blended image.</param> /// <param name="location">The location to draw the blended image.</param>
/// <param name="colorBlending">The color blending to apply.</param>
/// <param name="alphaComposition">The alpha composition mode.</param>
/// <param name="opacity">The opacity of the image to blend. Must be between 0 and 1.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> DrawImage<TPixel>(this IImageProcessingContext<TPixel> source, Image<TPixel> image, Point location, PixelColorBlendingMode colorBlending, PixelAlphaCompositionMode alphaComposition, float opacity)
where TPixel : struct, IPixel<TPixel>
=> source.ApplyProcessor(new DrawImageProcessor<TPixel>(image, location, colorBlending, alphaComposition, opacity));
/// <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="location">The location to draw the blended image.</param>
/// <param name="options">The options containing the blend mode and opacity.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns> /// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> DrawImage<TPixel>(this IImageProcessingContext<TPixel> source, GraphicsOptions options, Image<TPixel> image, Point location) public static IImageProcessingContext<TPixel> DrawImage<TPixel>(this IImageProcessingContext<TPixel> source, Image<TPixel> image, Point location, GraphicsOptions options)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
=> source.ApplyProcessor(new DrawImageProcessor<TPixel>(image, location, options)); => source.ApplyProcessor(new DrawImageProcessor<TPixel>(image, location, options.ColorBlendingMode, options.AlphaCompositionMode, options.BlendPercentage));
} }
} }

70
src/ImageSharp.Drawing/Processing/Processors/Drawing/DrawImageProcessor.cs

@ -18,80 +18,22 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
internal class DrawImageProcessor<TPixel> : ImageProcessor<TPixel> internal class DrawImageProcessor<TPixel> : ImageProcessor<TPixel>
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="DrawImageProcessor{TPixel}"/> class. /// Initializes a new instance of the <see cref="DrawImageProcessor{TPixel}"/> class.
/// </summary> /// </summary>
/// <param name="image">The image to blend with the currently processing image.</param> /// <param name="image">The image to blend with the currently processing image.</param>
/// <param name="location">The location to draw the blended image.</param>
/// <param name="colorBlendingMode">The blending mode to use when drawing the image.</param>
/// <param name="alphaCompositionMode">The Alpha blending mode to use when drawing the image.</param>
/// <param name="opacity">The opacity of the image to blend. Must be between 0 and 1.</param> /// <param name="opacity">The opacity of the image to blend. Must be between 0 and 1.</param>
public DrawImageProcessor(Image<TPixel> image, float opacity) public DrawImageProcessor(Image<TPixel> image, Point location, PixelColorBlendingMode colorBlendingMode, PixelAlphaCompositionMode alphaCompositionMode, float opacity)
: this(image, Point.Empty, opacity)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="DrawImageProcessor{TPixel}"/> class.
/// </summary>
/// <param name="image">The image to blend with the currently processing image.</param>
/// <param name="options">
/// The options containing the opacity of the image to blend and blending mode.
/// Opacity must be between 0 and 1.
/// </param>
public DrawImageProcessor(Image<TPixel> image, GraphicsOptions options)
: this(image, Point.Empty, options)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="DrawImageProcessor{TPixel}"/> class.
/// </summary>
/// <param name="image">The image to blend with the currently processing image.</param>
/// <param name="location">The location to draw the blended image.</param>
/// <param name="opacity">The opacity of the image to blend. Must be between 0 and 1.</param>
public DrawImageProcessor(Image<TPixel> image, Point location, float opacity)
: this(image, location, opacity, GraphicsOptions.Default.BlenderMode)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="DrawImageProcessor{TPixel}"/> class.
/// </summary>
/// <param name="image">The image to blend with the currently processing image.</param>
/// <param name="location">The location to draw the blended image.</param>
/// <param name="options">
/// The options containing the opacity of the image to blend and blending mode.
/// Opacity must be between 0 and 1.
/// </param>
public DrawImageProcessor(Image<TPixel> image, Point location, GraphicsOptions options)
: this(image, location, options.BlendPercentage, options.BlenderMode)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="DrawImageProcessor{TPixel}"/> class.
/// </summary>
/// <param name="image">The image to blend with the currently processing image.</param>
/// <param name="opacity">The opacity of the image to blend. Must be between 0 and 1.</param>
/// <param name="blenderMode">The blending mode to use when drawing the image.</param>
public DrawImageProcessor(Image<TPixel> image, float opacity, PixelBlenderMode blenderMode)
: this(image, Point.Empty, opacity, blenderMode)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="DrawImageProcessor{TPixel}"/> class.
/// </summary>
/// <param name="image">The image to blend with the currently processing image.</param>
/// <param name="location">The location to draw the blended image.</param>
/// <param name="opacity">The opacity of the image to blend. Must be between 0 and 1.</param>
/// <param name="blenderMode">The blending mode to use when drawing the image.</param>
public DrawImageProcessor(Image<TPixel> image, Point location, float opacity, PixelBlenderMode blenderMode)
{ {
Guard.MustBeBetweenOrEqualTo(opacity, 0, 1, nameof(opacity)); Guard.MustBeBetweenOrEqualTo(opacity, 0, 1, nameof(opacity));
this.Image = image; this.Image = image;
this.Opacity = opacity; this.Opacity = opacity;
this.Blender = PixelOperations<TPixel>.Instance.GetPixelBlender(blenderMode); this.Blender = PixelOperations<TPixel>.Instance.GetPixelBlender(colorBlendingMode, alphaCompositionMode);
this.Location = location; this.Location = location;
} }

16
src/ImageSharp.Drawing/Processing/Processors/Drawing/FillProcessor.cs

@ -102,14 +102,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing
private bool IsSolidBrushWithoutBlending(out SolidBrush<TPixel> solidBrush) private bool IsSolidBrushWithoutBlending(out SolidBrush<TPixel> solidBrush)
{ {
solidBrush = this.brush as SolidBrush<TPixel>; solidBrush = this.brush as SolidBrush<TPixel>;
return solidBrush != null if (solidBrush == null)
&& ((this.options.BlenderMode == PixelBlenderMode.Normal && this.options.BlendPercentage == 1f {
&& solidBrush.Color.ToVector4().W == 1f) return false;
|| (this.options.BlenderMode == PixelBlenderMode.Over && this.options.BlendPercentage == 1f }
&& solidBrush.Color.ToVector4().W == 1f)
|| (this.options.BlenderMode == PixelBlenderMode.Src)); return this.options.IsOpaqueColorWithoutBlending(solidBrush.Color);
} }
} }
} }

22
src/ImageSharp.Drawing/Processing/TextGraphicsOptions.cs

@ -32,7 +32,9 @@ namespace SixLabors.ImageSharp.Processing
private float? dpiY; private float? dpiY;
private PixelBlenderMode blenderMode; private PixelColorBlendingMode colorBlendingMode;
private PixelAlphaCompositionMode alphaCompositionMode;
private float wrapTextWidth; private float wrapTextWidth;
@ -53,7 +55,8 @@ namespace SixLabors.ImageSharp.Processing
this.verticalAlignment = VerticalAlignment.Top; this.verticalAlignment = VerticalAlignment.Top;
this.antialiasSubpixelDepth = 16; this.antialiasSubpixelDepth = 16;
this.blenderMode = PixelBlenderMode.Normal; this.colorBlendingMode = PixelColorBlendingMode.Normal;
this.alphaCompositionMode = PixelAlphaCompositionMode.SrcOver;
this.blendPercentage = 1; this.blendPercentage = 1;
this.antialias = enableAntialiasing; this.antialias = enableAntialiasing;
this.dpiX = DefaultTextDpi; this.dpiX = DefaultTextDpi;
@ -80,9 +83,14 @@ namespace SixLabors.ImageSharp.Processing
// some API thought post V1. // some API thought post V1.
/// <summary> /// <summary>
/// Gets or sets a value indicating the blending percentage to apply to the drawing operation /// Gets or sets a value indicating the color blending percentage to apply to the drawing operation
/// </summary>
public PixelColorBlendingMode ColorBlendingMode { get => this.colorBlendingMode; set => this.colorBlendingMode = value; }
/// <summary>
/// Gets or sets a value indicating the color blending percentage to apply to the drawing operation
/// </summary> /// </summary>
public PixelBlenderMode BlenderMode { get => this.blenderMode; set => this.blenderMode = value; } public PixelAlphaCompositionMode AlphaCompositionMode { get => this.alphaCompositionMode; set => this.alphaCompositionMode = value; }
/// <summary> /// <summary>
/// Gets or sets a value indicating whether the text should be drawing with kerning enabled. /// Gets or sets a value indicating whether the text should be drawing with kerning enabled.
@ -135,7 +143,8 @@ namespace SixLabors.ImageSharp.Processing
{ {
AntialiasSubpixelDepth = options.AntialiasSubpixelDepth, AntialiasSubpixelDepth = options.AntialiasSubpixelDepth,
blendPercentage = options.BlendPercentage, blendPercentage = options.BlendPercentage,
blenderMode = options.BlenderMode colorBlendingMode = options.ColorBlendingMode,
alphaCompositionMode = options.AlphaCompositionMode
}; };
} }
@ -151,7 +160,8 @@ namespace SixLabors.ImageSharp.Processing
return new GraphicsOptions(options.Antialias) return new GraphicsOptions(options.Antialias)
{ {
AntialiasSubpixelDepth = options.AntialiasSubpixelDepth, AntialiasSubpixelDepth = options.AntialiasSubpixelDepth,
BlenderMode = options.BlenderMode, ColorBlendingMode = options.ColorBlendingMode,
AlphaCompositionMode = options.AlphaCompositionMode,
BlendPercentage = options.BlendPercentage BlendPercentage = options.BlendPercentage
}; };
} }

113
src/ImageSharp/GraphicsOptions.cs

@ -21,7 +21,9 @@ namespace SixLabors.ImageSharp
private bool? antialias; private bool? antialias;
private PixelBlenderMode blenderMode; private PixelColorBlendingMode colorBlendingMode;
private PixelAlphaCompositionMode alphaCompositionMode;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="GraphicsOptions"/> struct. /// Initializes a new instance of the <see cref="GraphicsOptions"/> struct.
@ -29,10 +31,62 @@ namespace SixLabors.ImageSharp
/// <param name="enableAntialiasing">If set to <c>true</c> [enable antialiasing].</param> /// <param name="enableAntialiasing">If set to <c>true</c> [enable antialiasing].</param>
public GraphicsOptions(bool enableAntialiasing) public GraphicsOptions(bool enableAntialiasing)
{ {
this.blenderMode = PixelBlenderMode.Normal; this.colorBlendingMode = PixelColorBlendingMode.Normal;
this.alphaCompositionMode = PixelAlphaCompositionMode.SrcOver;
this.blendPercentage = 1; this.blendPercentage = 1;
this.antialiasSubpixelDepth = 16; this.antialiasSubpixelDepth = 16;
this.antialias = enableAntialiasing; this.antialias = enableAntialiasing;
}
/// <summary>
/// Initializes a new instance of the <see cref="GraphicsOptions"/> struct.
/// </summary>
/// <param name="enableAntialiasing">If set to <c>true</c> [enable antialiasing].</param>
/// <param name="opacity">blending percentage to apply to the drawing operation</param>
public GraphicsOptions(bool enableAntialiasing, float opacity)
{
Guard.MustBeBetweenOrEqualTo(opacity, 0, 1, nameof(opacity));
this.colorBlendingMode = PixelColorBlendingMode.Normal;
this.alphaCompositionMode = PixelAlphaCompositionMode.SrcOver;
this.blendPercentage = opacity;
this.antialiasSubpixelDepth = 16;
this.antialias = enableAntialiasing;
}
/// <summary>
/// Initializes a new instance of the <see cref="GraphicsOptions"/> struct.
/// </summary>
/// <param name="enableAntialiasing">If set to <c>true</c> [enable antialiasing].</param>
/// <param name="opacity">blending percentage to apply to the drawing operation</param>
/// <param name="blending">color blending mode to apply to the drawing operation</param>
public GraphicsOptions(bool enableAntialiasing, PixelColorBlendingMode blending, float opacity)
{
Guard.MustBeBetweenOrEqualTo(opacity, 0, 1, nameof(opacity));
this.colorBlendingMode = blending;
this.alphaCompositionMode = PixelAlphaCompositionMode.SrcOver;
this.blendPercentage = opacity;
this.antialiasSubpixelDepth = 16;
this.antialias = enableAntialiasing;
}
/// <summary>
/// Initializes a new instance of the <see cref="GraphicsOptions"/> struct.
/// </summary>
/// <param name="enableAntialiasing">If set to <c>true</c> [enable antialiasing].</param>
/// <param name="opacity">blending percentage to apply to the drawing operation</param>
/// <param name="blending">color blending mode to apply to the drawing operation</param>
/// <param name="composition">alpha composition mode to apply to the drawing operation</param>
public GraphicsOptions(bool enableAntialiasing, PixelColorBlendingMode blending, PixelAlphaCompositionMode composition, float opacity)
{
Guard.MustBeBetweenOrEqualTo(opacity, 0, 1, nameof(opacity));
this.colorBlendingMode = blending;
this.alphaCompositionMode = composition;
this.blendPercentage = opacity;
this.antialiasSubpixelDepth = 16;
this.antialias = enableAntialiasing;
} }
/// <summary> /// <summary>
@ -67,12 +121,59 @@ namespace SixLabors.ImageSharp
// some API thought post V1. // some API thought post V1.
/// <summary> /// <summary>
/// Gets or sets a value indicating the blending mode to apply to the drawing operation /// Gets or sets a value indicating the color blending mode to apply to the drawing operation
/// </summary>
public PixelColorBlendingMode ColorBlendingMode
{
get => this.colorBlendingMode;
set => this.colorBlendingMode = value;
}
/// <summary>
/// Gets or sets a value indicating the alpha composition mode to apply to the drawing operation
/// </summary> /// </summary>
public PixelBlenderMode BlenderMode public PixelAlphaCompositionMode AlphaCompositionMode
{ {
get => this.blenderMode; get => this.alphaCompositionMode;
set => this.blenderMode = value; set => this.alphaCompositionMode = value;
}
/// <summary>
/// Evaluates if a given SOURCE color can completely replace a BACKDROP color given the current blending and composition settings.
/// </summary>
/// <typeparam name="TPixel">The pixel format</typeparam>
/// <param name="color">the color</param>
/// <returns>true if the color can be considered opaque</returns>
/// <remarks>
/// Blending and composition is an expensive operation, in some cases, like
/// filling with a solid color, the blending can be avoided by a plain color replacement.
/// This method can be useful for such processors to select the fast path.
/// </remarks>
internal bool IsOpaqueColorWithoutBlending<TPixel>(TPixel color)
where TPixel : struct, IPixel<TPixel>
{
if (this.ColorBlendingMode != PixelColorBlendingMode.Normal)
{
return false;
}
if (this.AlphaCompositionMode != PixelAlphaCompositionMode.SrcOver &&
this.AlphaCompositionMode != PixelAlphaCompositionMode.Src)
{
return false;
}
if (this.BlendPercentage != 1f)
{
return false;
}
if (color.ToVector4().W != 1f)
{
return false;
}
return true;
} }
} }
} }

63
src/ImageSharp/PixelFormats/PixelBlenderMode.cs → src/ImageSharp/PixelFormats/PixelAlphaCompositionMode.cs

@ -4,55 +4,15 @@
namespace SixLabors.ImageSharp.PixelFormats namespace SixLabors.ImageSharp.PixelFormats
{ {
/// <summary> /// <summary>
/// Enumerates the various blending modes. /// Enumerates the various alpha composition modes.
/// </summary> /// </summary>
public enum PixelBlenderMode public enum PixelAlphaCompositionMode
{ {
/// <summary> /// <summary>
/// Default blending mode, also known as "Normal" or "Alpha Blending" /// returns the destination over the source.
/// </summary>
Normal = 0,
/// <summary>
/// Blends the 2 values by multiplication.
/// </summary>
Multiply,
/// <summary>
/// Blends the 2 values by addition.
/// </summary>
Add,
/// <summary>
/// Blends the 2 values by subtraction.
/// </summary>
Subtract,
/// <summary>
/// Multiplies the complements of the backdrop and source values, then complements the result.
/// </summary>
Screen,
/// <summary>
/// Selects the minimum of the backdrop and source values.
/// </summary>
Darken,
/// <summary>
/// Selects the max of the backdrop and source values.
/// </summary>
Lighten,
/// <summary>
/// Multiplies or screens the values, depending on the backdrop vector values.
/// </summary>
Overlay,
/// <summary>
/// Multiplies or screens the colors, depending on the source value.
/// </summary> /// </summary>
HardLight, SrcOver = 0,
/// <summary> /// <summary>
/// returns the source colors. /// returns the source colors.
/// </summary> /// </summary>
@ -61,22 +21,17 @@ namespace SixLabors.ImageSharp.PixelFormats
/// <summary> /// <summary>
/// returns the source over the destination. /// returns the source over the destination.
/// </summary> /// </summary>
Atop, SrcAtop,
/// <summary>
/// returns the destination over the source.
/// </summary>
Over,
/// <summary> /// <summary>
/// The source where the destination and source overlap. /// The source where the destination and source overlap.
/// </summary> /// </summary>
In, SrcIn,
/// <summary> /// <summary>
/// The destination where the destination and source overlap. /// The destination where the destination and source overlap.
/// </summary> /// </summary>
Out, SrcOut,
/// <summary> /// <summary>
/// The destination where the source does not overlap it. /// The destination where the source does not overlap it.

56
src/ImageSharp/PixelFormats/PixelColorBlendingMode.cs

@ -0,0 +1,56 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.PixelFormats
{
/// <summary>
/// Enumerates the various color blending modes.
/// </summary>
public enum PixelColorBlendingMode
{
/// <summary>
/// Default blending mode, also known as "Normal" or "Alpha Blending"
/// </summary>
Normal = 0,
/// <summary>
/// Blends the 2 values by multiplication.
/// </summary>
Multiply,
/// <summary>
/// Blends the 2 values by addition.
/// </summary>
Add,
/// <summary>
/// Blends the 2 values by subtraction.
/// </summary>
Subtract,
/// <summary>
/// Multiplies the complements of the backdrop and source values, then complements the result.
/// </summary>
Screen,
/// <summary>
/// Selects the minimum of the backdrop and source values.
/// </summary>
Darken,
/// <summary>
/// Selects the max of the backdrop and source values.
/// </summary>
Lighten,
/// <summary>
/// Multiplies or screens the values, depending on the backdrop vector values.
/// </summary>
Overlay,
/// <summary>
/// Multiplies or screens the colors, depending on the source value.
/// </summary>
HardLight,
}
}

265
src/ImageSharp/PixelFormats/PixelOperations{TPixel}.PixelBenders.cs

@ -1,50 +1,217 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats.PixelBlenders; using SixLabors.ImageSharp.PixelFormats.PixelBlenders;
namespace SixLabors.ImageSharp.PixelFormats namespace SixLabors.ImageSharp.PixelFormats
{ {
/// <content> /// <content>
/// Provides access to pixel blenders /// Provides access to pixel blenders
/// </content> /// </content>
public partial class PixelOperations<TPixel> public partial class PixelOperations<TPixel>
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
/// <summary> /// <summary>
/// Find an instance of the pixel blender. /// Find an instance of the pixel blender.
/// </summary> /// </summary>
/// <param name="mode">The blending mode to apply</param> /// <param name="options">the blending and composition to apply</param>
/// <returns>A <see cref="PixelBlender{TPixel}"/>.</returns> /// <returns>A <see cref="PixelBlender{TPixel}"/>.</returns>
internal virtual PixelBlender<TPixel> GetPixelBlender(PixelBlenderMode mode) internal PixelBlender<TPixel> GetPixelBlender(GraphicsOptions options)
{ {
switch (mode) return this.GetPixelBlender(options.ColorBlendingMode, options.AlphaCompositionMode);
{ }
case PixelBlenderMode.Multiply: return DefaultPixelBlenders<TPixel>.MultiplySrcOver.Instance;
case PixelBlenderMode.Add: return DefaultPixelBlenders<TPixel>.AddSrcOver.Instance; /// <summary>
case PixelBlenderMode.Subtract: return DefaultPixelBlenders<TPixel>.SubtractSrcOver.Instance; /// Find an instance of the pixel blender.
case PixelBlenderMode.Screen: return DefaultPixelBlenders<TPixel>.ScreenSrcOver.Instance; /// </summary>
case PixelBlenderMode.Darken: return DefaultPixelBlenders<TPixel>.DarkenSrcOver.Instance; /// <param name="colorMode">The color blending mode to apply</param>
case PixelBlenderMode.Lighten: return DefaultPixelBlenders<TPixel>.LightenSrcOver.Instance; /// <param name="alphaMode">The alpha composition mode to apply</param>
case PixelBlenderMode.Overlay: return DefaultPixelBlenders<TPixel>.OverlaySrcOver.Instance; /// <returns>A <see cref="PixelBlender{TPixel}"/>.</returns>
case PixelBlenderMode.HardLight: return DefaultPixelBlenders<TPixel>.HardLightSrcOver.Instance; internal virtual PixelBlender<TPixel> GetPixelBlender(PixelColorBlendingMode colorMode, PixelAlphaCompositionMode alphaMode)
case PixelBlenderMode.Src: return DefaultPixelBlenders<TPixel>.NormalSrc.Instance; {
case PixelBlenderMode.Atop: return DefaultPixelBlenders<TPixel>.NormalSrcAtop.Instance; switch (alphaMode)
case PixelBlenderMode.Over: return DefaultPixelBlenders<TPixel>.NormalSrcOver.Instance; {
case PixelBlenderMode.In: return DefaultPixelBlenders<TPixel>.NormalSrcIn.Instance; case PixelAlphaCompositionMode.Clear:
case PixelBlenderMode.Out: return DefaultPixelBlenders<TPixel>.NormalSrcOut.Instance; switch (colorMode)
case PixelBlenderMode.Dest: return DefaultPixelBlenders<TPixel>.NormalDest.Instance; {
case PixelBlenderMode.DestAtop: return DefaultPixelBlenders<TPixel>.NormalDestAtop.Instance; case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders<TPixel>.MultiplyClear.Instance;
case PixelBlenderMode.DestOver: return DefaultPixelBlenders<TPixel>.NormalDestOver.Instance; case PixelColorBlendingMode.Add: return DefaultPixelBlenders<TPixel>.AddClear.Instance;
case PixelBlenderMode.DestIn: return DefaultPixelBlenders<TPixel>.NormalDestIn.Instance; case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders<TPixel>.SubtractClear.Instance;
case PixelBlenderMode.DestOut: return DefaultPixelBlenders<TPixel>.NormalDestOut.Instance; case PixelColorBlendingMode.Screen: return DefaultPixelBlenders<TPixel>.ScreenClear.Instance;
case PixelBlenderMode.Clear: return DefaultPixelBlenders<TPixel>.NormalClear.Instance; case PixelColorBlendingMode.Darken: return DefaultPixelBlenders<TPixel>.DarkenClear.Instance;
case PixelBlenderMode.Xor: return DefaultPixelBlenders<TPixel>.NormalXor.Instance; case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders<TPixel>.LightenClear.Instance;
case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders<TPixel>.OverlayClear.Instance;
case PixelBlenderMode.Normal: case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders<TPixel>.HardLightClear.Instance;
default: case PixelColorBlendingMode.Normal:
return DefaultPixelBlenders<TPixel>.NormalSrcOver.Instance; default: return DefaultPixelBlenders<TPixel>.NormalClear.Instance;
} }
}
} case PixelAlphaCompositionMode.Xor:
switch (colorMode)
{
case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders<TPixel>.MultiplyXor.Instance;
case PixelColorBlendingMode.Add: return DefaultPixelBlenders<TPixel>.AddXor.Instance;
case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders<TPixel>.SubtractXor.Instance;
case PixelColorBlendingMode.Screen: return DefaultPixelBlenders<TPixel>.ScreenXor.Instance;
case PixelColorBlendingMode.Darken: return DefaultPixelBlenders<TPixel>.DarkenXor.Instance;
case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders<TPixel>.LightenXor.Instance;
case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders<TPixel>.OverlayXor.Instance;
case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders<TPixel>.HardLightXor.Instance;
case PixelColorBlendingMode.Normal:
default: return DefaultPixelBlenders<TPixel>.NormalXor.Instance;
}
case PixelAlphaCompositionMode.Src:
switch (colorMode)
{
case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders<TPixel>.MultiplySrc.Instance;
case PixelColorBlendingMode.Add: return DefaultPixelBlenders<TPixel>.AddSrc.Instance;
case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders<TPixel>.SubtractSrc.Instance;
case PixelColorBlendingMode.Screen: return DefaultPixelBlenders<TPixel>.ScreenSrc.Instance;
case PixelColorBlendingMode.Darken: return DefaultPixelBlenders<TPixel>.DarkenSrc.Instance;
case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders<TPixel>.LightenSrc.Instance;
case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders<TPixel>.OverlaySrc.Instance;
case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders<TPixel>.HardLightSrc.Instance;
case PixelColorBlendingMode.Normal:
default: return DefaultPixelBlenders<TPixel>.NormalSrc.Instance;
}
case PixelAlphaCompositionMode.SrcAtop:
switch (colorMode)
{
case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders<TPixel>.MultiplySrcAtop.Instance;
case PixelColorBlendingMode.Add: return DefaultPixelBlenders<TPixel>.AddSrcAtop.Instance;
case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders<TPixel>.SubtractSrcAtop.Instance;
case PixelColorBlendingMode.Screen: return DefaultPixelBlenders<TPixel>.ScreenSrcAtop.Instance;
case PixelColorBlendingMode.Darken: return DefaultPixelBlenders<TPixel>.DarkenSrcAtop.Instance;
case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders<TPixel>.LightenSrcAtop.Instance;
case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders<TPixel>.OverlaySrcAtop.Instance;
case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders<TPixel>.HardLightSrcAtop.Instance;
case PixelColorBlendingMode.Normal:
default: return DefaultPixelBlenders<TPixel>.NormalSrcAtop.Instance;
}
case PixelAlphaCompositionMode.SrcIn:
switch (colorMode)
{
case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders<TPixel>.MultiplySrcIn.Instance;
case PixelColorBlendingMode.Add: return DefaultPixelBlenders<TPixel>.AddSrcIn.Instance;
case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders<TPixel>.SubtractSrcIn.Instance;
case PixelColorBlendingMode.Screen: return DefaultPixelBlenders<TPixel>.ScreenSrcIn.Instance;
case PixelColorBlendingMode.Darken: return DefaultPixelBlenders<TPixel>.DarkenSrcIn.Instance;
case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders<TPixel>.LightenSrcIn.Instance;
case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders<TPixel>.OverlaySrcIn.Instance;
case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders<TPixel>.HardLightSrcIn.Instance;
case PixelColorBlendingMode.Normal:
default: return DefaultPixelBlenders<TPixel>.NormalSrcIn.Instance;
}
case PixelAlphaCompositionMode.SrcOut:
switch (colorMode)
{
case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders<TPixel>.MultiplySrcOut.Instance;
case PixelColorBlendingMode.Add: return DefaultPixelBlenders<TPixel>.AddSrcOut.Instance;
case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders<TPixel>.SubtractSrcOut.Instance;
case PixelColorBlendingMode.Screen: return DefaultPixelBlenders<TPixel>.ScreenSrcOut.Instance;
case PixelColorBlendingMode.Darken: return DefaultPixelBlenders<TPixel>.DarkenSrcOut.Instance;
case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders<TPixel>.LightenSrcOut.Instance;
case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders<TPixel>.OverlaySrcOut.Instance;
case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders<TPixel>.HardLightSrcOut.Instance;
case PixelColorBlendingMode.Normal:
default: return DefaultPixelBlenders<TPixel>.NormalSrcOut.Instance;
}
case PixelAlphaCompositionMode.Dest:
switch (colorMode)
{
case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders<TPixel>.MultiplyDest.Instance;
case PixelColorBlendingMode.Add: return DefaultPixelBlenders<TPixel>.AddDest.Instance;
case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders<TPixel>.SubtractDest.Instance;
case PixelColorBlendingMode.Screen: return DefaultPixelBlenders<TPixel>.ScreenDest.Instance;
case PixelColorBlendingMode.Darken: return DefaultPixelBlenders<TPixel>.DarkenDest.Instance;
case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders<TPixel>.LightenDest.Instance;
case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders<TPixel>.OverlayDest.Instance;
case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders<TPixel>.HardLightDest.Instance;
case PixelColorBlendingMode.Normal:
default: return DefaultPixelBlenders<TPixel>.NormalDest.Instance;
}
case PixelAlphaCompositionMode.DestAtop:
switch (colorMode)
{
case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders<TPixel>.MultiplyDestAtop.Instance;
case PixelColorBlendingMode.Add: return DefaultPixelBlenders<TPixel>.AddDestAtop.Instance;
case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders<TPixel>.SubtractDestAtop.Instance;
case PixelColorBlendingMode.Screen: return DefaultPixelBlenders<TPixel>.ScreenDestAtop.Instance;
case PixelColorBlendingMode.Darken: return DefaultPixelBlenders<TPixel>.DarkenDestAtop.Instance;
case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders<TPixel>.LightenDestAtop.Instance;
case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders<TPixel>.OverlayDestAtop.Instance;
case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders<TPixel>.HardLightDestAtop.Instance;
case PixelColorBlendingMode.Normal:
default: return DefaultPixelBlenders<TPixel>.NormalDestAtop.Instance;
}
case PixelAlphaCompositionMode.DestIn:
switch (colorMode)
{
case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders<TPixel>.MultiplyDestIn.Instance;
case PixelColorBlendingMode.Add: return DefaultPixelBlenders<TPixel>.AddDestIn.Instance;
case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders<TPixel>.SubtractDestIn.Instance;
case PixelColorBlendingMode.Screen: return DefaultPixelBlenders<TPixel>.ScreenDestIn.Instance;
case PixelColorBlendingMode.Darken: return DefaultPixelBlenders<TPixel>.DarkenDestIn.Instance;
case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders<TPixel>.LightenDestIn.Instance;
case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders<TPixel>.OverlayDestIn.Instance;
case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders<TPixel>.HardLightDestIn.Instance;
case PixelColorBlendingMode.Normal:
default: return DefaultPixelBlenders<TPixel>.NormalDestIn.Instance;
}
case PixelAlphaCompositionMode.DestOut:
switch (colorMode)
{
case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders<TPixel>.MultiplyDestOut.Instance;
case PixelColorBlendingMode.Add: return DefaultPixelBlenders<TPixel>.AddDestOut.Instance;
case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders<TPixel>.SubtractDestOut.Instance;
case PixelColorBlendingMode.Screen: return DefaultPixelBlenders<TPixel>.ScreenDestOut.Instance;
case PixelColorBlendingMode.Darken: return DefaultPixelBlenders<TPixel>.DarkenDestOut.Instance;
case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders<TPixel>.LightenDestOut.Instance;
case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders<TPixel>.OverlayDestOut.Instance;
case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders<TPixel>.HardLightDestOut.Instance;
case PixelColorBlendingMode.Normal:
default: return DefaultPixelBlenders<TPixel>.NormalDestOut.Instance;
}
case PixelAlphaCompositionMode.DestOver:
switch (colorMode)
{
case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders<TPixel>.MultiplyDestOver.Instance;
case PixelColorBlendingMode.Add: return DefaultPixelBlenders<TPixel>.AddDestOver.Instance;
case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders<TPixel>.SubtractDestOver.Instance;
case PixelColorBlendingMode.Screen: return DefaultPixelBlenders<TPixel>.ScreenDestOver.Instance;
case PixelColorBlendingMode.Darken: return DefaultPixelBlenders<TPixel>.DarkenDestOver.Instance;
case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders<TPixel>.LightenDestOver.Instance;
case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders<TPixel>.OverlayDestOver.Instance;
case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders<TPixel>.HardLightDestOver.Instance;
case PixelColorBlendingMode.Normal:
default: return DefaultPixelBlenders<TPixel>.NormalDestOver.Instance;
}
case PixelAlphaCompositionMode.SrcOver:
default:
switch (colorMode)
{
case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders<TPixel>.MultiplySrcOver.Instance;
case PixelColorBlendingMode.Add: return DefaultPixelBlenders<TPixel>.AddSrcOver.Instance;
case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders<TPixel>.SubtractSrcOver.Instance;
case PixelColorBlendingMode.Screen: return DefaultPixelBlenders<TPixel>.ScreenSrcOver.Instance;
case PixelColorBlendingMode.Darken: return DefaultPixelBlenders<TPixel>.DarkenSrcOver.Instance;
case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders<TPixel>.LightenSrcOver.Instance;
case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders<TPixel>.OverlaySrcOver.Instance;
case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders<TPixel>.HardLightSrcOver.Instance;
case PixelColorBlendingMode.Normal:
default: return DefaultPixelBlenders<TPixel>.NormalSrcOver.Instance;
}
}
}
}
} }

2
src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor.cs

@ -81,7 +81,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays
amountSpan[i] = this.GraphicsOptions.BlendPercentage; amountSpan[i] = this.GraphicsOptions.BlendPercentage;
} }
PixelBlender<TPixel> blender = PixelOperations<TPixel>.Instance.GetPixelBlender(this.GraphicsOptions.BlenderMode); PixelBlender<TPixel> blender = PixelOperations<TPixel>.Instance.GetPixelBlender(this.GraphicsOptions);
ParallelFor.WithConfiguration( ParallelFor.WithConfiguration(
minY, minY,
maxY, maxY,

2
src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs

@ -62,7 +62,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays
{ {
this.GlowColor = color; this.GlowColor = color;
this.Radius = radius; this.Radius = radius;
this.blender = PixelOperations<TPixel>.Instance.GetPixelBlender(options.BlenderMode); this.blender = PixelOperations<TPixel>.Instance.GetPixelBlender(options);
this.GraphicsOptions = options; this.GraphicsOptions = options;
} }

4
src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs

@ -41,7 +41,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays
{ {
this.VignetteColor = color; this.VignetteColor = color;
this.GraphicsOptions = options; this.GraphicsOptions = options;
this.blender = PixelOperations<TPixel>.Instance.GetPixelBlender(options.BlenderMode); this.blender = PixelOperations<TPixel>.Instance.GetPixelBlender(options);
} }
/// <summary> /// <summary>
@ -56,7 +56,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays
this.VignetteColor = color; this.VignetteColor = color;
this.RadiusX = radiusX; this.RadiusX = radiusX;
this.RadiusY = radiusY; this.RadiusY = radiusY;
this.blender = PixelOperations<TPixel>.Instance.GetPixelBlender(options.BlenderMode); this.blender = PixelOperations<TPixel>.Instance.GetPixelBlender(options);
this.GraphicsOptions = options; this.GraphicsOptions = options;
} }

32
tests/ImageSharp.Tests/Drawing/DrawImageTest.cs

@ -25,30 +25,30 @@ namespace SixLabors.ImageSharp.Tests
}; };
[Theory] [Theory]
[WithFileCollection(nameof(TestFiles), PixelTypes, PixelBlenderMode.Normal)] [WithFileCollection(nameof(TestFiles), PixelTypes, PixelColorBlendingMode.Normal)]
[WithFileCollection(nameof(TestFiles), PixelTypes, PixelBlenderMode.Multiply)] [WithFileCollection(nameof(TestFiles), PixelTypes, PixelColorBlendingMode.Multiply)]
[WithFileCollection(nameof(TestFiles), PixelTypes, PixelBlenderMode.Add)] [WithFileCollection(nameof(TestFiles), PixelTypes, PixelColorBlendingMode.Add)]
[WithFileCollection(nameof(TestFiles), PixelTypes, PixelBlenderMode.Subtract)] [WithFileCollection(nameof(TestFiles), PixelTypes, PixelColorBlendingMode.Subtract)]
[WithFileCollection(nameof(TestFiles), PixelTypes, PixelBlenderMode.Screen)] [WithFileCollection(nameof(TestFiles), PixelTypes, PixelColorBlendingMode.Screen)]
[WithFileCollection(nameof(TestFiles), PixelTypes, PixelBlenderMode.Darken)] [WithFileCollection(nameof(TestFiles), PixelTypes, PixelColorBlendingMode.Darken)]
[WithFileCollection(nameof(TestFiles), PixelTypes, PixelBlenderMode.Lighten)] [WithFileCollection(nameof(TestFiles), PixelTypes, PixelColorBlendingMode.Lighten)]
[WithFileCollection(nameof(TestFiles), PixelTypes, PixelBlenderMode.Overlay)] [WithFileCollection(nameof(TestFiles), PixelTypes, PixelColorBlendingMode.Overlay)]
[WithFileCollection(nameof(TestFiles), PixelTypes, PixelBlenderMode.HardLight)] [WithFileCollection(nameof(TestFiles), PixelTypes, PixelColorBlendingMode.HardLight)]
public void ImageShouldApplyDrawImage<TPixel>(TestImageProvider<TPixel> provider, PixelBlenderMode mode) public void ImageShouldApplyDrawImage<TPixel>(TestImageProvider<TPixel> provider, PixelColorBlendingMode mode)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
using (Image<TPixel> image = provider.GetImage()) using (Image<TPixel> image = provider.GetImage())
using (var blend = Image.Load<TPixel>(TestFile.Create(TestImages.Bmp.Car).Bytes)) using (var blend = Image.Load<TPixel>(TestFile.Create(TestImages.Bmp.Car).Bytes))
{ {
blend.Mutate(x => x.Resize(image.Width / 2, image.Height / 2)); blend.Mutate(x => x.Resize(image.Width / 2, image.Height / 2));
image.Mutate(x => x.DrawImage(blend, mode, .75f, new Point(image.Width / 4, image.Height / 4))); image.Mutate(x => x.DrawImage(blend, new Point(image.Width / 4, image.Height / 4), mode, .75f) );
image.DebugSave(provider, new { mode }); image.DebugSave(provider, new { mode });
} }
} }
[Theory] [Theory]
[WithFileCollection(nameof(TestFiles), PixelTypes, PixelBlenderMode.Normal)] [WithFileCollection(nameof(TestFiles), PixelTypes, PixelColorBlendingMode.Normal)]
public void ImageShouldDrawTransformedImage<TPixel>(TestImageProvider<TPixel> provider, PixelBlenderMode mode) public void ImageShouldDrawTransformedImage<TPixel>(TestImageProvider<TPixel> provider, PixelColorBlendingMode mode)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
using (Image<TPixel> image = provider.GetImage()) using (Image<TPixel> image = provider.GetImage())
@ -70,7 +70,7 @@ namespace SixLabors.ImageSharp.Tests
new Rectangle(0, 0, destBounds.Width, destBounds.Height))); new Rectangle(0, 0, destBounds.Width, destBounds.Height)));
var position = new Point((image.Width - blend.Width) / 2, (image.Height - blend.Height) / 2); var position = new Point((image.Width - blend.Width) / 2, (image.Height - blend.Height) / 2);
image.Mutate(x => x.DrawImage(blend, mode, .75F, position)); image.Mutate(x => x.DrawImage(blend, position, mode, .75F));
image.DebugSave(provider, new[] { "Transformed" }); image.DebugSave(provider, new[] { "Transformed" });
} }
} }
@ -88,7 +88,7 @@ namespace SixLabors.ImageSharp.Tests
Rgba32 backgroundPixel = background[0, 0]; Rgba32 backgroundPixel = background[0, 0];
Rgba32 overlayPixel = overlay[Math.Abs(xy) + 1, Math.Abs(xy) + 1]; Rgba32 overlayPixel = overlay[Math.Abs(xy) + 1, Math.Abs(xy) + 1];
background.Mutate(x => x.DrawImage(overlay, PixelBlenderMode.Normal, 1F, new Point(xy, xy))); background.Mutate(x => x.DrawImage(overlay, new Point(xy, xy), PixelColorBlendingMode.Normal, 1F));
Assert.Equal(Rgba32.White, backgroundPixel); Assert.Equal(Rgba32.White, backgroundPixel);
Assert.Equal(overlayPixel, background[0, 0]); Assert.Equal(overlayPixel, background[0, 0]);
@ -110,7 +110,7 @@ namespace SixLabors.ImageSharp.Tests
Rgba32 backgroundPixel = background[xy - 1, xy - 1]; Rgba32 backgroundPixel = background[xy - 1, xy - 1];
Rgba32 overlayPixel = overlay[0, 0]; Rgba32 overlayPixel = overlay[0, 0];
background.Mutate(x => x.DrawImage(overlay, PixelBlenderMode.Normal, 1F, new Point(xy, xy))); background.Mutate(x => x.DrawImage(overlay, new Point(xy, xy), PixelColorBlendingMode.Normal, 1F));
Assert.Equal(Rgba32.White, backgroundPixel); Assert.Equal(Rgba32.White, backgroundPixel);
Assert.Equal(overlayPixel, background[xy, xy]); Assert.Equal(overlayPixel, background[xy, xy]);

68
tests/ImageSharp.Tests/Drawing/FillSolidBrushTests.cs

@ -98,38 +98,38 @@ namespace SixLabors.ImageSharp.Tests.Drawing
useReferenceOutputFrom: nameof(this.FillRegion)); useReferenceOutputFrom: nameof(this.FillRegion));
} }
public static readonly TheoryData<bool, string, float, PixelBlenderMode, float> BlendData = public static readonly TheoryData<bool, string, float, PixelColorBlendingMode, float> BlendData =
new TheoryData<bool, string, float, PixelBlenderMode, float>() new TheoryData<bool, string, float, PixelColorBlendingMode, float>()
{ {
{ false, "Blue", 0.5f, PixelBlenderMode.Normal, 1.0f }, { false, "Blue", 0.5f, PixelColorBlendingMode.Normal, 1.0f },
{ false, "Blue", 1.0f, PixelBlenderMode.Normal, 0.5f }, { false, "Blue", 1.0f, PixelColorBlendingMode.Normal, 0.5f },
{ false, "Green", 0.5f, PixelBlenderMode.Normal, 0.3f }, { false, "Green", 0.5f, PixelColorBlendingMode.Normal, 0.3f },
{ false, "HotPink", 0.8f, PixelBlenderMode.Normal, 0.8f }, { false, "HotPink", 0.8f, PixelColorBlendingMode.Normal, 0.8f },
{ false, "Blue", 0.5f, PixelBlenderMode.Multiply, 1.0f }, { false, "Blue", 0.5f, PixelColorBlendingMode.Multiply, 1.0f },
{ false, "Blue", 1.0f, PixelBlenderMode.Multiply, 0.5f }, { false, "Blue", 1.0f, PixelColorBlendingMode.Multiply, 0.5f },
{ false, "Green", 0.5f, PixelBlenderMode.Multiply, 0.3f }, { false, "Green", 0.5f, PixelColorBlendingMode.Multiply, 0.3f },
{ false, "HotPink", 0.8f, PixelBlenderMode.Multiply, 0.8f }, { false, "HotPink", 0.8f, PixelColorBlendingMode.Multiply, 0.8f },
{ false, "Blue", 0.5f, PixelBlenderMode.Add, 1.0f }, { false, "Blue", 0.5f, PixelColorBlendingMode.Add, 1.0f },
{ false, "Blue", 1.0f, PixelBlenderMode.Add, 0.5f }, { false, "Blue", 1.0f, PixelColorBlendingMode.Add, 0.5f },
{ false, "Green", 0.5f, PixelBlenderMode.Add, 0.3f }, { false, "Green", 0.5f, PixelColorBlendingMode.Add, 0.3f },
{ false, "HotPink", 0.8f, PixelBlenderMode.Add, 0.8f }, { false, "HotPink", 0.8f, PixelColorBlendingMode.Add, 0.8f },
{ true, "Blue", 0.5f, PixelBlenderMode.Normal, 1.0f }, { true, "Blue", 0.5f, PixelColorBlendingMode.Normal, 1.0f },
{ true, "Blue", 1.0f, PixelBlenderMode.Normal, 0.5f }, { true, "Blue", 1.0f, PixelColorBlendingMode.Normal, 0.5f },
{ true, "Green", 0.5f, PixelBlenderMode.Normal, 0.3f }, { true, "Green", 0.5f, PixelColorBlendingMode.Normal, 0.3f },
{ true, "HotPink", 0.8f, PixelBlenderMode.Normal, 0.8f }, { true, "HotPink", 0.8f, PixelColorBlendingMode.Normal, 0.8f },
{ true, "Blue", 0.5f, PixelBlenderMode.Multiply, 1.0f }, { true, "Blue", 0.5f, PixelColorBlendingMode.Multiply, 1.0f },
{ true, "Blue", 1.0f, PixelBlenderMode.Multiply, 0.5f }, { true, "Blue", 1.0f, PixelColorBlendingMode.Multiply, 0.5f },
{ true, "Green", 0.5f, PixelBlenderMode.Multiply, 0.3f }, { true, "Green", 0.5f, PixelColorBlendingMode.Multiply, 0.3f },
{ true, "HotPink", 0.8f, PixelBlenderMode.Multiply, 0.8f }, { true, "HotPink", 0.8f, PixelColorBlendingMode.Multiply, 0.8f },
{ true, "Blue", 0.5f, PixelBlenderMode.Add, 1.0f }, { true, "Blue", 0.5f, PixelColorBlendingMode.Add, 1.0f },
{ true, "Blue", 1.0f, PixelBlenderMode.Add, 0.5f }, { true, "Blue", 1.0f, PixelColorBlendingMode.Add, 0.5f },
{ true, "Green", 0.5f, PixelBlenderMode.Add, 0.3f }, { true, "Green", 0.5f, PixelColorBlendingMode.Add, 0.3f },
{ true, "HotPink", 0.8f, PixelBlenderMode.Add, 0.8f }, { true, "HotPink", 0.8f, PixelColorBlendingMode.Add, 0.8f },
}; };
[Theory] [Theory]
@ -139,7 +139,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing
bool triggerFillRegion, bool triggerFillRegion,
string newColorName, string newColorName,
float alpha, float alpha,
PixelBlenderMode blenderMode, PixelColorBlendingMode blenderMode,
float blendPercentage) float blendPercentage)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
@ -155,7 +155,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing
var options = new GraphicsOptions(false) var options = new GraphicsOptions(false)
{ {
BlenderMode = blenderMode, ColorBlendingMode = blenderMode,
BlendPercentage = blendPercentage BlendPercentage = blendPercentage
}; };
@ -185,7 +185,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing
appendPixelTypeToFileName: false, appendPixelTypeToFileName: false,
appendSourceFileOrDescription: false); appendSourceFileOrDescription: false);
PixelBlender<TPixel> blender = PixelOperations<TPixel>.Instance.GetPixelBlender(blenderMode); PixelBlender<TPixel> blender = PixelOperations<TPixel>.Instance.GetPixelBlender(blenderMode, PixelAlphaCompositionMode.SrcOver);
TPixel expectedPixel = blender.Blend(bgColor, fillColor, blendPercentage); TPixel expectedPixel = blender.Blend(bgColor, fillColor, blendPercentage);
image.ComparePixelBufferTo(expectedPixel); image.ComparePixelBufferTo(expectedPixel);

65
tests/ImageSharp.Tests/Drawing/SolidFillBlendedShapesTests.cs

@ -15,14 +15,26 @@ namespace SixLabors.ImageSharp.Tests.Drawing
[GroupOutput("Drawing")] [GroupOutput("Drawing")]
public class SolidFillBlendedShapesTests public class SolidFillBlendedShapesTests
{ {
public static IEnumerable<object[]> modes = public static IEnumerable<object[]> modes = GetAllModeCombinations();
((PixelBlenderMode[])Enum.GetValues(typeof(PixelBlenderMode))).Select(x => new object[] { x });
private static IEnumerable<object[]> GetAllModeCombinations()
{
foreach (var composition in Enum.GetValues(typeof(PixelAlphaCompositionMode)))
{
foreach (var blending in Enum.GetValues(typeof(PixelColorBlendingMode)))
{
yield return new object[] { blending, composition };
}
}
}
[Theory] [Theory]
[WithBlankImages(nameof(modes), 250, 250, PixelTypes.Rgba32)] [WithBlankImages(nameof(modes), 250, 250, PixelTypes.Rgba32)]
public void _1DarkBlueRect_2BlendHotPinkRect<TPixel>( public void _1DarkBlueRect_2BlendHotPinkRect<TPixel>(
TestImageProvider<TPixel> provider, TestImageProvider<TPixel> provider,
PixelBlenderMode mode) PixelColorBlendingMode blending,
PixelAlphaCompositionMode composition)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
using (Image<TPixel> img = provider.GetImage()) using (Image<TPixel> img = provider.GetImage())
@ -34,12 +46,12 @@ namespace SixLabors.ImageSharp.Tests.Drawing
NamedColors<TPixel>.DarkBlue, NamedColors<TPixel>.DarkBlue,
new Rectangle(0 * scaleX, 40 * scaleY, 100 * scaleX, 20 * scaleY) new Rectangle(0 * scaleX, 40 * scaleY, 100 * scaleX, 20 * scaleY)
) )
.Fill(new GraphicsOptions(true) { BlenderMode = mode }, .Fill(new GraphicsOptions(true) { ColorBlendingMode = blending, AlphaCompositionMode=composition },
NamedColors<TPixel>.HotPink, NamedColors<TPixel>.HotPink,
new Rectangle(20 * scaleX, 0 * scaleY, 30 * scaleX, 100 * scaleY)) new Rectangle(20 * scaleX, 0 * scaleY, 30 * scaleX, 100 * scaleY))
); );
VerifyImage(provider, mode, img); VerifyImage(provider, blending, composition, img);
} }
} }
@ -47,7 +59,8 @@ namespace SixLabors.ImageSharp.Tests.Drawing
[WithBlankImages(nameof(modes), 250, 250, PixelTypes.Rgba32)] [WithBlankImages(nameof(modes), 250, 250, PixelTypes.Rgba32)]
public void _1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse<TPixel>( public void _1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse<TPixel>(
TestImageProvider<TPixel> provider, TestImageProvider<TPixel> provider,
PixelBlenderMode mode) PixelColorBlendingMode blending,
PixelAlphaCompositionMode composition)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
using (Image<TPixel> img = provider.GetImage()) using (Image<TPixel> img = provider.GetImage())
@ -60,17 +73,17 @@ namespace SixLabors.ImageSharp.Tests.Drawing
new Rectangle(0 * scaleX, 40 * scaleY, 100 * scaleX, 20 * scaleY))); new Rectangle(0 * scaleX, 40 * scaleY, 100 * scaleX, 20 * scaleY)));
img.Mutate( img.Mutate(
x => x.Fill( x => x.Fill(
new GraphicsOptions(true) { BlenderMode = mode }, new GraphicsOptions(true) { ColorBlendingMode = blending, AlphaCompositionMode = composition },
NamedColors<TPixel>.HotPink, NamedColors<TPixel>.HotPink,
new Rectangle(20 * scaleX, 0 * scaleY, 30 * scaleX, 100 * scaleY))); new Rectangle(20 * scaleX, 0 * scaleY, 30 * scaleX, 100 * scaleY)));
img.Mutate( img.Mutate(
x => x.Fill( x => x.Fill(
new GraphicsOptions(true) { BlenderMode = mode }, new GraphicsOptions(true) { ColorBlendingMode = blending, AlphaCompositionMode = composition },
NamedColors<TPixel>.Transparent, NamedColors<TPixel>.Transparent,
new Shapes.EllipsePolygon(40 * scaleX, 50 * scaleY, 50 * scaleX, 50 * scaleY)) new Shapes.EllipsePolygon(40 * scaleX, 50 * scaleY, 50 * scaleX, 50 * scaleY))
); );
VerifyImage(provider, mode, img); VerifyImage(provider, blending, composition, img);
} }
} }
@ -78,7 +91,8 @@ namespace SixLabors.ImageSharp.Tests.Drawing
[WithBlankImages(nameof(modes), 250, 250, PixelTypes.Rgba32)] [WithBlankImages(nameof(modes), 250, 250, PixelTypes.Rgba32)]
public void _1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse<TPixel>( public void _1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse<TPixel>(
TestImageProvider<TPixel> provider, TestImageProvider<TPixel> provider,
PixelBlenderMode mode) PixelColorBlendingMode blending,
PixelAlphaCompositionMode composition)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
using (Image<TPixel> img = provider.GetImage()) using (Image<TPixel> img = provider.GetImage())
@ -91,7 +105,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing
new Rectangle(0 * scaleX, 40, 100 * scaleX, 20 * scaleY))); new Rectangle(0 * scaleX, 40, 100 * scaleX, 20 * scaleY)));
img.Mutate( img.Mutate(
x => x.Fill( x => x.Fill(
new GraphicsOptions(true) { BlenderMode = mode }, new GraphicsOptions(true) { ColorBlendingMode = blending, AlphaCompositionMode = composition },
NamedColors<TPixel>.HotPink, NamedColors<TPixel>.HotPink,
new Rectangle(20 * scaleX, 0, 30 * scaleX, 100 * scaleY))); new Rectangle(20 * scaleX, 0, 30 * scaleX, 100 * scaleY)));
var c = NamedColors<TPixel>.Red.ToVector4(); var c = NamedColors<TPixel>.Red.ToVector4();
@ -101,18 +115,21 @@ namespace SixLabors.ImageSharp.Tests.Drawing
img.Mutate( img.Mutate(
x => x.Fill( x => x.Fill(
new GraphicsOptions(true) { BlenderMode = mode }, new GraphicsOptions(true) { ColorBlendingMode = blending, AlphaCompositionMode = composition },
pixel, pixel,
new Shapes.EllipsePolygon(40 * scaleX, 50 * scaleY, 50 * scaleX, 50 * scaleY)) new Shapes.EllipsePolygon(40 * scaleX, 50 * scaleY, 50 * scaleX, 50 * scaleY))
); );
VerifyImage(provider, mode, img); ; VerifyImage(provider, blending, composition, img); ;
} }
} }
[Theory] [Theory]
[WithBlankImages(nameof(modes), 250, 250, PixelTypes.Rgba32)] [WithBlankImages(nameof(modes), 250, 250, PixelTypes.Rgba32)]
public void _1DarkBlueRect_2BlendBlackEllipse<TPixel>(TestImageProvider<TPixel> provider, PixelBlenderMode mode) public void _1DarkBlueRect_2BlendBlackEllipse<TPixel>(
TestImageProvider<TPixel> provider,
PixelColorBlendingMode blending,
PixelAlphaCompositionMode composition)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
using(Image<TPixel> dstImg = provider.GetImage(), srcImg = provider.GetImage()) using(Image<TPixel> dstImg = provider.GetImage(), srcImg = provider.GetImage())
@ -131,28 +148,32 @@ namespace SixLabors.ImageSharp.Tests.Drawing
new Shapes.EllipsePolygon(40 * scaleX, 50 * scaleY, 50 * scaleX, 50 * scaleY))); new Shapes.EllipsePolygon(40 * scaleX, 50 * scaleY, 50 * scaleX, 50 * scaleY)));
dstImg.Mutate( dstImg.Mutate(
x => x.DrawImage(new GraphicsOptions(true) { BlenderMode = mode }, srcImg) x => x.DrawImage(srcImg, new GraphicsOptions(true) { ColorBlendingMode = blending, AlphaCompositionMode = composition })
); );
VerifyImage(provider, mode, dstImg); VerifyImage(provider, blending, composition, dstImg);
} }
} }
private static void VerifyImage<TPixel>(TestImageProvider<TPixel> provider, PixelBlenderMode mode, Image<TPixel> img) private static void VerifyImage<TPixel>(
TestImageProvider<TPixel> provider,
PixelColorBlendingMode blending,
PixelAlphaCompositionMode composition,
Image<TPixel> img)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
img.DebugSave( img.DebugSave(
provider, provider,
new { mode }, new { composition, blending },
appendPixelTypeToFileName: false, appendPixelTypeToFileName: false,
appendSourceFileOrDescription: false); appendSourceFileOrDescription: false);
var comparer = ImageComparer.TolerantPercentage(0.01f, 3); var comparer = ImageComparer.TolerantPercentage(0.01f, 3);
img.CompareFirstFrameToReferenceOutput(comparer, img.CompareFirstFrameToReferenceOutput(comparer,
provider, provider,
new { mode }, new { composition, blending },
appendPixelTypeToFileName: false, appendPixelTypeToFileName: false,
appendSourceFileOrDescription: false); appendSourceFileOrDescription: false);
} }
} }
} }

101
tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffCompositorTests.cs

@ -1,47 +1,56 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders
{ {
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing;
using Xunit; using Xunit;
public class PorterDuffCompositorTests public class PorterDuffCompositorTests
{ {
// TODO: Add other modes to compare. // TODO: Add other modes to compare.
public static readonly TheoryData<PixelBlenderMode> CompositingOperators = public static readonly TheoryData<PixelAlphaCompositionMode> CompositingOperators =
new TheoryData<PixelBlenderMode> new TheoryData<PixelAlphaCompositionMode>
{ {
PixelBlenderMode.Src, PixelAlphaCompositionMode.Src,
PixelBlenderMode.Atop, PixelAlphaCompositionMode.SrcAtop,
PixelBlenderMode.Over, PixelAlphaCompositionMode.SrcOver,
PixelBlenderMode.In, PixelAlphaCompositionMode.SrcIn,
PixelBlenderMode.Out, PixelAlphaCompositionMode.SrcOut,
PixelBlenderMode.Dest, PixelAlphaCompositionMode.Dest,
PixelBlenderMode.DestAtop, PixelAlphaCompositionMode.DestAtop,
PixelBlenderMode.DestOver, PixelAlphaCompositionMode.DestOver,
PixelBlenderMode.DestIn, PixelAlphaCompositionMode.DestIn,
PixelBlenderMode.DestOut, PixelAlphaCompositionMode.DestOut,
PixelBlenderMode.Clear, PixelAlphaCompositionMode.Clear,
PixelBlenderMode.Xor PixelAlphaCompositionMode.Xor
}; };
[Theory] [Theory]
[WithFile(TestImages.Png.PDDest, nameof(CompositingOperators), PixelTypes.Rgba32)] [WithFile(TestImages.Png.PDDest, nameof(CompositingOperators), PixelTypes.Rgba32)]
public void PorterDuffOutputIsCorrect(TestImageProvider<Rgba32> provider, PixelBlenderMode mode) public void PorterDuffOutputIsCorrect(TestImageProvider<Rgba32> provider, PixelAlphaCompositionMode mode)
{ {
var srcFile = TestFile.Create(TestImages.Png.PDSrc); var srcFile = TestFile.Create(TestImages.Png.PDSrc);
using (Image<Rgba32> src = srcFile.CreateImage()) using (Image<Rgba32> src = srcFile.CreateImage())
using (Image<Rgba32> dest = provider.GetImage()) using (Image<Rgba32> dest = provider.GetImage())
{ {
using (Image<Rgba32> res = dest.Clone(x => x.DrawImage(new GraphicsOptions { BlenderMode = mode }, src))) GraphicsOptions options = new GraphicsOptions
{ {
res.DebugSave(provider, mode.ToString()); AlphaCompositionMode = mode
res.CompareToReferenceOutput(provider, mode.ToString()); };
}
} using (Image<Rgba32> res = dest.Clone(x => x.DrawImage(src, options)))
} {
} string combinedMode = mode.ToString();
if (combinedMode != "Src" && combinedMode.StartsWith("Src")) combinedMode = combinedMode.Substring(3);
res.DebugSave(provider, combinedMode);
res.CompareToReferenceOutput(provider, combinedMode);
}
}
}
}
} }

74
tests/ImageSharp.Tests/PixelFormats/PixelOperationsTests.Blender.cs

@ -12,64 +12,36 @@ using Xunit;
namespace SixLabors.ImageSharp.Tests.PixelFormats namespace SixLabors.ImageSharp.Tests.PixelFormats
{ {
public class PixelBlenderTests public class PixelBlenderTests
{ {
public static TheoryData<object, Type, PixelColorBlendingMode> BlenderMappings = new TheoryData<object, Type, PixelColorBlendingMode>()
public static TheoryData<object, Type, PixelBlenderMode> BlenderMappings = new TheoryData<object, Type, PixelBlenderMode>()
{ {
{ new TestPixel<Rgba32>(), typeof(DefaultPixelBlenders<Rgba32>.NormalSrcOver), PixelBlenderMode.Normal }, { new TestPixel<Rgba32>(), typeof(DefaultPixelBlenders<Rgba32>.NormalSrcOver), PixelColorBlendingMode.Normal },
{ new TestPixel<Rgba32>(), typeof(DefaultPixelBlenders<Rgba32>.ScreenSrcOver), PixelBlenderMode.Screen }, { new TestPixel<Rgba32>(), typeof(DefaultPixelBlenders<Rgba32>.ScreenSrcOver), PixelColorBlendingMode.Screen },
{ new TestPixel<Rgba32>(), typeof(DefaultPixelBlenders<Rgba32>.HardLightSrcOver), PixelBlenderMode.HardLight }, { new TestPixel<Rgba32>(), typeof(DefaultPixelBlenders<Rgba32>.HardLightSrcOver), PixelColorBlendingMode.HardLight },
{ new TestPixel<Rgba32>(), typeof(DefaultPixelBlenders<Rgba32>.OverlaySrcOver), PixelBlenderMode.Overlay }, { new TestPixel<Rgba32>(), typeof(DefaultPixelBlenders<Rgba32>.OverlaySrcOver), PixelColorBlendingMode.Overlay },
{ new TestPixel<Rgba32>(), typeof(DefaultPixelBlenders<Rgba32>.DarkenSrcOver), PixelBlenderMode.Darken }, { new TestPixel<Rgba32>(), typeof(DefaultPixelBlenders<Rgba32>.DarkenSrcOver), PixelColorBlendingMode.Darken },
{ new TestPixel<Rgba32>(), typeof(DefaultPixelBlenders<Rgba32>.LightenSrcOver), PixelBlenderMode.Lighten }, { new TestPixel<Rgba32>(), typeof(DefaultPixelBlenders<Rgba32>.LightenSrcOver), PixelColorBlendingMode.Lighten },
{ new TestPixel<Rgba32>(), typeof(DefaultPixelBlenders<Rgba32>.AddSrcOver), PixelBlenderMode.Add }, { new TestPixel<Rgba32>(), typeof(DefaultPixelBlenders<Rgba32>.AddSrcOver), PixelColorBlendingMode.Add },
{ new TestPixel<Rgba32>(), typeof(DefaultPixelBlenders<Rgba32>.SubtractSrcOver), PixelBlenderMode.Subtract }, { new TestPixel<Rgba32>(), typeof(DefaultPixelBlenders<Rgba32>.SubtractSrcOver), PixelColorBlendingMode.Subtract },
{ new TestPixel<Rgba32>(), typeof(DefaultPixelBlenders<Rgba32>.MultiplySrcOver), PixelBlenderMode.Multiply }, { new TestPixel<Rgba32>(), typeof(DefaultPixelBlenders<Rgba32>.MultiplySrcOver), PixelColorBlendingMode.Multiply },
{ new TestPixel<Rgba32>(), typeof(DefaultPixelBlenders<Rgba32>.NormalSrc), PixelBlenderMode.Src }, { new TestPixel<RgbaVector>(), typeof(DefaultPixelBlenders<RgbaVector>.NormalSrcOver), PixelColorBlendingMode.Normal },
{ new TestPixel<Rgba32>(), typeof(DefaultPixelBlenders<Rgba32>.NormalSrcAtop), PixelBlenderMode.Atop }, { new TestPixel<RgbaVector>(), typeof(DefaultPixelBlenders<RgbaVector>.ScreenSrcOver), PixelColorBlendingMode.Screen },
{ new TestPixel<Rgba32>(), typeof(DefaultPixelBlenders<Rgba32>.NormalSrcOver), PixelBlenderMode.Over }, { new TestPixel<RgbaVector>(), typeof(DefaultPixelBlenders<RgbaVector>.HardLightSrcOver), PixelColorBlendingMode.HardLight },
{ new TestPixel<Rgba32>(), typeof(DefaultPixelBlenders<Rgba32>.NormalSrcIn), PixelBlenderMode.In }, { new TestPixel<RgbaVector>(), typeof(DefaultPixelBlenders<RgbaVector>.OverlaySrcOver), PixelColorBlendingMode.Overlay },
{ new TestPixel<Rgba32>(), typeof(DefaultPixelBlenders<Rgba32>.NormalSrcOut), PixelBlenderMode.Out }, { new TestPixel<RgbaVector>(), typeof(DefaultPixelBlenders<RgbaVector>.DarkenSrcOver), PixelColorBlendingMode.Darken },
{ new TestPixel<Rgba32>(), typeof(DefaultPixelBlenders<Rgba32>.NormalDest), PixelBlenderMode.Dest }, { new TestPixel<RgbaVector>(), typeof(DefaultPixelBlenders<RgbaVector>.LightenSrcOver), PixelColorBlendingMode.Lighten },
{ new TestPixel<Rgba32>(), typeof(DefaultPixelBlenders<Rgba32>.NormalDestAtop), PixelBlenderMode.DestAtop }, { new TestPixel<RgbaVector>(), typeof(DefaultPixelBlenders<RgbaVector>.AddSrcOver), PixelColorBlendingMode.Add },
{ new TestPixel<Rgba32>(), typeof(DefaultPixelBlenders<Rgba32>.NormalDestOver), PixelBlenderMode.DestOver }, { new TestPixel<RgbaVector>(), typeof(DefaultPixelBlenders<RgbaVector>.SubtractSrcOver), PixelColorBlendingMode.Subtract },
{ new TestPixel<Rgba32>(), typeof(DefaultPixelBlenders<Rgba32>.NormalDestIn), PixelBlenderMode.DestIn }, { new TestPixel<RgbaVector>(), typeof(DefaultPixelBlenders<RgbaVector>.MultiplySrcOver), PixelColorBlendingMode.Multiply },
{ new TestPixel<Rgba32>(), typeof(DefaultPixelBlenders<Rgba32>.NormalDestOut), PixelBlenderMode.DestOut },
{ new TestPixel<Rgba32>(), typeof(DefaultPixelBlenders<Rgba32>.NormalClear), PixelBlenderMode.Clear },
{ new TestPixel<Rgba32>(), typeof(DefaultPixelBlenders<Rgba32>.NormalXor), PixelBlenderMode.Xor },
{ new TestPixel<RgbaVector>(), typeof(DefaultPixelBlenders<RgbaVector>.NormalSrcOver), PixelBlenderMode.Normal },
{ new TestPixel<RgbaVector>(), typeof(DefaultPixelBlenders<RgbaVector>.ScreenSrcOver), PixelBlenderMode.Screen },
{ new TestPixel<RgbaVector>(), typeof(DefaultPixelBlenders<RgbaVector>.HardLightSrcOver), PixelBlenderMode.HardLight },
{ new TestPixel<RgbaVector>(), typeof(DefaultPixelBlenders<RgbaVector>.OverlaySrcOver), PixelBlenderMode.Overlay },
{ new TestPixel<RgbaVector>(), typeof(DefaultPixelBlenders<RgbaVector>.DarkenSrcOver), PixelBlenderMode.Darken },
{ new TestPixel<RgbaVector>(), typeof(DefaultPixelBlenders<RgbaVector>.LightenSrcOver), PixelBlenderMode.Lighten },
{ new TestPixel<RgbaVector>(), typeof(DefaultPixelBlenders<RgbaVector>.AddSrcOver), PixelBlenderMode.Add },
{ new TestPixel<RgbaVector>(), typeof(DefaultPixelBlenders<RgbaVector>.SubtractSrcOver), PixelBlenderMode.Subtract },
{ new TestPixel<RgbaVector>(), typeof(DefaultPixelBlenders<RgbaVector>.MultiplySrcOver), PixelBlenderMode.Multiply },
{ new TestPixel<RgbaVector>(), typeof(DefaultPixelBlenders<RgbaVector>.NormalSrc), PixelBlenderMode.Src },
{ new TestPixel<RgbaVector>(), typeof(DefaultPixelBlenders<RgbaVector>.NormalSrcAtop), PixelBlenderMode.Atop },
{ new TestPixel<RgbaVector>(), typeof(DefaultPixelBlenders<RgbaVector>.NormalSrcOver), PixelBlenderMode.Over },
{ new TestPixel<RgbaVector>(), typeof(DefaultPixelBlenders<RgbaVector>.NormalSrcIn), PixelBlenderMode.In },
{ new TestPixel<RgbaVector>(), typeof(DefaultPixelBlenders<RgbaVector>.NormalSrcOut), PixelBlenderMode.Out },
{ new TestPixel<RgbaVector>(), typeof(DefaultPixelBlenders<RgbaVector>.NormalDest), PixelBlenderMode.Dest },
{ new TestPixel<RgbaVector>(), typeof(DefaultPixelBlenders<RgbaVector>.NormalDestAtop), PixelBlenderMode.DestAtop },
{ new TestPixel<RgbaVector>(), typeof(DefaultPixelBlenders<RgbaVector>.NormalDestOver), PixelBlenderMode.DestOver },
{ new TestPixel<RgbaVector>(), typeof(DefaultPixelBlenders<RgbaVector>.NormalDestIn), PixelBlenderMode.DestIn },
{ new TestPixel<RgbaVector>(), typeof(DefaultPixelBlenders<RgbaVector>.NormalDestOut), PixelBlenderMode.DestOut },
{ new TestPixel<RgbaVector>(), typeof(DefaultPixelBlenders<RgbaVector>.NormalClear), PixelBlenderMode.Clear },
{ new TestPixel<RgbaVector>(), typeof(DefaultPixelBlenders<RgbaVector>.NormalXor), PixelBlenderMode.Xor },
}; };
[Theory] [Theory]
[MemberData(nameof(BlenderMappings))] [MemberData(nameof(BlenderMappings))]
public void ReturnsCorrectBlender<TPixel>(TestPixel<TPixel> pixel, Type type, PixelBlenderMode mode) public void ReturnsCorrectBlender<TPixel>(TestPixel<TPixel> pixel, Type type, PixelColorBlendingMode mode)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
PixelBlender<TPixel> blender = PixelOperations<TPixel>.Instance.GetPixelBlender(mode); PixelBlender<TPixel> blender = PixelOperations<TPixel>.Instance.GetPixelBlender(mode, PixelAlphaCompositionMode.SrcOver);
Assert.IsType(type, blender); Assert.IsType(type, blender);
} }
} }

11
tests/ImageSharp.Tests/PixelFormats/PixelOperationsTests.cs

@ -88,6 +88,17 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
Assert.NotNull(PixelOperations<TPixel>.Instance); Assert.NotNull(PixelOperations<TPixel>.Instance);
}
[Fact]
public void IsOpaqueColor()
{
Assert.True(new GraphicsOptions(true).IsOpaqueColorWithoutBlending(ImageSharp.PixelFormats.Rgba32.Red));
Assert.False(new GraphicsOptions(true, 0.5f).IsOpaqueColorWithoutBlending(ImageSharp.PixelFormats.Rgba32.Red));
Assert.False(new GraphicsOptions(true).IsOpaqueColorWithoutBlending(ImageSharp.PixelFormats.Rgba32.Transparent));
Assert.False(new GraphicsOptions(true, PixelColorBlendingMode.Lighten, 1).IsOpaqueColorWithoutBlending(ImageSharp.PixelFormats.Rgba32.Red));
Assert.False(new GraphicsOptions(true, PixelColorBlendingMode.Normal,PixelAlphaCompositionMode.DestOver, 1).IsOpaqueColorWithoutBlending(ImageSharp.PixelFormats.Rgba32.Red));
} }
} }

2
tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs

@ -365,7 +365,7 @@ namespace SixLabors.ImageSharp.Tests
if (!File.Exists(referenceOutputFile)) if (!File.Exists(referenceOutputFile))
{ {
throw new Exception("Reference output file missing: " + referenceOutputFile); throw new System.IO.FileNotFoundException("Reference output file missing: " + referenceOutputFile, referenceOutputFile);
} }
IImageDecoder decoder = TestEnvironment.GetReferenceDecoder(referenceOutputFile); IImageDecoder decoder = TestEnvironment.GetReferenceDecoder(referenceOutputFile);

2
tests/Images/External

@ -1 +1 @@
Subproject commit 825220cdc4e9d1b4b3b474c63139e18e1cdb800e Subproject commit 6a43d335f216d6325a6a9fd8d35942ade12b7c7b
Loading…
Cancel
Save