11// Copyright (c) Six Labors.
22// Licensed under the Six Labors Split License.
33
4+ using System . Numerics ;
45using SixLabors . ImageSharp . Drawing . Processing . Backends ;
56
67namespace SixLabors . ImageSharp . Drawing . Processing ;
@@ -22,6 +23,7 @@ internal sealed class DrawingCanvasBatcher<TPixel>
2223 private CompositionSceneCommand [ ] commands ;
2324 private int commandCount ;
2425 private bool hasLayers ;
26+ private bool hasClips ;
2527
2628 internal DrawingCanvasBatcher (
2729 Configuration configuration ,
@@ -48,6 +50,7 @@ public void AddComposition(in CompositionCommand composition)
4850 this . EnsureCommandCapacity ( this . commandCount + 1 ) ;
4951 this . commands [ this . commandCount ++ ] = new PathCompositionSceneCommand ( composition ) ;
5052 this . hasLayers |= composition . Kind is not CompositionCommandKind . FillLayer ;
53+ this . hasClips |= composition . ClipPaths is not null ;
5154 }
5255
5356 /// <summary>
@@ -86,9 +89,10 @@ public void FlushCompositions()
8689
8790 try
8891 {
89- CompositionScene scene = new (
90- new ArraySegment < CompositionSceneCommand > ( this . commands , 0 , this . commandCount ) ,
91- this . hasLayers ) ;
92+ this . ApplyClipping ( ) ;
93+
94+ CompositionScene scene = new ( this . commands , this . hasLayers ) ;
95+
9296 this . backend . FlushCompositions ( this . configuration , this . TargetFrame , scene ) ;
9397 }
9498 finally
@@ -118,4 +122,51 @@ private void EnsureCommandCapacity(int requiredCapacity)
118122
119123 Array . Resize ( ref this . commands , nextCapacity ) ;
120124 }
125+
126+ private void ApplyClipping ( )
127+ {
128+ if ( ! this . hasClips )
129+ {
130+ return ;
131+ }
132+
133+ _ = Parallel . For ( 0 , this . commandCount , i =>
134+ {
135+ CompositionSceneCommand command = this . commands [ i ] ;
136+ if ( command is PathCompositionSceneCommand pathCommand )
137+ {
138+ CompositionCommand composition = pathCommand . Command ;
139+
140+ // If clipping is present we need to apply that now before handing the command
141+ // to the backend. This avoids complicating the backend with clipping logic
142+ // and allows us to reuse the same optimized backend code for clipped and unclipped paths.
143+ if ( composition . ClipPaths is { Count : > 0 } )
144+ {
145+ IPath path = composition . SourcePath ;
146+
147+ path = path . Transform ( composition . Transform ) ;
148+
149+ if ( composition . Pen is not null )
150+ {
151+ path = path . GenerateOutline ( composition . Pen . StrokeWidth ) ;
152+ }
153+
154+ path = path . Clip ( composition . ShapeOptions , composition . ClipPaths ) ;
155+
156+ RasterizerOptions rasterizerOptions = composition . RasterizerOptions ;
157+
158+ // Update the command with the clipped path.
159+ pathCommand . Command = CompositionCommand . Create (
160+ path ,
161+ composition . Brush . Transform ( composition . Transform ) ,
162+ composition . GraphicsOptions ,
163+ in rasterizerOptions ,
164+ composition . ShapeOptions ,
165+ Matrix4x4 . Identity ,
166+ composition . TargetBounds ,
167+ composition . DestinationOffset ) ;
168+ }
169+ }
170+ } ) ;
171+ }
121172}
0 commit comments