@@ -178,6 +178,75 @@ public void DrawImage_WithForeignPixelFormat_WholeImage_MatchesFullConversion<TP
178178 new RectangleF ( 24 , 20 , 260 , 200 ) ,
179179 new Matrix4x4 ( Matrix3x2 . CreateRotation ( 0.15F , new Vector2 ( 160 , 120 ) ) ) ) ;
180180
181+ [ Theory ]
182+ [ WithBasicTestPatternImages ( 320 , 240 , PixelTypes . Rgba32 ) ]
183+ public void DrawImage_WithEmptySourceRect_IsNoOp < TPixel > ( TestImageProvider < TPixel > provider )
184+ where TPixel : unmanaged, IPixel < TPixel >
185+ => AssertDrawImageIsNoOp (
186+ provider ,
187+ new Rectangle ( 40 , 30 , 0 , 120 ) ,
188+ new RectangleF ( 20 , 20 , 200 , 160 ) ) ;
189+
190+ [ Theory ]
191+ [ WithBasicTestPatternImages ( 320 , 240 , PixelTypes . Rgba32 ) ]
192+ public void DrawImage_WithEmptyDestinationRect_IsNoOp < TPixel > ( TestImageProvider < TPixel > provider )
193+ where TPixel : unmanaged, IPixel < TPixel >
194+ => AssertDrawImageIsNoOp (
195+ provider ,
196+ new Rectangle ( 40 , 30 , 180 , 150 ) ,
197+ new RectangleF ( 20 , 20 , 200 , 0 ) ) ;
198+
199+ [ Theory ]
200+ [ WithBasicTestPatternImages ( 320 , 240 , PixelTypes . Rgba32 ) ]
201+ public void DrawImage_WithSourceRectFullyOutsideImage_IsNoOp < TPixel > ( TestImageProvider < TPixel > provider )
202+ where TPixel : unmanaged, IPixel < TPixel >
203+ => AssertDrawImageIsNoOp (
204+ provider ,
205+ new Rectangle ( 400 , 300 , 120 , 100 ) ,
206+ new RectangleF ( 20 , 20 , 200 , 160 ) ) ;
207+
208+ /// <summary>
209+ /// A draw whose clipped source/destination region is empty must be a no-op for both the
210+ /// typed <see cref="Image{TPixel}"/> overload and the foreign-pixel-format <see cref="Image"/>
211+ /// overload, leaving the cleared background untouched.
212+ /// </summary>
213+ private static void AssertDrawImageIsNoOp < TPixel > (
214+ TestImageProvider < TPixel > provider ,
215+ Rectangle sourceRect ,
216+ RectangleF destinationRect )
217+ where TPixel : unmanaged, IPixel < TPixel >
218+ {
219+ using Image < TPixel > source = provider . GetImage ( ) ;
220+
221+ // A source image whose pixel format differs from the canvas, forcing the foreign-format path.
222+ using Image < Rgb24 > foreignSource = source . CloneAs < Rgb24 > ( ) ;
223+
224+ // The reference is the cleared background: a degenerate draw must not change any pixel.
225+ using Image < TPixel > expected = new ( source . Width , source . Height ) ;
226+ using ( DrawingCanvas < TPixel > reference = CreateCanvas ( provider , expected , new DrawingOptions ( ) ) )
227+ {
228+ reference . Clear ( Brushes . Solid ( Color . White ) ) ;
229+ }
230+
231+ void AssertNoOp ( Action < DrawingCanvas < TPixel > > draw )
232+ {
233+ using Image < TPixel > actual = new ( source . Width , source . Height ) ;
234+ using ( DrawingCanvas < TPixel > canvas = CreateCanvas ( provider , actual , new DrawingOptions ( ) ) )
235+ {
236+ canvas . Clear ( Brushes . Solid ( Color . White ) ) ;
237+ draw ( canvas ) ;
238+ }
239+
240+ ImageComparer . Exact . VerifySimilarity ( expected , actual ) ;
241+ }
242+
243+ // Typed overload -> DrawImageCore empty-region early-return.
244+ AssertNoOp ( canvas => canvas . DrawImage ( source , sourceRect , destinationRect , KnownResamplers . Bicubic ) ) ;
245+
246+ // Foreign-format overload -> DrawImage empty-region early-return before any conversion.
247+ AssertNoOp ( canvas => canvas . DrawImage ( ( Image ) foreignSource , sourceRect , destinationRect , KnownResamplers . Bicubic ) ) ;
248+ }
249+
181250 /// <summary>
182251 /// Drawing a foreign-pixel-format image (which converts only the clipped source region) must
183252 /// produce pixels identical to first converting the whole image to the canvas format and drawing that.
0 commit comments