66namespace SixLabors . ImageSharp . Drawing . Processing . Processors . Drawing ;
77
88/// <summary>
9- /// The main workhorse class. This has access to the pixel buffer but
10- /// in an abstract/generic way .
9+ /// Applies a processing operation to a clipped path region by constraining the operation's input domain
10+ /// to the bounds of the path, then using the processed result as an image brush to fill the path .
1111/// </summary>
1212/// <typeparam name="TPixel">The type of pixel.</typeparam>
1313internal class ClipPathProcessor < TPixel > : IImageProcessor < TPixel >
@@ -32,34 +32,41 @@ public void Dispose()
3232
3333 public void Execute ( )
3434 {
35- // Clone out our source image so we can apply various effects to it without mutating
36- // the original yet.
37- using Image < TPixel > clone = this . source . Clone ( this . definition . Operation ) ;
35+ // Bounds in drawing are floating point. We must conservatively cover the entire shape bounds.
36+ RectangleF boundsF = this . definition . Region . Bounds ;
3837
39- // Use an image brush to apply cloned image as the source for filling the shape.
40- // We pass explicit bounds to avoid the need to crop the clone;
41- RectangleF bounds = this . definition . Region . Bounds ;
38+ int left = ( int ) MathF . Floor ( boundsF . Left ) ;
39+ int top = ( int ) MathF . Floor ( boundsF . Top ) ;
40+ int right = ( int ) MathF . Ceiling ( boundsF . Right ) ;
41+ int bottom = ( int ) MathF . Ceiling ( boundsF . Bottom ) ;
4242
43- // add some clamping offsets to the brush to account for the target drawing location due to the cloned image not fill the image as expected
44- int offsetX = 0 ;
45- int offsetY = 0 ;
46- if ( bounds . X < 0 )
47- {
48- offsetX = - ( int ) MathF . Floor ( bounds . X ) ;
49- }
43+ Rectangle crop = Rectangle . FromLTRB ( left , top , right , bottom ) ;
5044
51- if ( bounds . Y < 0 )
45+ // Constrain the operation to the intersection of the requested bounds and source region.
46+ Rectangle clipped = Rectangle . Intersect ( this . sourceRectangle , crop ) ;
47+
48+ if ( clipped . Width <= 0 || clipped . Height <= 0 )
5249 {
53- offsetY = - ( int ) MathF . Floor ( bounds . Y ) ;
50+ return ;
5451 }
5552
56- ImageBrush brush = new ( clone , bounds , new Point ( offsetX , offsetY ) ) ;
53+ Action < IImageProcessingContext > operation = this . definition . Operation ;
5754
58- // Grab hold of an image processor that can fill paths with a brush to allow it to do the hard pixel pushing for us
59- FillPathProcessor processor = new ( this . definition . Options , brush , this . definition . Region ) ;
60- using IImageProcessor < TPixel > p = processor . CreatePixelSpecificProcessor ( this . configuration , this . source , this . sourceRectangle ) ;
55+ // Run the operation on the clipped context so only pixels inside the clip are affected,
56+ // matching the expected semantics of clipping in other graphics APIs.
57+ using Image < TPixel > clone = this . source . Clone ( ctx => operation ( ctx . Crop ( clipped ) ) ) ;
6158
62- // Fill the shape using the image brush
63- p . Execute ( ) ;
59+ // Use the clone as a brush source so only the clipped result contributes to the fill,
60+ // keeping the effect confined to the clipped region.
61+ Point brushOffset = new (
62+ clipped . X - ( int ) MathF . Floor ( boundsF . Left ) ,
63+ clipped . Y - ( int ) MathF . Floor ( boundsF . Top ) ) ;
64+
65+ ImageBrush brush = new ( clone , clone . Bounds , brushOffset ) ;
66+
67+ // Fill the shape using the image brush.
68+ FillPathProcessor processor = new ( this . definition . Options , brush , this . definition . Region ) ;
69+ using IImageProcessor < TPixel > pixelProcessor = processor . CreatePixelSpecificProcessor ( this . configuration , this . source , this . sourceRectangle ) ;
70+ pixelProcessor . Execute ( ) ;
6471 }
6572}
0 commit comments