@@ -21,6 +21,11 @@ public class ImageBrush : Brush
2121 /// </summary>
2222 private readonly RectangleF region ;
2323
24+ /// <summary>
25+ /// The offet to apply to the source image while applying the imagebrush
26+ /// </summary>
27+ private readonly Point offset ;
28+
2429 /// <summary>
2530 /// Initializes a new instance of the <see cref="ImageBrush"/> class.
2631 /// </summary>
@@ -33,12 +38,44 @@ public ImageBrush(Image image)
3338 /// <summary>
3439 /// Initializes a new instance of the <see cref="ImageBrush"/> class.
3540 /// </summary>
36- /// <param name="image">The source image.</param>
37- /// <param name="region">The region of interest within the source image to draw.</param>
41+ /// <param name="image">The image.</param>
42+ /// <param name="offset">
43+ /// An offset to apply the to image image while drawing apply the texture.
44+ /// </param>
45+ public ImageBrush ( Image image , Point offset )
46+ : this ( image , image . Bounds , offset )
47+ {
48+ }
49+
50+ /// <summary>
51+ /// Initializes a new instance of the <see cref="ImageBrush"/> class.
52+ /// </summary>
53+ /// <param name="image">The image.</param>
54+ /// <param name="region">
55+ /// The region of interest.
56+ /// This overrides any region used to initialize the brush applicator.
57+ /// </param>
3858 public ImageBrush ( Image image , RectangleF region )
59+ : this ( image , region , Point . Empty )
60+ {
61+ }
62+
63+ /// <summary>
64+ /// Initializes a new instance of the <see cref="ImageBrush"/> class.
65+ /// </summary>
66+ /// <param name="image">The image.</param>
67+ /// <param name="region">
68+ /// The region of interest.
69+ /// This overrides any region used to initialize the brush applicator.
70+ /// </param>
71+ /// <param name="offset">
72+ /// An offset to apply the to image image while drawing apply the texture.
73+ /// </param>
74+ public ImageBrush ( Image image , RectangleF region , Point offset )
3975 {
4076 this . image = image ;
4177 this . region = RectangleF . Intersect ( image . Bounds , region ) ;
78+ this . offset = offset ;
4279 }
4380
4481 /// <inheritdoc />
@@ -64,11 +101,11 @@ public override BrushApplicator<TPixel> CreateApplicator<TPixel>(
64101 {
65102 if ( this . image is Image < TPixel > specificImage )
66103 {
67- return new ImageBrushApplicator < TPixel > ( configuration , options , source , specificImage , region , this . region , false ) ;
104+ return new ImageBrushApplicator < TPixel > ( configuration , options , source , specificImage , region , this . region , this . offset , false ) ;
68105 }
69106
70107 specificImage = this . image . CloneAs < TPixel > ( ) ;
71- return new ImageBrushApplicator < TPixel > ( configuration , options , source , specificImage , region , this . region , true ) ;
108+ return new ImageBrushApplicator < TPixel > ( configuration , options , source , specificImage , region , this . region , this . offset , true ) ;
72109 }
73110
74111 /// <summary>
@@ -107,6 +144,7 @@ private class ImageBrushApplicator<TPixel> : BrushApplicator<TPixel>
107144 /// <param name="image">The image.</param>
108145 /// <param name="targetRegion">The region of the target image we will be drawing to.</param>
109146 /// <param name="sourceRegion">The region of the source image we will be using to source pixels to draw from.</param>
147+ /// <param name="offset">An offset to apply to the texture while drawing.</param>
110148 /// <param name="shouldDisposeImage">Whether to dispose the image on disposal of the applicator.</param>
111149 public ImageBrushApplicator (
112150 Configuration configuration ,
@@ -115,6 +153,7 @@ public ImageBrushApplicator(
115153 Image < TPixel > image ,
116154 RectangleF targetRegion ,
117155 RectangleF sourceRegion ,
156+ Point offset ,
118157 bool shouldDisposeImage )
119158 : base ( configuration , options , target )
120159 {
@@ -124,8 +163,8 @@ public ImageBrushApplicator(
124163
125164 this . sourceRegion = Rectangle . Intersect ( image . Bounds , ( Rectangle ) sourceRegion ) ;
126165
127- this . offsetY = ( int ) MathF . Max ( MathF . Floor ( targetRegion . Top ) , 0 ) ;
128- this . offsetX = ( int ) MathF . Max ( MathF . Floor ( targetRegion . Left ) , 0 ) ;
166+ this . offsetY = ( int ) MathF . Floor ( targetRegion . Top ) + offset . Y ;
167+ this . offsetX = ( int ) MathF . Floor ( targetRegion . Left ) + offset . X ;
129168 }
130169
131170 internal TPixel this [ int x , int y ]
@@ -166,14 +205,18 @@ public override void Apply(Span<float> scanline, int x, int y)
166205 Span < TPixel > overlaySpan = overlay . Memory . Span ;
167206
168207 int offsetX = x - this . offsetX ;
169- int sourceY = ( ( y - this . offsetY ) % this . sourceRegion . Height ) + this . sourceRegion . Y ;
208+ int sourceY = ( ( ( ( y - this . offsetY ) % this . sourceRegion . Height ) // clamp the number between -height and +height
209+ + this . sourceRegion . Height ) % this . sourceRegion . Height ) // clamp the number between 0 and +height
210+ + this . sourceRegion . Y ;
170211 Span < TPixel > sourceRow = this . sourceFrame . PixelBuffer . DangerousGetRowSpan ( sourceY ) ;
171212
172213 for ( int i = 0 ; i < scanline . Length ; i ++ )
173214 {
174215 amountSpan [ i ] = scanline [ i ] * this . Options . BlendPercentage ;
175216
176- int sourceX = ( ( i + offsetX ) % this . sourceRegion . Width ) + this . sourceRegion . X ;
217+ int sourceX = ( ( ( ( i + offsetX ) % this . sourceRegion . Width ) // clamp the number between -width and +width
218+ + this . sourceRegion . Width ) % this . sourceRegion . Width ) // clamp the number between 0 and +width
219+ + this . sourceRegion . X ;
177220
178221 overlaySpan [ i ] = sourceRow [ sourceX ] ;
179222 }
0 commit comments