From 42336ba7f918f1f718b2a954b671629fd53f5b36 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 12 Jun 2023 21:03:57 +1000 Subject: [PATCH 1/4] Fix #2447 --- .../Extensions/Drawing/DrawImageExtensions.cs | 162 +++++++++--------- .../Processors/Drawing/DrawImageProcessor.cs | 31 ++-- .../DrawImageProcessor{TPixelBg,TPixelFg}.cs | 132 +++++++------- .../Drawing/DrawImageTests.cs | 73 ++++++-- tests/ImageSharp.Tests/TestImages.cs | 3 + .../Drawing/DrawImageTests/Issue2447_A.png | 3 + .../Drawing/DrawImageTests/Issue2447_B.png | 3 + .../Drawing/DrawImageTests/Issue2447_C.png | 3 + tests/Images/Input/Png/issues/issue_2447.png | 3 + 9 files changed, 249 insertions(+), 164 deletions(-) create mode 100644 tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/Issue2447_A.png create mode 100644 tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/Issue2447_B.png create mode 100644 tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/Issue2447_C.png create mode 100644 tests/Images/Input/Png/issues/issue_2447.png diff --git a/src/ImageSharp/Processing/Extensions/Drawing/DrawImageExtensions.cs b/src/ImageSharp/Processing/Extensions/Drawing/DrawImageExtensions.cs index ad93d6f16..25e504831 100644 --- a/src/ImageSharp/Processing/Extensions/Drawing/DrawImageExtensions.cs +++ b/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. /// /// The current image processing context. - /// The image to draw on the currently processing image. + /// The image to draw on the currently processing image. /// The opacity of the image to draw. Must be between 0 and 1. /// The . 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); } /// /// Draws the given image together with the currently processing image by blending their pixels. /// /// The current image processing context. - /// The image to draw on the currently processing image. - /// The rectangle structure that specifies the portion of the image to draw. + /// The image to draw on the currently processing image. + /// The rectangle structure that specifies the portion of the image to draw. /// The opacity of the image to draw. Must be between 0 and 1. /// The . 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); } /// /// Draws the given image together with the currently processing image by blending their pixels. /// /// The current image processing context. - /// The image to draw on the currently processing image. + /// The image to draw on the currently processing image. /// The color blending mode. /// The opacity of the image to draw. Must be between 0 and 1. /// The . 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); /// /// Draws the given image together with the currently processing image by blending their pixels. /// /// The current image processing context. - /// The image to draw on the currently processing image. - /// The rectangle structure that specifies the portion of the image to draw. + /// The image to draw on the currently processing image. + /// The rectangle structure that specifies the portion of the image to draw. /// The color blending mode. /// The opacity of the image to draw. Must be between 0 and 1. /// The . 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); /// /// Draws the given image together with the currently processing image by blending their pixels. /// /// The current image processing context. - /// The image to draw on the currently processing image. + /// The image to draw on the currently processing image. /// The color blending mode. /// The alpha composition mode. /// The opacity of the image to draw. Must be between 0 and 1. /// The . 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); /// /// Draws the given image together with the currently processing image by blending their pixels. /// /// The current image processing context. - /// The image to draw on the currently processing image. - /// The rectangle structure that specifies the portion of the image to draw. + /// The image to draw on the currently processing image. + /// The rectangle structure that specifies the portion of the image to draw. /// The color blending mode. /// The alpha composition mode. /// The opacity of the image to draw. Must be between 0 and 1. /// The . 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); /// /// Draws the given image together with the currently processing image by blending their pixels. /// /// The current image processing context. - /// The image to draw on the currently processing image. + /// The image to draw on the currently processing image. /// The options, including the blending type and blending amount. /// The . 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); /// /// Draws the given image together with the currently processing image by blending their pixels. /// /// The current image processing context. - /// The image to draw on the currently processing image. - /// The rectangle structure that specifies the portion of the image to draw. + /// The image to draw on the currently processing image. + /// The rectangle structure that specifies the portion of the image to draw. /// The options, including the blending type and blending amount. /// The . 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); /// /// Draws the given image together with the currently processing image by blending their pixels. /// /// The current image processing context. - /// The image to draw on the currently processing image. - /// The location on the currenty processing image at which to draw. + /// The image to draw on the currently processing image. + /// The location on the currently processing image at which to draw. /// The opacity of the image to draw. Must be between 0 and 1. /// The . 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); } /// /// Draws the given image together with the currently processing image by blending their pixels. /// /// The current image processing context. - /// The image to draw on the currently processing image. - /// The location on the currenty processing image at which to draw. - /// The rectangle structure that specifies the portion of the image to draw. + /// The image to draw on the currently processing image. + /// The location on the currently processing image at which to draw. + /// The rectangle structure that specifies the portion of the image to draw. /// The opacity of the image to draw. Must be between 0 and 1. /// The . 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); } /// /// Draws the given image together with the currently processing image by blending their pixels. /// /// The current image processing context. - /// The image to draw on the currently processing image. - /// The location on the currenty processing image at which to draw. + /// The image to draw on the currently processing image. + /// The location on the currently processing image at which to draw. /// The color blending to apply. /// The opacity of the image to draw. Must be between 0 and 1. /// The . 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); /// /// Draws the given image together with the currently processing image by blending their pixels. /// /// The current image processing context. - /// The image to draw on the currently processing image. - /// The location on the currenty processing image at which to draw. - /// The rectangle structure that specifies the portion of the image to draw. + /// The image to draw on the currently processing image. + /// The location on the currently processing image at which to draw. + /// The rectangle structure that specifies the portion of the image to draw. /// The color blending to apply. /// The opacity of the image to draw. Must be between 0 and 1. /// The . 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); /// /// Draws the given image together with the currently processing image by blending their pixels. /// /// The current image processing context. - /// The image to draw on the currently processing image. - /// The location on the currenty processing image at which to draw. + /// The image to draw on the currently processing image. + /// The location on the currently processing image at which to draw. /// The options containing the blend mode and opacity. /// The . 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); /// /// Draws the given image together with the currently processing image by blending their pixels. /// /// The current image processing context. - /// The image to draw on the currently processing image. - /// The location on the currenty processing image at which to draw. - /// The rectangle structure that specifies the portion of the image to draw. + /// The image to draw on the currently processing image. + /// The location on the currently processing image at which to draw. + /// The rectangle structure that specifies the portion of the image to draw. /// The options containing the blend mode and opacity. /// The . 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); /// /// Draws the given image together with the currently processing image by blending their pixels. /// /// The current image processing context. - /// The image to draw on the currently processing image. - /// The location on the currenty processing image at which to draw. + /// The image to draw on the currently processing image. + /// The location on the currently processing image at which to draw. /// The color blending to apply. /// The alpha composition mode. /// The opacity of the image to draw. Must be between 0 and 1. /// The . 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)); /// /// Draws the given image together with the currently processing image by blending their pixels. /// /// The current image processing context. - /// The image to draw on the currently processing image. - /// The location on the currenty processing image at which to draw. - /// The rectangle structure that specifies the portion of the image to draw. + /// The image to draw on the currently processing image. + /// The location on the currently processing image at which to draw. + /// The rectangle structure that specifies the portion of the image to draw. /// The color blending to apply. /// The alpha composition mode. /// The opacity of the image to draw. Must be between 0 and 1. /// The . 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); } diff --git a/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor.cs b/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor.cs index 88b59b7dc..98c17dc90 100644 --- a/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor.cs @@ -15,19 +15,22 @@ public class DrawImageProcessor : IImageProcessor /// Initializes a new instance of the class. /// /// The image to blend. - /// The location to draw the blended image. + /// The location to draw the foreground image on the background. + /// The rectangular portion of the foreground image to draw. /// The blending mode to use when drawing the image. /// The Alpha blending mode to use when drawing the image. /// The opacity of the image to blend. public DrawImageProcessor( Image image, - Point location, + Point backgroundLocation, + Rectangle foregoundRectangle, PixelColorBlendingMode colorBlendingMode, PixelAlphaCompositionMode alphaCompositionMode, float opacity) { this.Image = image; - this.Location = location; + this.BackgroundLocation = backgroundLocation; + this.ForegroundRectangle = foregoundRectangle; this.ColorBlendingMode = colorBlendingMode; this.AlphaCompositionMode = alphaCompositionMode; this.Opacity = opacity; @@ -39,9 +42,14 @@ public class DrawImageProcessor : IImageProcessor public Image Image { get; } /// - /// Gets the location to draw the blended image. + /// Gets the location to draw the foreground image on the background. /// - public Point Location { get; } + public Point BackgroundLocation { get; } + + /// + /// Gets the rectangular portion of the foreground image to draw. + /// + public Rectangle ForegroundRectangle { get; } /// /// Gets the blending mode to use when drawing the image. @@ -62,7 +70,7 @@ public class DrawImageProcessor : IImageProcessor public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) where TPixelBg : unmanaged, IPixel { - ProcessorFactoryVisitor visitor = new(configuration, this, source, sourceRectangle); + ProcessorFactoryVisitor visitor = new(configuration, this, source); this.Image.AcceptVisitor(visitor); return visitor.Result!; } @@ -73,14 +81,15 @@ public class DrawImageProcessor : IImageProcessor private readonly Configuration configuration; private readonly DrawImageProcessor definition; private readonly Image source; - private readonly Rectangle sourceRectangle; - public ProcessorFactoryVisitor(Configuration configuration, DrawImageProcessor definition, Image source, Rectangle sourceRectangle) + public ProcessorFactoryVisitor( + Configuration configuration, + DrawImageProcessor definition, + Image source) { this.configuration = configuration; this.definition = definition; this.source = source; - this.sourceRectangle = sourceRectangle; } public IImageProcessor? Result { get; private set; } @@ -91,8 +100,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); diff --git a/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs b/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs index 436a44797..378ea20fa 100644 --- a/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs +++ b/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs @@ -21,36 +21,42 @@ internal class DrawImageProcessor : ImageProcessor /// Initializes a new instance of the class. /// /// The configuration which allows altering default behaviour or extending the library. - /// The foreground to blend with the currently processing image. - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - /// The location to draw the blended image. + /// The foreground to blend with the currently processing image. + /// The source for the current processor instance. + /// The location to draw the blended image. + /// The source area to process for the current processor instance. /// The blending mode to use when drawing the image. - /// The Alpha blending mode to use when drawing the image. + /// The alpha blending mode to use when drawing the image. /// The opacity of the image to blend. Must be between 0 and 1. public DrawImageProcessor( Configuration configuration, - Image image, - Image source, - Rectangle sourceRectangle, - Point location, + Image foregroundImage, + Image 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.Instance.GetPixelBlender(colorBlendingMode, alphaCompositionMode); - this.Location = location; + this.BackgroundLocation = backgroundLocation; } /// /// Gets the image to blend /// - public Image Image { get; } + public Image ForegroundImage { get; } + + /// + /// Gets the rectangular portion of the foreground image to draw. + /// + public Rectangle ForegroundRectangle { get; } /// /// Gets the opacity of the image to blend @@ -65,43 +71,57 @@ internal class DrawImageProcessor : ImageProcessor /// /// Gets the location to draw the blended image /// - public Point Location { get; } + public Point BackgroundLocation { get; } /// protected override void OnFrameApply(ImageFrame source) { - Rectangle sourceRectangle = this.SourceRectangle; - Configuration configuration = this.Configuration; - - Image targetImage = this.Image; - PixelBlender 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.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.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 : ImageProcessor /// private readonly struct RowOperation : IRowOperation { - private readonly Buffer2D source; - private readonly Buffer2D target; + private readonly Buffer2D background; + private readonly Buffer2D foreground; private readonly PixelBlender 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 source, - Buffer2D target, - PixelBlender blender, Configuration configuration, - int minX, - int width, - int locationY, - int targetX, + Buffer2D background, + Buffer2D foreground, + Rectangle backgroundRectangle, + Rectangle foregroundRectangle, + PixelBlender 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 : ImageProcessor [MethodImpl(InliningOptions.ShortMethod)] public void Invoke(int y) { - Span background = this.source.DangerousGetRowSpan(y).Slice(this.minX, this.width); - Span foreground = this.target.DangerousGetRowSpan(y - this.locationY).Slice(this.targetX, this.width); + Span background = this.background.DangerousGetRowSpan(y + this.backgroundRectangle.Top).Slice(this.backgroundRectangle.Left, this.backgroundRectangle.Width); + Span foreground = this.foreground.DangerousGetRowSpan(y + this.foregroundRectangle.Top).Slice(this.foregroundRectangle.Left, this.foregroundRectangle.Width); this.blender.Blend(this.configuration, background, background, foreground, this.opacity); } } diff --git a/tests/ImageSharp.Tests/Drawing/DrawImageTests.cs b/tests/ImageSharp.Tests/Drawing/DrawImageTests.cs index d017e5ad4..c908ab3d4 100644 --- a/tests/ImageSharp.Tests/Drawing/DrawImageTests.cs +++ b/tests/ImageSharp.Tests/Drawing/DrawImageTests.cs @@ -112,9 +112,9 @@ public class DrawImageTests } [Theory] - [WithSolidFilledImages(100, 100, "White", PixelTypes.Rgba32, 0, 0)] - [WithSolidFilledImages(100, 100, "White", PixelTypes.Rgba32, 25, 25)] - [WithSolidFilledImages(100, 100, "White", PixelTypes.Rgba32, 75, 50)] + //[WithSolidFilledImages(100, 100, "White", PixelTypes.Rgba32, 0, 0)] + //[WithSolidFilledImages(100, 100, "White", PixelTypes.Rgba32, 25, 25)] + //[WithSolidFilledImages(100, 100, "White", PixelTypes.Rgba32, 75, 50)] [WithSolidFilledImages(100, 100, "White", PixelTypes.Rgba32, -25, -30)] public void WorksWithDifferentLocations(TestImageProvider provider, int x, int y) { @@ -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 provider, int x, int y) + [WithFile(TestImages.Png.Issue2447, PixelTypes.Rgba32)] + public void Issue2447_A(TestImageProvider provider) + where TPixel : unmanaged, IPixel { - using Image background = provider.GetImage(); - using Image overlay = new(Configuration.Default, 10, 10, Color.Black); - ImageProcessingException ex = Assert.Throws(Test); + using Image foreground = provider.GetImage(); + using Image 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(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image foreground = provider.GetImage(); + using Image 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(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image foreground = provider.GetImage(); + using Image 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); } } diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 8a676c02d..47e1809fc 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -132,6 +132,9 @@ public static class TestImages // Issue 2259: https://github.com/SixLabors/ImageSharp/issues/2259 public const string Issue2259 = "Png/issues/Issue_2259.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"; diff --git a/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/Issue2447_A.png b/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/Issue2447_A.png new file mode 100644 index 000000000..6bf7bb19b --- /dev/null +++ b/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 diff --git a/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/Issue2447_B.png b/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/Issue2447_B.png new file mode 100644 index 000000000..232184c4c --- /dev/null +++ b/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 diff --git a/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/Issue2447_C.png b/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/Issue2447_C.png new file mode 100644 index 000000000..fe4e44fbf --- /dev/null +++ b/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 diff --git a/tests/Images/Input/Png/issues/issue_2447.png b/tests/Images/Input/Png/issues/issue_2447.png new file mode 100644 index 000000000..3b79487c5 --- /dev/null +++ b/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 From bc29fbfb84bcd387468befeb0efaf86826e01193 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 12 Jun 2023 21:08:41 +1000 Subject: [PATCH 2/4] Restore tests --- tests/ImageSharp.Tests/Drawing/DrawImageTests.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/ImageSharp.Tests/Drawing/DrawImageTests.cs b/tests/ImageSharp.Tests/Drawing/DrawImageTests.cs index c908ab3d4..8b0db773a 100644 --- a/tests/ImageSharp.Tests/Drawing/DrawImageTests.cs +++ b/tests/ImageSharp.Tests/Drawing/DrawImageTests.cs @@ -112,9 +112,9 @@ public class DrawImageTests } [Theory] - //[WithSolidFilledImages(100, 100, "White", PixelTypes.Rgba32, 0, 0)] - //[WithSolidFilledImages(100, 100, "White", PixelTypes.Rgba32, 25, 25)] - //[WithSolidFilledImages(100, 100, "White", PixelTypes.Rgba32, 75, 50)] + [WithSolidFilledImages(100, 100, "White", PixelTypes.Rgba32, 0, 0)] + [WithSolidFilledImages(100, 100, "White", PixelTypes.Rgba32, 25, 25)] + [WithSolidFilledImages(100, 100, "White", PixelTypes.Rgba32, 75, 50)] [WithSolidFilledImages(100, 100, "White", PixelTypes.Rgba32, -25, -30)] public void WorksWithDifferentLocations(TestImageProvider provider, int x, int y) { From 3f65ff61b349cde06bb2020fe46976b695663c8f Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 12 Jun 2023 22:23:51 +1000 Subject: [PATCH 3/4] Restore constructor, fix casing --- .../Processors/Drawing/DrawImageProcessor.cs | 34 ++++++++++++++----- tests/ImageSharp.Tests/TestImages.cs | 2 +- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor.cs b/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor.cs index 98c17dc90..6ecf16fc6 100644 --- a/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor.cs @@ -14,23 +14,41 @@ public class DrawImageProcessor : IImageProcessor /// /// Initializes a new instance of the class. /// - /// The image to blend. + /// The image to blend. /// The location to draw the foreground image on the background. - /// The rectangular portion of the foreground image to draw. /// The blending mode to use when drawing the image. /// The Alpha blending mode to use when drawing the image. /// The opacity of the image to blend. public DrawImageProcessor( - Image image, + Image foreground, Point backgroundLocation, - Rectangle foregoundRectangle, PixelColorBlendingMode colorBlendingMode, PixelAlphaCompositionMode alphaCompositionMode, float opacity) + : this(foreground, backgroundLocation, foreground.Bounds, colorBlendingMode, alphaCompositionMode, opacity) { - this.Image = image; + } + + /// + /// Initializes a new instance of the class. + /// + /// The image to blend. + /// The location to draw the foreground image on the background. + /// The rectangular portion of the foreground image to draw. + /// The blending mode to use when drawing the image. + /// The Alpha blending mode to use when drawing the image. + /// The opacity of the image to blend. + public DrawImageProcessor( + Image foreground, + Point backgroundLocation, + Rectangle foregroundRectangle, + PixelColorBlendingMode colorBlendingMode, + PixelAlphaCompositionMode alphaCompositionMode, + float opacity) + { + this.ForeGround = foreground; this.BackgroundLocation = backgroundLocation; - this.ForegroundRectangle = foregoundRectangle; + this.ForegroundRectangle = foregroundRectangle; this.ColorBlendingMode = colorBlendingMode; this.AlphaCompositionMode = alphaCompositionMode; this.Opacity = opacity; @@ -39,7 +57,7 @@ public class DrawImageProcessor : IImageProcessor /// /// Gets the image to blend. /// - public Image Image { get; } + public Image ForeGround { get; } /// /// Gets the location to draw the foreground image on the background. @@ -71,7 +89,7 @@ public class DrawImageProcessor : IImageProcessor where TPixelBg : unmanaged, IPixel { ProcessorFactoryVisitor visitor = new(configuration, this, source); - this.Image.AcceptVisitor(visitor); + this.ForeGround.AcceptVisitor(visitor); return visitor.Result!; } diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 47e1809fc..ee22c3541 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -133,7 +133,7 @@ public static class TestImages public const string Issue2259 = "Png/issues/Issue_2259.png"; // Issue 2447: https://github.com/SixLabors/ImageSharp/issues/2447 - public const string Issue2447 = "Png/issues/Issue_2447.png"; + public const string Issue2447 = "Png/issues/issue_2447.png"; public static class Bad { From ff9ed1259b0437f321324b2e4c833f559a14142d Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 12 Jun 2023 22:42:36 +1000 Subject: [PATCH 4/4] Fix operations tests --- .../Drawing/DrawImageExtensionsTests.cs | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/tests/ImageSharp.Tests/Drawing/DrawImageExtensionsTests.cs b/tests/ImageSharp.Tests/Drawing/DrawImageExtensionsTests.cs index 26129c599..59e1bc4d8 100644 --- a/tests/ImageSharp.Tests/Drawing/DrawImageExtensionsTests.cs +++ b/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 image = new(Configuration.Default, 1, 1); + this.operations.DrawImage(image, 0.5f); DrawImageProcessor dip = this.Verify(); 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 image = new(Configuration.Default, 1, 1); + this.operations.DrawImage(image, PixelColorBlendingMode.Multiply, 0.5f); DrawImageProcessor dip = this.Verify(); 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 image = new(Configuration.Default, 1, 1); + this.operations.DrawImage(image, Point.Empty, 0.5f); DrawImageProcessor dip = this.Verify(); 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 image = new(Configuration.Default, 1, 1); + this.operations.DrawImage(image, Point.Empty, PixelColorBlendingMode.Multiply, 0.5f); DrawImageProcessor dip = this.Verify(); Assert.Equal(0.5, dip.Opacity);