Browse Source

Merge branch 'main' into js/2469-color-LUT-memory

pull/2473/head
James Jackson-South 3 years ago
committed by GitHub
parent
commit
d3e88332d6
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 162
      src/ImageSharp/Processing/Extensions/Drawing/DrawImageExtensions.cs
  2. 59
      src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor.cs
  3. 132
      src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs
  4. 20
      tests/ImageSharp.Tests/Drawing/DrawImageExtensionsTests.cs
  5. 67
      tests/ImageSharp.Tests/Drawing/DrawImageTests.cs
  6. 3
      tests/ImageSharp.Tests/TestImages.cs
  7. 3
      tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/Issue2447_A.png
  8. 3
      tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/Issue2447_B.png
  9. 3
      tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/Issue2447_C.png
  10. 3
      tests/Images/Input/Png/issues/issue_2447.png

162
src/ImageSharp/Processing/Extensions/Drawing/DrawImageExtensions.cs

@ -15,277 +15,277 @@ public static class DrawImageExtensions
/// Draws the given image together with the currently processing image by blending their pixels.
/// </summary>
/// <param name="source">The current image processing context.</param>
/// <param name="image">The image to draw on the currently processing image.</param>
/// <param name="foreground">The image to draw on the currently processing image.</param>
/// <param name="opacity">The opacity of the image to draw. Must be between 0 and 1.</param>
/// <returns>The <see cref="IImageProcessingContext"/>.</returns>
public static IImageProcessingContext DrawImage(
this IImageProcessingContext source,
Image image,
Image foreground,
float opacity)
{
GraphicsOptions options = source.GetGraphicsOptions();
return DrawImage(source, image, options.ColorBlendingMode, options.AlphaCompositionMode, opacity);
return DrawImage(source, foreground, options.ColorBlendingMode, options.AlphaCompositionMode, opacity);
}
/// <summary>
/// Draws the given image together with the currently processing image by blending their pixels.
/// </summary>
/// <param name="source">The current image processing context.</param>
/// <param name="image">The image to draw on the currently processing image.</param>
/// <param name="rectangle">The rectangle structure that specifies the portion of the image to draw.</param>
/// <param name="foreground">The image to draw on the currently processing image.</param>
/// <param name="foregroundRectangle">The rectangle structure that specifies the portion of the image to draw.</param>
/// <param name="opacity">The opacity of the image to draw. Must be between 0 and 1.</param>
/// <returns>The <see cref="IImageProcessingContext"/>.</returns>
public static IImageProcessingContext DrawImage(
this IImageProcessingContext source,
Image image,
Rectangle rectangle,
Image foreground,
Rectangle foregroundRectangle,
float opacity)
{
GraphicsOptions options = source.GetGraphicsOptions();
return DrawImage(source, image, rectangle, options.ColorBlendingMode, options.AlphaCompositionMode, opacity);
return DrawImage(source, foreground, foregroundRectangle, options.ColorBlendingMode, options.AlphaCompositionMode, opacity);
}
/// <summary>
/// Draws the given image together with the currently processing image by blending their pixels.
/// </summary>
/// <param name="source">The current image processing context.</param>
/// <param name="image">The image to draw on the currently processing image.</param>
/// <param name="foreground">The image to draw on the currently processing image.</param>
/// <param name="colorBlending">The color blending mode.</param>
/// <param name="opacity">The opacity of the image to draw. Must be between 0 and 1.</param>
/// <returns>The <see cref="IImageProcessingContext"/>.</returns>
public static IImageProcessingContext DrawImage(
this IImageProcessingContext source,
Image image,
Image foreground,
PixelColorBlendingMode colorBlending,
float opacity)
=> DrawImage(source, image, Point.Empty, colorBlending, opacity);
=> DrawImage(source, foreground, Point.Empty, colorBlending, opacity);
/// <summary>
/// Draws the given image together with the currently processing image by blending their pixels.
/// </summary>
/// <param name="source">The current image processing context.</param>
/// <param name="image">The image to draw on the currently processing image.</param>
/// <param name="rectangle">The rectangle structure that specifies the portion of the image to draw.</param>
/// <param name="foreground">The image to draw on the currently processing image.</param>
/// <param name="foregroundRectangle">The rectangle structure that specifies the portion of the image to draw.</param>
/// <param name="colorBlending">The color blending mode.</param>
/// <param name="opacity">The opacity of the image to draw. Must be between 0 and 1.</param>
/// <returns>The <see cref="IImageProcessingContext"/>.</returns>
public static IImageProcessingContext DrawImage(
this IImageProcessingContext source,
Image image,
Rectangle rectangle,
Image foreground,
Rectangle foregroundRectangle,
PixelColorBlendingMode colorBlending,
float opacity)
=> DrawImage(source, image, rectangle, colorBlending, source.GetGraphicsOptions().AlphaCompositionMode, opacity);
=> DrawImage(source, foreground, foregroundRectangle, colorBlending, source.GetGraphicsOptions().AlphaCompositionMode, opacity);
/// <summary>
/// Draws the given image together with the currently processing image by blending their pixels.
/// </summary>
/// <param name="source">The current image processing context.</param>
/// <param name="image">The image to draw on the currently processing image.</param>
/// <param name="foreground">The image to draw on 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 draw. Must be between 0 and 1.</param>
/// <returns>The <see cref="IImageProcessingContext"/>.</returns>
public static IImageProcessingContext DrawImage(
this IImageProcessingContext source,
Image image,
Image foreground,
PixelColorBlendingMode colorBlending,
PixelAlphaCompositionMode alphaComposition,
float opacity)
=> DrawImage(source, image, Point.Empty, colorBlending, alphaComposition, opacity);
=> DrawImage(source, foreground, Point.Empty, colorBlending, alphaComposition, opacity);
/// <summary>
/// Draws the given image together with the currently processing image by blending their pixels.
/// </summary>
/// <param name="source">The current image processing context.</param>
/// <param name="image">The image to draw on the currently processing image.</param>
/// <param name="rectangle">The rectangle structure that specifies the portion of the image to draw.</param>
/// <param name="foreground">The image to draw on the currently processing image.</param>
/// <param name="foregroundRectangle">The rectangle structure that specifies the portion of the image to draw.</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 draw. Must be between 0 and 1.</param>
/// <returns>The <see cref="IImageProcessingContext"/>.</returns>
public static IImageProcessingContext DrawImage(
this IImageProcessingContext source,
Image image,
Rectangle rectangle,
Image foreground,
Rectangle foregroundRectangle,
PixelColorBlendingMode colorBlending,
PixelAlphaCompositionMode alphaComposition,
float opacity)
=> DrawImage(source, image, Point.Empty, rectangle, colorBlending, alphaComposition, opacity);
=> DrawImage(source, foreground, Point.Empty, foregroundRectangle, colorBlending, alphaComposition, opacity);
/// <summary>
/// Draws the given image together with the currently processing image by blending their pixels.
/// </summary>
/// <param name="source">The current image processing context.</param>
/// <param name="image">The image to draw on the currently processing image.</param>
/// <param name="foreground">The image to draw on the currently processing image.</param>
/// <param name="options">The options, including the blending type and blending amount.</param>
/// <returns>The <see cref="IImageProcessingContext"/>.</returns>
public static IImageProcessingContext DrawImage(
this IImageProcessingContext source,
Image image,
Image foreground,
GraphicsOptions options)
=> DrawImage(source, image, Point.Empty, options);
=> DrawImage(source, foreground, Point.Empty, options);
/// <summary>
/// Draws the given image together with the currently processing image by blending their pixels.
/// </summary>
/// <param name="source">The current image processing context.</param>
/// <param name="image">The image to draw on the currently processing image.</param>
/// <param name="rectangle">The rectangle structure that specifies the portion of the image to draw.</param>
/// <param name="foreground">The image to draw on the currently processing image.</param>
/// <param name="foregroundRectangle">The rectangle structure that specifies the portion of the image to draw.</param>
/// <param name="options">The options, including the blending type and blending amount.</param>
/// <returns>The <see cref="IImageProcessingContext"/>.</returns>
public static IImageProcessingContext DrawImage(
this IImageProcessingContext source,
Image image,
Rectangle rectangle,
Image foreground,
Rectangle foregroundRectangle,
GraphicsOptions options)
=> DrawImage(source, image, Point.Empty, rectangle, options);
=> DrawImage(source, foreground, Point.Empty, foregroundRectangle, options);
/// <summary>
/// Draws the given image together with the currently processing image by blending their pixels.
/// </summary>
/// <param name="source">The current image processing context.</param>
/// <param name="image">The image to draw on the currently processing image.</param>
/// <param name="location">The location on the currenty processing image at which to draw.</param>
/// <param name="foreground">The image to draw on the currently processing image.</param>
/// <param name="backgroundLocation">The location on the currently processing image at which to draw.</param>
/// <param name="opacity">The opacity of the image to draw. Must be between 0 and 1.</param>
/// <returns>The <see cref="IImageProcessingContext"/>.</returns>
public static IImageProcessingContext DrawImage(
this IImageProcessingContext source,
Image image,
Point location,
Image foreground,
Point backgroundLocation,
float opacity)
{
GraphicsOptions options = source.GetGraphicsOptions();
return DrawImage(source, image, location, options.ColorBlendingMode, options.AlphaCompositionMode, opacity);
return DrawImage(source, foreground, backgroundLocation, options.ColorBlendingMode, options.AlphaCompositionMode, opacity);
}
/// <summary>
/// Draws the given image together with the currently processing image by blending their pixels.
/// </summary>
/// <param name="source">The current image processing context.</param>
/// <param name="image">The image to draw on the currently processing image.</param>
/// <param name="location">The location on the currenty processing image at which to draw.</param>
/// <param name="rectangle">The rectangle structure that specifies the portion of the image to draw.</param>
/// <param name="foreground">The image to draw on the currently processing image.</param>
/// <param name="backgroundLocation">The location on the currently processing image at which to draw.</param>
/// <param name="foregroundRectangle">The rectangle structure that specifies the portion of the image to draw.</param>
/// <param name="opacity">The opacity of the image to draw. Must be between 0 and 1.</param>
/// <returns>The <see cref="IImageProcessingContext"/>.</returns>
public static IImageProcessingContext DrawImage(
this IImageProcessingContext source,
Image image,
Point location,
Rectangle rectangle,
Image foreground,
Point backgroundLocation,
Rectangle foregroundRectangle,
float opacity)
{
GraphicsOptions options = source.GetGraphicsOptions();
return DrawImage(source, image, location, rectangle, options.ColorBlendingMode, options.AlphaCompositionMode, opacity);
return DrawImage(source, foreground, backgroundLocation, foregroundRectangle, options.ColorBlendingMode, options.AlphaCompositionMode, opacity);
}
/// <summary>
/// Draws the given image together with the currently processing image by blending their pixels.
/// </summary>
/// <param name="source">The current image processing context.</param>
/// <param name="image">The image to draw on the currently processing image.</param>
/// <param name="location">The location on the currenty processing image at which to draw.</param>
/// <param name="foreground">The image to draw on the currently processing image.</param>
/// <param name="backgroundLocation">The location on the currently processing image at which to draw.</param>
/// <param name="colorBlending">The color blending to apply.</param>
/// <param name="opacity">The opacity of the image to draw. Must be between 0 and 1.</param>
/// <returns>The <see cref="IImageProcessingContext"/>.</returns>
public static IImageProcessingContext DrawImage(
this IImageProcessingContext source,
Image image,
Point location,
Image foreground,
Point backgroundLocation,
PixelColorBlendingMode colorBlending,
float opacity)
=> DrawImage(source, image, location, colorBlending, source.GetGraphicsOptions().AlphaCompositionMode, opacity);
=> DrawImage(source, foreground, backgroundLocation, colorBlending, source.GetGraphicsOptions().AlphaCompositionMode, opacity);
/// <summary>
/// Draws the given image together with the currently processing image by blending their pixels.
/// </summary>
/// <param name="source">The current image processing context.</param>
/// <param name="image">The image to draw on the currently processing image.</param>
/// <param name="location">The location on the currenty processing image at which to draw.</param>
/// <param name="rectangle">The rectangle structure that specifies the portion of the image to draw.</param>
/// <param name="foreground">The image to draw on the currently processing image.</param>
/// <param name="backgroundLocation">The location on the currently processing image at which to draw.</param>
/// <param name="foregroundRectangle">The rectangle structure that specifies the portion of the image to draw.</param>
/// <param name="colorBlending">The color blending to apply.</param>
/// <param name="opacity">The opacity of the image to draw. Must be between 0 and 1.</param>
/// <returns>The <see cref="IImageProcessingContext"/>.</returns>
public static IImageProcessingContext DrawImage(
this IImageProcessingContext source,
Image image,
Point location,
Rectangle rectangle,
Image foreground,
Point backgroundLocation,
Rectangle foregroundRectangle,
PixelColorBlendingMode colorBlending,
float opacity)
=> DrawImage(source, image, location, rectangle, colorBlending, source.GetGraphicsOptions().AlphaCompositionMode, opacity);
=> DrawImage(source, foreground, backgroundLocation, foregroundRectangle, colorBlending, source.GetGraphicsOptions().AlphaCompositionMode, opacity);
/// <summary>
/// Draws the given image together with the currently processing image by blending their pixels.
/// </summary>
/// <param name="source">The current image processing context.</param>
/// <param name="image">The image to draw on the currently processing image.</param>
/// <param name="location">The location on the currenty processing image at which to draw.</param>
/// <param name="foreground">The image to draw on the currently processing image.</param>
/// <param name="backgroundLocation">The location on the currently processing image at which to draw.</param>
/// <param name="options">The options containing the blend mode and opacity.</param>
/// <returns>The <see cref="IImageProcessingContext"/>.</returns>
public static IImageProcessingContext DrawImage(
this IImageProcessingContext source,
Image image,
Point location,
Image foreground,
Point backgroundLocation,
GraphicsOptions options)
=> DrawImage(source, image, location, options.ColorBlendingMode, options.AlphaCompositionMode, options.BlendPercentage);
=> DrawImage(source, foreground, backgroundLocation, options.ColorBlendingMode, options.AlphaCompositionMode, options.BlendPercentage);
/// <summary>
/// Draws the given image together with the currently processing image by blending their pixels.
/// </summary>
/// <param name="source">The current image processing context.</param>
/// <param name="image">The image to draw on the currently processing image.</param>
/// <param name="location">The location on the currenty processing image at which to draw.</param>
/// <param name="rectangle">The rectangle structure that specifies the portion of the image to draw.</param>
/// <param name="foreground">The image to draw on the currently processing image.</param>
/// <param name="backgroundLocation">The location on the currently processing image at which to draw.</param>
/// <param name="foregroundRectangle">The rectangle structure that specifies the portion of the image to draw.</param>
/// <param name="options">The options containing the blend mode and opacity.</param>
/// <returns>The <see cref="IImageProcessingContext"/>.</returns>
public static IImageProcessingContext DrawImage(
this IImageProcessingContext source,
Image image,
Point location,
Rectangle rectangle,
Image foreground,
Point backgroundLocation,
Rectangle foregroundRectangle,
GraphicsOptions options)
=> DrawImage(source, image, location, rectangle, options.ColorBlendingMode, options.AlphaCompositionMode, options.BlendPercentage);
=> DrawImage(source, foreground, backgroundLocation, foregroundRectangle, options.ColorBlendingMode, options.AlphaCompositionMode, options.BlendPercentage);
/// <summary>
/// Draws the given image together with the currently processing image by blending their pixels.
/// </summary>
/// <param name="source">The current image processing context.</param>
/// <param name="image">The image to draw on the currently processing image.</param>
/// <param name="location">The location on the currenty processing image at which to draw.</param>
/// <param name="foreground">The image to draw on the currently processing image.</param>
/// <param name="backgroundLocation">The location on the currently processing image at which to draw.</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 draw. Must be between 0 and 1.</param>
/// <returns>The <see cref="IImageProcessingContext"/>.</returns>
public static IImageProcessingContext DrawImage(
this IImageProcessingContext source,
Image image,
Point location,
Image foreground,
Point backgroundLocation,
PixelColorBlendingMode colorBlending,
PixelAlphaCompositionMode alphaComposition,
float opacity)
=> source.ApplyProcessor(new DrawImageProcessor(image, location, colorBlending, alphaComposition, opacity));
=> source.ApplyProcessor(new DrawImageProcessor(foreground, backgroundLocation, foreground.Bounds, colorBlending, alphaComposition, opacity));
/// <summary>
/// Draws the given image together with the currently processing image by blending their pixels.
/// </summary>
/// <param name="source">The current image processing context.</param>
/// <param name="image">The image to draw on the currently processing image.</param>
/// <param name="location">The location on the currenty processing image at which to draw.</param>
/// <param name="rectangle">The rectangle structure that specifies the portion of the image to draw.</param>
/// <param name="foreground">The image to draw on the currently processing image.</param>
/// <param name="backgroundLocation">The location on the currently processing image at which to draw.</param>
/// <param name="foregroundRectangle">The rectangle structure that specifies the portion of the image to draw.</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 draw. Must be between 0 and 1.</param>
/// <returns>The <see cref="IImageProcessingContext"/>.</returns>
public static IImageProcessingContext DrawImage(
this IImageProcessingContext source,
Image image,
Point location,
Rectangle rectangle,
Image foreground,
Point backgroundLocation,
Rectangle foregroundRectangle,
PixelColorBlendingMode colorBlending,
PixelAlphaCompositionMode alphaComposition,
float opacity) =>
source.ApplyProcessor(
new DrawImageProcessor(image, location, colorBlending, alphaComposition, opacity),
rectangle);
new DrawImageProcessor(foreground, backgroundLocation, foregroundRectangle, colorBlending, alphaComposition, opacity),
foregroundRectangle);
}

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

@ -14,20 +14,41 @@ public class DrawImageProcessor : IImageProcessor
/// <summary>
/// Initializes a new instance of the <see cref="DrawImageProcessor"/> class.
/// </summary>
/// <param name="image">The image to blend.</param>
/// <param name="location">The location to draw the blended image.</param>
/// <param name="foreground">The image to blend.</param>
/// <param name="backgroundLocation">The location to draw the foreground image on the background.</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.</param>
public DrawImageProcessor(
Image image,
Point location,
Image foreground,
Point backgroundLocation,
PixelColorBlendingMode colorBlendingMode,
PixelAlphaCompositionMode alphaCompositionMode,
float opacity)
: this(foreground, backgroundLocation, foreground.Bounds, colorBlendingMode, alphaCompositionMode, opacity)
{
this.Image = image;
this.Location = location;
}
/// <summary>
/// Initializes a new instance of the <see cref="DrawImageProcessor"/> class.
/// </summary>
/// <param name="foreground">The image to blend.</param>
/// <param name="backgroundLocation">The location to draw the foreground image on the background.</param>
/// <param name="foregroundRectangle">The rectangular portion of the foreground image to draw.</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.</param>
public DrawImageProcessor(
Image foreground,
Point backgroundLocation,
Rectangle foregroundRectangle,
PixelColorBlendingMode colorBlendingMode,
PixelAlphaCompositionMode alphaCompositionMode,
float opacity)
{
this.ForeGround = foreground;
this.BackgroundLocation = backgroundLocation;
this.ForegroundRectangle = foregroundRectangle;
this.ColorBlendingMode = colorBlendingMode;
this.AlphaCompositionMode = alphaCompositionMode;
this.Opacity = opacity;
@ -36,12 +57,17 @@ public class DrawImageProcessor : IImageProcessor
/// <summary>
/// Gets the image to blend.
/// </summary>
public Image Image { get; }
public Image ForeGround { get; }
/// <summary>
/// Gets the location to draw the foreground image on the background.
/// </summary>
public Point BackgroundLocation { get; }
/// <summary>
/// Gets the location to draw the blended image.
/// Gets the rectangular portion of the foreground image to draw.
/// </summary>
public Point Location { get; }
public Rectangle ForegroundRectangle { get; }
/// <summary>
/// Gets the blending mode to use when drawing the image.
@ -62,8 +88,8 @@ public class DrawImageProcessor : IImageProcessor
public IImageProcessor<TPixelBg> CreatePixelSpecificProcessor<TPixelBg>(Configuration configuration, Image<TPixelBg> source, Rectangle sourceRectangle)
where TPixelBg : unmanaged, IPixel<TPixelBg>
{
ProcessorFactoryVisitor<TPixelBg> visitor = new(configuration, this, source, sourceRectangle);
this.Image.AcceptVisitor(visitor);
ProcessorFactoryVisitor<TPixelBg> visitor = new(configuration, this, source);
this.ForeGround.AcceptVisitor(visitor);
return visitor.Result!;
}
@ -73,14 +99,15 @@ public class DrawImageProcessor : IImageProcessor
private readonly Configuration configuration;
private readonly DrawImageProcessor definition;
private readonly Image<TPixelBg> source;
private readonly Rectangle sourceRectangle;
public ProcessorFactoryVisitor(Configuration configuration, DrawImageProcessor definition, Image<TPixelBg> source, Rectangle sourceRectangle)
public ProcessorFactoryVisitor(
Configuration configuration,
DrawImageProcessor definition,
Image<TPixelBg> source)
{
this.configuration = configuration;
this.definition = definition;
this.source = source;
this.sourceRectangle = sourceRectangle;
}
public IImageProcessor<TPixelBg>? Result { get; private set; }
@ -91,8 +118,8 @@ public class DrawImageProcessor : IImageProcessor
this.configuration,
image,
this.source,
this.sourceRectangle,
this.definition.Location,
this.definition.BackgroundLocation,
this.definition.ForegroundRectangle,
this.definition.ColorBlendingMode,
this.definition.AlphaCompositionMode,
this.definition.Opacity);

132
src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs

@ -21,36 +21,42 @@ internal class DrawImageProcessor<TPixelBg, TPixelFg> : ImageProcessor<TPixelBg>
/// Initializes a new instance of the <see cref="DrawImageProcessor{TPixelBg, TPixelFg}"/> class.
/// </summary>
/// <param name="configuration">The configuration which allows altering default behaviour or extending the library.</param>
/// <param name="image">The foreground <see cref="Image{TPixelFg}"/> to blend with the currently processing image.</param>
/// <param name="source">The source <see cref="Image{TPixelBg}"/> for the current processor instance.</param>
/// <param name="sourceRectangle">The source area to process for the current processor instance.</param>
/// <param name="location">The location to draw the blended image.</param>
/// <param name="foregroundImage">The foreground <see cref="Image{TPixelFg}"/> to blend with the currently processing image.</param>
/// <param name="backgroundImage">The source <see cref="Image{TPixelBg}"/> for the current processor instance.</param>
/// <param name="backgroundLocation">The location to draw the blended image.</param>
/// <param name="foregroundRectangle">The source area to process for the current processor instance.</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="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>
public DrawImageProcessor(
Configuration configuration,
Image<TPixelFg> image,
Image<TPixelBg> source,
Rectangle sourceRectangle,
Point location,
Image<TPixelFg> foregroundImage,
Image<TPixelBg> backgroundImage,
Point backgroundLocation,
Rectangle foregroundRectangle,
PixelColorBlendingMode colorBlendingMode,
PixelAlphaCompositionMode alphaCompositionMode,
float opacity)
: base(configuration, source, sourceRectangle)
: base(configuration, backgroundImage, backgroundImage.Bounds)
{
Guard.MustBeBetweenOrEqualTo(opacity, 0, 1, nameof(opacity));
this.Image = image;
this.ForegroundImage = foregroundImage;
this.ForegroundRectangle = foregroundRectangle;
this.Opacity = opacity;
this.Blender = PixelOperations<TPixelBg>.Instance.GetPixelBlender(colorBlendingMode, alphaCompositionMode);
this.Location = location;
this.BackgroundLocation = backgroundLocation;
}
/// <summary>
/// Gets the image to blend
/// </summary>
public Image<TPixelFg> Image { get; }
public Image<TPixelFg> ForegroundImage { get; }
/// <summary>
/// Gets the rectangular portion of the foreground image to draw.
/// </summary>
public Rectangle ForegroundRectangle { get; }
/// <summary>
/// Gets the opacity of the image to blend
@ -65,43 +71,57 @@ internal class DrawImageProcessor<TPixelBg, TPixelFg> : ImageProcessor<TPixelBg>
/// <summary>
/// Gets the location to draw the blended image
/// </summary>
public Point Location { get; }
public Point BackgroundLocation { get; }
/// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixelBg> source)
{
Rectangle sourceRectangle = this.SourceRectangle;
Configuration configuration = this.Configuration;
Image<TPixelFg> targetImage = this.Image;
PixelBlender<TPixelBg> blender = this.Blender;
int locationY = this.Location.Y;
// Align the bounds so that both the source and targets are the same width and height for blending.
// We ensure that negative locations are subtracted from both bounds so that foreground images can partially overlap.
Rectangle foregroundRectangle = this.ForegroundRectangle;
// Align start/end positions.
Rectangle bounds = targetImage.Bounds;
// Sanitize the location so that we don't try and sample outside the image.
int left = this.BackgroundLocation.X;
int top = this.BackgroundLocation.Y;
int minX = Math.Max(this.Location.X, sourceRectangle.X);
int maxX = Math.Min(this.Location.X + bounds.Width, sourceRectangle.Right);
int targetX = minX - this.Location.X;
int minY = Math.Max(this.Location.Y, sourceRectangle.Y);
int maxY = Math.Min(this.Location.Y + bounds.Height, sourceRectangle.Bottom);
int width = maxX - minX;
if (this.BackgroundLocation.X < 0)
{
foregroundRectangle.Width += this.BackgroundLocation.X;
left = 0;
}
Rectangle workingRect = Rectangle.FromLTRB(minX, minY, maxX, maxY);
if (this.BackgroundLocation.Y < 0)
{
foregroundRectangle.Height += this.BackgroundLocation.Y;
top = 0;
}
// Not a valid operation because rectangle does not overlap with this image.
if (workingRect.Width <= 0 || workingRect.Height <= 0)
int width = foregroundRectangle.Width;
int height = foregroundRectangle.Height;
if (width <= 0 || height <= 0)
{
throw new ImageProcessingException(
"Cannot draw image because the source image does not overlap the target image.");
// Nothing to do, return.
return;
}
DrawImageProcessor<TPixelBg, TPixelFg>.RowOperation operation = new(source.PixelBuffer, targetImage.Frames.RootFrame.PixelBuffer, blender, configuration, minX, width, locationY, targetX, this.Opacity);
// Sanitize the dimensions so that we don't try and sample outside the image.
foregroundRectangle = Rectangle.Intersect(foregroundRectangle, this.ForegroundImage.Bounds);
Rectangle backgroundRectangle = Rectangle.Intersect(new(left, top, width, height), this.SourceRectangle);
Configuration configuration = this.Configuration;
DrawImageProcessor<TPixelBg, TPixelFg>.RowOperation operation =
new(
configuration,
source.PixelBuffer,
this.ForegroundImage.Frames.RootFrame.PixelBuffer,
backgroundRectangle,
foregroundRectangle,
this.Blender,
this.Opacity);
ParallelRowIterator.IterateRows(
configuration,
workingRect,
new(0, 0, foregroundRectangle.Width, foregroundRectangle.Height),
in operation);
}
@ -110,36 +130,30 @@ internal class DrawImageProcessor<TPixelBg, TPixelFg> : ImageProcessor<TPixelBg>
/// </summary>
private readonly struct RowOperation : IRowOperation
{
private readonly Buffer2D<TPixelBg> source;
private readonly Buffer2D<TPixelFg> target;
private readonly Buffer2D<TPixelBg> background;
private readonly Buffer2D<TPixelFg> foreground;
private readonly PixelBlender<TPixelBg> blender;
private readonly Configuration configuration;
private readonly int minX;
private readonly int width;
private readonly int locationY;
private readonly int targetX;
private readonly Rectangle foregroundRectangle;
private readonly Rectangle backgroundRectangle;
private readonly float opacity;
[MethodImpl(InliningOptions.ShortMethod)]
public RowOperation(
Buffer2D<TPixelBg> source,
Buffer2D<TPixelFg> target,
PixelBlender<TPixelBg> blender,
Configuration configuration,
int minX,
int width,
int locationY,
int targetX,
Buffer2D<TPixelBg> background,
Buffer2D<TPixelFg> foreground,
Rectangle backgroundRectangle,
Rectangle foregroundRectangle,
PixelBlender<TPixelBg> blender,
float opacity)
{
this.source = source;
this.target = target;
this.blender = blender;
this.configuration = configuration;
this.minX = minX;
this.width = width;
this.locationY = locationY;
this.targetX = targetX;
this.background = background;
this.foreground = foreground;
this.backgroundRectangle = backgroundRectangle;
this.foregroundRectangle = foregroundRectangle;
this.blender = blender;
this.opacity = opacity;
}
@ -147,8 +161,8 @@ internal class DrawImageProcessor<TPixelBg, TPixelFg> : ImageProcessor<TPixelBg>
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(int y)
{
Span<TPixelBg> background = this.source.DangerousGetRowSpan(y).Slice(this.minX, this.width);
Span<TPixelFg> foreground = this.target.DangerousGetRowSpan(y - this.locationY).Slice(this.targetX, this.width);
Span<TPixelBg> background = this.background.DangerousGetRowSpan(y + this.backgroundRectangle.Top).Slice(this.backgroundRectangle.Left, this.backgroundRectangle.Width);
Span<TPixelFg> foreground = this.foreground.DangerousGetRowSpan(y + this.foregroundRectangle.Top).Slice(this.foregroundRectangle.Left, this.foregroundRectangle.Width);
this.blender.Blend<TPixelFg>(this.configuration, background, background, foreground, this.opacity);
}
}

20
tests/ImageSharp.Tests/Drawing/DrawImageExtensionsTests.cs

@ -13,11 +13,12 @@ public class DrawImageExtensionsTests : BaseImageOperationsExtensionTest
[Fact]
public void DrawImage_OpacityOnly_VerifyGraphicOptionsTakenFromContext()
{
// non-default values as we cant easly defect usage otherwise
// non-default values as we cant easily defect usage otherwise
this.options.AlphaCompositionMode = PixelAlphaCompositionMode.Xor;
this.options.ColorBlendingMode = PixelColorBlendingMode.Screen;
this.operations.DrawImage(null, 0.5f);
using Image<Rgba32> image = new(Configuration.Default, 1, 1);
this.operations.DrawImage(image, 0.5f);
DrawImageProcessor dip = this.Verify<DrawImageProcessor>();
Assert.Equal(0.5, dip.Opacity);
@ -28,11 +29,12 @@ public class DrawImageExtensionsTests : BaseImageOperationsExtensionTest
[Fact]
public void DrawImage_OpacityAndBlending_VerifyGraphicOptionsTakenFromContext()
{
// non-default values as we cant easly defect usage otherwise
// non-default values as we cant easily defect usage otherwise
this.options.AlphaCompositionMode = PixelAlphaCompositionMode.Xor;
this.options.ColorBlendingMode = PixelColorBlendingMode.Screen;
this.operations.DrawImage(null, PixelColorBlendingMode.Multiply, 0.5f);
using Image<Rgba32> image = new(Configuration.Default, 1, 1);
this.operations.DrawImage(image, PixelColorBlendingMode.Multiply, 0.5f);
DrawImageProcessor dip = this.Verify<DrawImageProcessor>();
Assert.Equal(0.5, dip.Opacity);
@ -43,11 +45,12 @@ public class DrawImageExtensionsTests : BaseImageOperationsExtensionTest
[Fact]
public void DrawImage_LocationAndOpacity_VerifyGraphicOptionsTakenFromContext()
{
// non-default values as we cant easly defect usage otherwise
// non-default values as we cant easily defect usage otherwise
this.options.AlphaCompositionMode = PixelAlphaCompositionMode.Xor;
this.options.ColorBlendingMode = PixelColorBlendingMode.Screen;
this.operations.DrawImage(null, Point.Empty, 0.5f);
using Image<Rgba32> image = new(Configuration.Default, 1, 1);
this.operations.DrawImage(image, Point.Empty, 0.5f);
DrawImageProcessor dip = this.Verify<DrawImageProcessor>();
Assert.Equal(0.5, dip.Opacity);
@ -58,11 +61,12 @@ public class DrawImageExtensionsTests : BaseImageOperationsExtensionTest
[Fact]
public void DrawImage_LocationAndOpacityAndBlending_VerifyGraphicOptionsTakenFromContext()
{
// non-default values as we cant easly defect usage otherwise
// non-default values as we cant easily defect usage otherwise
this.options.AlphaCompositionMode = PixelAlphaCompositionMode.Xor;
this.options.ColorBlendingMode = PixelColorBlendingMode.Screen;
this.operations.DrawImage(null, Point.Empty, PixelColorBlendingMode.Multiply, 0.5f);
using Image<Rgba32> image = new(Configuration.Default, 1, 1);
this.operations.DrawImage(image, Point.Empty, PixelColorBlendingMode.Multiply, 0.5f);
DrawImageProcessor dip = this.Verify<DrawImageProcessor>();
Assert.Equal(0.5, dip.Opacity);

67
tests/ImageSharp.Tests/Drawing/DrawImageTests.cs

@ -190,18 +190,65 @@ public class DrawImageTests
}
[Theory]
[WithSolidFilledImages(100, 100, 255, 255, 255, PixelTypes.Rgba32, -30, -30)]
[WithSolidFilledImages(100, 100, 255, 255, 255, PixelTypes.Rgba32, 130, -30)]
[WithSolidFilledImages(100, 100, 255, 255, 255, PixelTypes.Rgba32, 130, 130)]
[WithSolidFilledImages(100, 100, 255, 255, 255, PixelTypes.Rgba32, -30, 130)]
public void NonOverlappingImageThrows(TestImageProvider<Rgba32> provider, int x, int y)
[WithFile(TestImages.Png.Issue2447, PixelTypes.Rgba32)]
public void Issue2447_A<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using Image<Rgba32> background = provider.GetImage();
using Image<Rgba32> overlay = new(Configuration.Default, 10, 10, Color.Black);
ImageProcessingException ex = Assert.Throws<ImageProcessingException>(Test);
using Image<TPixel> foreground = provider.GetImage();
using Image<Rgba32> background = new(100, 100, new Rgba32(0, 255, 255));
Assert.Contains("does not overlap", ex.ToString());
background.Mutate(c => c.DrawImage(foreground, new Point(64, 10), new Rectangle(32, 32, 32, 32), 1F));
void Test() => background.Mutate(context => context.DrawImage(overlay, new Point(x, y), new GraphicsOptions()));
background.DebugSave(
provider,
appendPixelTypeToFileName: false,
appendSourceFileOrDescription: false);
background.CompareToReferenceOutput(
provider,
appendPixelTypeToFileName: false,
appendSourceFileOrDescription: false);
}
[Theory]
[WithFile(TestImages.Png.Issue2447, PixelTypes.Rgba32)]
public void Issue2447_B<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using Image<TPixel> foreground = provider.GetImage();
using Image<Rgba32> background = new(100, 100, new Rgba32(0, 255, 255));
background.Mutate(c => c.DrawImage(foreground, new Point(10, 10), new Rectangle(320, 128, 32, 32), 1F));
background.DebugSave(
provider,
appendPixelTypeToFileName: false,
appendSourceFileOrDescription: false);
background.CompareToReferenceOutput(
provider,
appendPixelTypeToFileName: false,
appendSourceFileOrDescription: false);
}
[Theory]
[WithFile(TestImages.Png.Issue2447, PixelTypes.Rgba32)]
public void Issue2447_C<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using Image<TPixel> foreground = provider.GetImage();
using Image<Rgba32> background = new(100, 100, new Rgba32(0, 255, 255));
background.Mutate(c => c.DrawImage(foreground, new Point(10, 10), new Rectangle(32, 32, 32, 32), 1F));
background.DebugSave(
provider,
appendPixelTypeToFileName: false,
appendSourceFileOrDescription: false);
background.CompareToReferenceOutput(
provider,
appendPixelTypeToFileName: false,
appendSourceFileOrDescription: false);
}
}

3
tests/ImageSharp.Tests/TestImages.cs

@ -135,6 +135,9 @@ public static class TestImages
// Issue 2259: https://github.com/SixLabors/ImageSharp/issues/2469
public const string Issue2469 = "Png/issues/issue_2469.png";
// Issue 2447: https://github.com/SixLabors/ImageSharp/issues/2447
public const string Issue2447 = "Png/issues/issue_2447.png";
public static class Bad
{
public const string MissingDataChunk = "Png/xdtn0g01.png";

3
tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/Issue2447_A.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:2012789669110c08a00d37add7f53967b902bd617c90f85d7e90b13a32a0a429
size 354

3
tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/Issue2447_B.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f628327efbf1e530d32dc092f2ab361de5ab35fe78db6b5e0274c71f1d170496
size 363

3
tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/Issue2447_C.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:40fc8f14b8f9e98fd73855f3dfada39062cc1aff874b3389133a55eb2e968f66
size 354

3
tests/Images/Input/Png/issues/issue_2447.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:52f7e55f812db926d95ac1ab0c3235fbaca53331b99f73e65f3c1c2094503e20
size 15824
Loading…
Cancel
Save