Skip to content

Commit ff269db

Browse files
atruskieJimBobSquarePants
authored andcommitted
Now throws a better excpetion DrawImage source does not overlap target (#877)
* No longer throws when DrawImage source does not overlap target Previously, when DrawImage was used to overlay an image, in cases where the source image did not overlap the target image, a very confusing error was reported: "System.ArgumentOutOfRangeException: Specified argument was out of the range of valid values. Parameter name: MaxDegreeOfParallelism" Now, when this case happens, the DrawImage method will simply not affect the target image, which is the same way FillRegionProcessor handles such cases. ParallelHelper.IterRows also now does more validation of the input rectangle so that any further cases of this kind of problem throw a more relvant exception. Note I switched from DebugGuard to Guard because IterRows is a public API and thus should always validate its inputs. Fixes #875 * Refines DrawImage non-overlap error logic Adresses PR feedback in #877. Changes DrawImage shortcut to be less than or equal to. Also changes maxX calculation to use `Right` over `Width`. This is a semantic change that reflects intention better. No actual change because Top,Left for that rectanngle should always be 0,0. And adds more informative argument names to ParallelHelper.IterRows error message. * Non-overlapping DrawImage now throws Adressing PR feedback from #877 DrawImage now throws when the source image does not overlap, with a useful error message. Also improved the error messages for IterRows (and added validation to the other IterRows method) * DrawImage overlap test changed to support RELEASE The tests on the CI server are run in RELEASE which wrap the expected exception with an ImageProcessingException. * Adress feedback for DrawImage exception DrawImage throws an ImageProcessor exception which makes it easier to catch. And reverted IterRows to use Guard helpers
1 parent 02d04c8 commit ff269db

File tree

4 files changed

+100
-2
lines changed

4 files changed

+100
-2
lines changed

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ protected override void OnFrameApply(ImageFrame<TPixelDst> source, Rectangle sou
7171
Rectangle bounds = targetImage.Bounds();
7272

7373
int minX = Math.Max(this.Location.X, sourceRectangle.X);
74-
int maxX = Math.Min(this.Location.X + bounds.Width, sourceRectangle.Width);
74+
int maxX = Math.Min(this.Location.X + bounds.Width, sourceRectangle.Right);
7575
int targetX = minX - this.Location.X;
7676

7777
int minY = Math.Max(this.Location.Y, sourceRectangle.Y);
@@ -81,6 +81,12 @@ protected override void OnFrameApply(ImageFrame<TPixelDst> source, Rectangle sou
8181

8282
var workingRect = Rectangle.FromLTRB(minX, minY, maxX, maxY);
8383

84+
// not a valid operation because rectangle does not overlap with this image.
85+
if (workingRect.Width <= 0 || workingRect.Height <= 0)
86+
{
87+
throw new ImageProcessingException("Cannot draw image because the source image does not overlap the target image.");
88+
}
89+
8490
ParallelHelper.IterateRows(
8591
workingRect,
8692
configuration,

src/ImageSharp/Common/ParallelUtils/ParallelHelper.cs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ public static void IterateRows(
4545
in ParallelExecutionSettings parallelSettings,
4646
Action<RowInterval> body)
4747
{
48-
DebugGuard.MustBeGreaterThan(rectangle.Width, 0, nameof(rectangle));
48+
ValidateRectangle(rectangle);
4949

5050
int maxSteps = DivideCeil(rectangle.Width * rectangle.Height, parallelSettings.MinimumPixelsProcessedPerTask);
5151

@@ -87,6 +87,8 @@ public static void IterateRowsWithTempBuffer<T>(
8787
Action<RowInterval, Memory<T>> body)
8888
where T : unmanaged
8989
{
90+
ValidateRectangle(rectangle);
91+
9092
int maxSteps = DivideCeil(rectangle.Width * rectangle.Height, parallelSettings.MinimumPixelsProcessedPerTask);
9193

9294
int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps);
@@ -142,5 +144,18 @@ public static void IterateRowsWithTempBuffer<T>(
142144

143145
[MethodImpl(MethodImplOptions.AggressiveInlining)]
144146
private static int DivideCeil(int dividend, int divisor) => 1 + ((dividend - 1) / divisor);
147+
148+
private static void ValidateRectangle(Rectangle rectangle)
149+
{
150+
Guard.MustBeGreaterThan(
151+
rectangle.Width,
152+
0,
153+
$"{nameof(rectangle)}.{nameof(rectangle.Width)}");
154+
155+
Guard.MustBeGreaterThan(
156+
rectangle.Height,
157+
0,
158+
$"{nameof(rectangle)}.{nameof(rectangle.Height)}");
159+
}
145160
}
146161
}

tests/ImageSharp.Tests/Drawing/DrawImageTest.cs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,48 @@ public void ImageShouldHandlePositiveLocation(TestImageProvider<Rgba32> provider
131131
background.DebugSave(provider, testOutputDetails: "Positive");
132132
}
133133
}
134+
[Theory]
135+
[WithSolidFilledImages(100, 100, 255, 255, 255, PixelTypes.Rgba32)]
136+
public void ImageShouldHandlePositiveLocationTruncatedOverlay(TestImageProvider<Rgba32> provider)
137+
{
138+
using (Image<Rgba32> background = provider.GetImage())
139+
using (var overlay = new Image<Rgba32>(50, 50))
140+
{
141+
overlay.Mutate(x => x.Fill(Rgba32.Black));
142+
143+
const int xy = 75;
144+
Rgba32 backgroundPixel = background[xy - 1, xy - 1];
145+
Rgba32 overlayPixel = overlay[0, 0];
146+
147+
background.Mutate(x => x.DrawImage(overlay, new Point(xy, xy), PixelColorBlendingMode.Normal, 1F));
148+
149+
Assert.Equal(Rgba32.White, backgroundPixel);
150+
Assert.Equal(overlayPixel, background[xy, xy]);
151+
152+
background.DebugSave(provider, testOutputDetails: "PositiveTruncated");
153+
}
154+
}
155+
156+
[Theory]
157+
[WithSolidFilledImages(100, 100, 255, 255, 255, PixelTypes.Rgba32, -30, -30)]
158+
[WithSolidFilledImages(100, 100, 255, 255, 255, PixelTypes.Rgba32, 130, -30)]
159+
[WithSolidFilledImages(100, 100, 255, 255, 255, PixelTypes.Rgba32, 130, 130)]
160+
[WithSolidFilledImages(100, 100, 255, 255, 255, PixelTypes.Rgba32, -30, 130)]
161+
public void NonOverlappingImageThrows(TestImageProvider<Rgba32> provider, int x, int y)
162+
{
163+
using (Image<Rgba32> background = provider.GetImage())
164+
using (var overlay = new Image<Rgba32>(Configuration.Default, 10, 10, Rgba32.Black))
165+
{
166+
ImageProcessingException ex = Assert.Throws<ImageProcessingException>(Test);
167+
168+
Assert.Contains("does not overlap", ex.ToString());
169+
170+
void Test()
171+
{
172+
background.Mutate(context => context.DrawImage(overlay, new Point(x, y), GraphicsOptions.Default));
173+
}
174+
}
175+
}
134176

135177
private static void VerifyImage<TPixel>(
136178
TestImageProvider<TPixel> provider,

tests/ImageSharp.Tests/Helpers/ParallelHelperTests.cs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
using SixLabors.ImageSharp.Memory;
1111
using SixLabors.ImageSharp.ParallelUtils;
12+
using SixLabors.ImageSharp.PixelFormats;
1213
using SixLabors.Memory;
1314
using SixLabors.Primitives;
1415

@@ -334,5 +335,39 @@ void FillRow(int y, Buffer2D<Point> buffer)
334335
TestImageExtensions.CompareBuffers(expected.Span, actual.Span);
335336
}
336337
}
338+
339+
[Theory]
340+
[InlineData(0, 10)]
341+
[InlineData(10, 0)]
342+
[InlineData(-10, 10)]
343+
[InlineData(10, -10)]
344+
public void IterateRowsRequiresValidRectangle(int width, int height)
345+
{
346+
var parallelSettings = new ParallelExecutionSettings();
347+
348+
var rect = new Rectangle(0, 0, width, height);
349+
350+
ArgumentOutOfRangeException ex = Assert.Throws<ArgumentOutOfRangeException>(
351+
() => ParallelHelper.IterateRows(rect, parallelSettings, (rows) => { }));
352+
353+
Assert.Contains(width <= 0 ? "Width" : "Height", ex.Message);
354+
}
355+
356+
[Theory]
357+
[InlineData(0, 10)]
358+
[InlineData(10, 0)]
359+
[InlineData(-10, 10)]
360+
[InlineData(10, -10)]
361+
public void IterateRowsWithTempBufferRequiresValidRectangle(int width, int height)
362+
{
363+
var parallelSettings = new ParallelExecutionSettings();
364+
365+
var rect = new Rectangle(0, 0, width, height);
366+
367+
ArgumentOutOfRangeException ex = Assert.Throws<ArgumentOutOfRangeException>(
368+
() => ParallelHelper.IterateRowsWithTempBuffer<Rgba32>(rect, parallelSettings, (rows, memory) => { }));
369+
370+
Assert.Contains(width <= 0 ? "Width" : "Height", ex.Message);
371+
}
337372
}
338373
}

0 commit comments

Comments
 (0)