@@ -21,36 +21,42 @@ internal class DrawImageProcessor<TPixelBg, TPixelFg> : ImageProcessor<TPixelBg>
2121 /// Initializes a new instance of the <see cref="DrawImageProcessor{TPixelBg, TPixelFg}"/> class.
2222 /// </summary>
2323 /// <param name="configuration">The configuration which allows altering default behaviour or extending the library.</param>
24- /// <param name="image ">The foreground <see cref="Image{TPixelFg}"/> to blend with the currently processing image.</param>
25- /// <param name="source ">The source <see cref="Image{TPixelBg}"/> for the current processor instance.</param>
26- /// <param name="sourceRectangle ">The source area to process for the current processor instance .</param>
27- /// <param name="location ">The location to draw the blended image .</param>
24+ /// <param name="foregroundImage ">The foreground <see cref="Image{TPixelFg}"/> to blend with the currently processing image.</param>
25+ /// <param name="backgroundImage ">The source <see cref="Image{TPixelBg}"/> for the current processor instance.</param>
26+ /// <param name="backgroundLocation ">The location to draw the blended image .</param>
27+ /// <param name="foregroundRectangle ">The source area to process for the current processor instance .</param>
2828 /// <param name="colorBlendingMode">The blending mode to use when drawing the image.</param>
29- /// <param name="alphaCompositionMode">The Alpha blending mode to use when drawing the image.</param>
29+ /// <param name="alphaCompositionMode">The alpha blending mode to use when drawing the image.</param>
3030 /// <param name="opacity">The opacity of the image to blend. Must be between 0 and 1.</param>
3131 public DrawImageProcessor (
3232 Configuration configuration ,
33- Image < TPixelFg > image ,
34- Image < TPixelBg > source ,
35- Rectangle sourceRectangle ,
36- Point location ,
33+ Image < TPixelFg > foregroundImage ,
34+ Image < TPixelBg > backgroundImage ,
35+ Point backgroundLocation ,
36+ Rectangle foregroundRectangle ,
3737 PixelColorBlendingMode colorBlendingMode ,
3838 PixelAlphaCompositionMode alphaCompositionMode ,
3939 float opacity )
40- : base ( configuration , source , sourceRectangle )
40+ : base ( configuration , backgroundImage , backgroundImage . Bounds )
4141 {
4242 Guard . MustBeBetweenOrEqualTo ( opacity , 0 , 1 , nameof ( opacity ) ) ;
4343
44- this . Image = image ;
44+ this . ForegroundImage = foregroundImage ;
45+ this . ForegroundRectangle = foregroundRectangle ;
4546 this . Opacity = opacity ;
4647 this . Blender = PixelOperations < TPixelBg > . Instance . GetPixelBlender ( colorBlendingMode , alphaCompositionMode ) ;
47- this . Location = location ;
48+ this . BackgroundLocation = backgroundLocation ;
4849 }
4950
5051 /// <summary>
5152 /// Gets the image to blend
5253 /// </summary>
53- public Image < TPixelFg > Image { get ; }
54+ public Image < TPixelFg > ForegroundImage { get ; }
55+
56+ /// <summary>
57+ /// Gets the rectangular portion of the foreground image to draw.
58+ /// </summary>
59+ public Rectangle ForegroundRectangle { get ; }
5460
5561 /// <summary>
5662 /// Gets the opacity of the image to blend
@@ -65,43 +71,57 @@ public DrawImageProcessor(
6571 /// <summary>
6672 /// Gets the location to draw the blended image
6773 /// </summary>
68- public Point Location { get ; }
74+ public Point BackgroundLocation { get ; }
6975
7076 /// <inheritdoc/>
7177 protected override void OnFrameApply ( ImageFrame < TPixelBg > source )
7278 {
73- Rectangle sourceRectangle = this . SourceRectangle ;
74- Configuration configuration = this . Configuration ;
75-
76- Image < TPixelFg > targetImage = this . Image ;
77- PixelBlender < TPixelBg > blender = this . Blender ;
78- int locationY = this . Location . Y ;
79+ // Align the bounds so that both the source and targets are the same width and height for blending.
80+ // We ensure that negative locations are subtracted from both bounds so that foreground images can partially overlap.
81+ Rectangle foregroundRectangle = this . ForegroundRectangle ;
7982
80- // Align start/end positions.
81- Rectangle bounds = targetImage . Bounds ;
83+ // Sanitize the location so that we don't try and sample outside the image.
84+ int left = this . BackgroundLocation . X ;
85+ int top = this . BackgroundLocation . Y ;
8286
83- int minX = Math . Max ( this . Location . X , sourceRectangle . X ) ;
84- int maxX = Math . Min ( this . Location . X + bounds . Width , sourceRectangle . Right ) ;
85- int targetX = minX - this . Location . X ;
86-
87- int minY = Math . Max ( this . Location . Y , sourceRectangle . Y ) ;
88- int maxY = Math . Min ( this . Location . Y + bounds . Height , sourceRectangle . Bottom ) ;
89-
90- int width = maxX - minX ;
87+ if ( this . BackgroundLocation . X < 0 )
88+ {
89+ foregroundRectangle . Width += this . BackgroundLocation . X ;
90+ left = 0 ;
91+ }
9192
92- Rectangle workingRect = Rectangle . FromLTRB ( minX , minY , maxX , maxY ) ;
93+ if ( this . BackgroundLocation . Y < 0 )
94+ {
95+ foregroundRectangle . Height += this . BackgroundLocation . Y ;
96+ top = 0 ;
97+ }
9398
94- // Not a valid operation because rectangle does not overlap with this image.
95- if ( workingRect . Width <= 0 || workingRect . Height <= 0 )
99+ int width = foregroundRectangle . Width ;
100+ int height = foregroundRectangle . Height ;
101+ if ( width <= 0 || height <= 0 )
96102 {
97- throw new ImageProcessingException (
98- "Cannot draw image because the source image does not overlap the target image." ) ;
103+ // Nothing to do, return.
104+ return ;
99105 }
100106
101- DrawImageProcessor < TPixelBg , TPixelFg > . RowOperation operation = new ( source . PixelBuffer , targetImage . Frames . RootFrame . PixelBuffer , blender , configuration , minX , width , locationY , targetX , this . Opacity ) ;
107+ // Sanitize the dimensions so that we don't try and sample outside the image.
108+ foregroundRectangle = Rectangle . Intersect ( foregroundRectangle , this . ForegroundImage . Bounds ) ;
109+ Rectangle backgroundRectangle = Rectangle . Intersect ( new ( left , top , width , height ) , this . SourceRectangle ) ;
110+ Configuration configuration = this . Configuration ;
111+
112+ DrawImageProcessor < TPixelBg , TPixelFg > . RowOperation operation =
113+ new (
114+ configuration ,
115+ source . PixelBuffer ,
116+ this . ForegroundImage . Frames . RootFrame . PixelBuffer ,
117+ backgroundRectangle ,
118+ foregroundRectangle ,
119+ this . Blender ,
120+ this . Opacity ) ;
121+
102122 ParallelRowIterator . IterateRows (
103123 configuration ,
104- workingRect ,
124+ new ( 0 , 0 , foregroundRectangle . Width , foregroundRectangle . Height ) ,
105125 in operation ) ;
106126 }
107127
@@ -110,45 +130,39 @@ protected override void OnFrameApply(ImageFrame<TPixelBg> source)
110130 /// </summary>
111131 private readonly struct RowOperation : IRowOperation
112132 {
113- private readonly Buffer2D < TPixelBg > source ;
114- private readonly Buffer2D < TPixelFg > target ;
133+ private readonly Buffer2D < TPixelBg > background ;
134+ private readonly Buffer2D < TPixelFg > foreground ;
115135 private readonly PixelBlender < TPixelBg > blender ;
116136 private readonly Configuration configuration ;
117- private readonly int minX ;
118- private readonly int width ;
119- private readonly int locationY ;
120- private readonly int targetX ;
137+ private readonly Rectangle foregroundRectangle ;
138+ private readonly Rectangle backgroundRectangle ;
121139 private readonly float opacity ;
122140
123141 [ MethodImpl ( InliningOptions . ShortMethod ) ]
124142 public RowOperation (
125- Buffer2D < TPixelBg > source ,
126- Buffer2D < TPixelFg > target ,
127- PixelBlender < TPixelBg > blender ,
128143 Configuration configuration ,
129- int minX ,
130- int width ,
131- int locationY ,
132- int targetX ,
144+ Buffer2D < TPixelBg > background ,
145+ Buffer2D < TPixelFg > foreground ,
146+ Rectangle backgroundRectangle ,
147+ Rectangle foregroundRectangle ,
148+ PixelBlender < TPixelBg > blender ,
133149 float opacity )
134150 {
135- this . source = source ;
136- this . target = target ;
137- this . blender = blender ;
138151 this . configuration = configuration ;
139- this . minX = minX ;
140- this . width = width ;
141- this . locationY = locationY ;
142- this . targetX = targetX ;
152+ this . background = background ;
153+ this . foreground = foreground ;
154+ this . backgroundRectangle = backgroundRectangle ;
155+ this . foregroundRectangle = foregroundRectangle ;
156+ this . blender = blender ;
143157 this . opacity = opacity ;
144158 }
145159
146160 /// <inheritdoc/>
147161 [ MethodImpl ( InliningOptions . ShortMethod ) ]
148162 public void Invoke ( int y )
149163 {
150- Span < TPixelBg > background = this . source . DangerousGetRowSpan ( y ) . Slice ( this . minX , this . width ) ;
151- Span < TPixelFg > foreground = this . target . DangerousGetRowSpan ( y - this . locationY ) . Slice ( this . targetX , this . width ) ;
164+ Span < TPixelBg > background = this . background . DangerousGetRowSpan ( y + this . backgroundRectangle . Top ) . Slice ( this . backgroundRectangle . Left , this . backgroundRectangle . Width ) ;
165+ Span < TPixelFg > foreground = this . foreground . DangerousGetRowSpan ( y + this . foregroundRectangle . Top ) . Slice ( this . foregroundRectangle . Left , this . foregroundRectangle . Width ) ;
152166 this . blender . Blend < TPixelFg > ( this . configuration , background , background , foreground , this . opacity ) ;
153167 }
154168 }
0 commit comments