Skip to content

Commit fbdb8c6

Browse files
committed
🧿 Fixes for tiled masking.
1 parent 43a9ea8 commit fbdb8c6

File tree

5 files changed

+129
-15
lines changed

5 files changed

+129
-15
lines changed

‎src/composite/compositor.rs‎

Lines changed: 113 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use std::cmp::min;
22

3-
use crate::{BlendMode, Color, Image, Point};
3+
use crate::{BlendMode, Color, Image, Mask, Point};
44

55
use super::blend::{self, RgbaColor};
66
use super::operation::Operation;
@@ -74,16 +74,18 @@ pub fn draw_layer_over_image(image: &mut Image, layer: &Layer) {
7474
let target_offset = ((target_y_offset + y) * image.bytes_per_row) as i32;
7575
let target_offset = (target_offset + (start_x as i32) * 4) as usize;
7676
// Using a second loop was a tiny bit faster than splicing the vec.
77-
for x in (0..required_width * 4).step_by(4) {
77+
for x in 0..required_width {
78+
let alpha = layer.mask_alpha(Point {
79+
x: x as u32,
80+
y: y as u32,
81+
});
82+
if alpha == 0 {
83+
continue;
84+
}
85+
let mask_opacity = alpha as f32 / u8::MAX as f32;
86+
let x = x * 4;
7887
let x_position = x + x_offset;
7988
let x_position = (x_position as f32 * pixel_ratio_x).floor() as usize;
80-
if layer.mask_alpha(Point {
81-
x: x_position as u32,
82-
y: y_position as u32,
83-
}) == 0
84-
{
85-
break;
86-
}
8789
let start = offset + x_position;
8890
let blend_color = pixel_data(&layer.image.data, start);
8991
let blend_color: Color = blend_color.into();
@@ -96,7 +98,7 @@ pub fn draw_layer_over_image(image: &mut Image, layer: &Layer) {
9698
&mut base_color,
9799
&blend_color,
98100
layer.blend_mode,
99-
layer.opacity,
101+
layer.opacity * mask_opacity,
100102
);
101103
// let base_color = Color::RED;
102104

@@ -118,6 +120,7 @@ impl Image {
118120
) {
119121
let mut layer = Layer::new(image, location.into());
120122
layer.blend_mode = options.blend_mode.to_owned();
123+
layer.masks = options.masks.to_owned();
121124
draw_layer_over_image(self, &layer);
122125
}
123126
}
@@ -213,12 +216,16 @@ fn blend_colors(color: &mut Color, blend_color: &Color, blend_mode: BlendMode, o
213216
pub struct CompositeOperationOptions<'a> {
214217
/// The blend mode.
215218
pub blend_mode: BlendMode,
216-
/// The mask image.
217-
pub mask_image: Option<&'a Image>,
219+
/// The masks.
220+
pub masks: Vec<Mask<'a>>,
218221
}
219222

220223
#[cfg(test)]
221224
mod test {
225+
use std::borrow::Cow;
226+
227+
use crate::{Mask, Size, TiledMask};
228+
222229
use super::*;
223230

224231
#[test]
@@ -251,4 +258,98 @@ mod test {
251258
assert_eq!(color.blue, 0xff, "Blues don’t match.");
252259
assert_eq!(color.alpha, 153, "Alphas don’t match.");
253260
}
261+
262+
#[test]
263+
fn draw_layer_with_tiled_mask() {
264+
let mut base_image = Image::color(
265+
&Color::from_rgb_u32(0x639bff),
266+
Size {
267+
width: 16,
268+
height: 8,
269+
},
270+
);
271+
let image = Image::color(
272+
&Color::from_rgb_u32(0xcbdbfc),
273+
Size {
274+
width: 13,
275+
height: 6,
276+
},
277+
);
278+
let position = Point { x: 1.0, y: 1.0 };
279+
let mut layer = Layer::new(&image, position);
280+
281+
// Setting up a checkerboard image.
282+
let mut image = Image::empty(Size {
283+
width: 2,
284+
height: 2,
285+
});
286+
image.set_pixel_color(Color::BLACK, Point::zero());
287+
image.set_pixel_color(Color::BLACK, Point { x: 1, y: 1 });
288+
289+
let mask = TiledMask {
290+
image: Cow::Borrowed(&image),
291+
offset: Point { x: 1, y: 0 },
292+
};
293+
let mask = Mask::Tiled(mask);
294+
layer.masks = vec![mask];
295+
296+
draw_layer_over_image(&mut base_image, &layer);
297+
298+
// Reposition the layer and draw again to make sure
299+
// that the mask stays put.
300+
layer.position.x += 1.0;
301+
draw_layer_over_image(&mut base_image, &layer);
302+
303+
// base_image.save("/tmp/tiled_mask.png").unwrap();
304+
305+
let expected_image = Image::open("tests/images/tiled_mask.png").unwrap();
306+
307+
assert!(base_image.appears_equal_to(&expected_image));
308+
}
309+
310+
#[test]
311+
fn draw_layer_with_tiled_mask_negative_position() {
312+
let mut base_image = Image::color(
313+
&Color::from_rgb_u32(0x639bff),
314+
Size {
315+
width: 16,
316+
height: 8,
317+
},
318+
);
319+
let image = Image::color(
320+
&Color::from_rgb_u32(0xcbdbfc),
321+
Size {
322+
width: 13,
323+
height: 6,
324+
},
325+
);
326+
let position = Point { x: -1.0, y: -1.0 };
327+
let mut layer = Layer::new(&image, position);
328+
329+
// Setting up a checkerboard image.
330+
let mut image = Image::empty(Size {
331+
width: 3,
332+
height: 3,
333+
});
334+
image.set_pixel_color(Color::BLACK, Point::zero());
335+
image.set_pixel_color(Color::BLACK, Point { x: 1, y: 1 });
336+
337+
let mask = TiledMask {
338+
image: Cow::Borrowed(&image),
339+
offset: Point { x: 0, y: 0 },
340+
};
341+
let mask = Mask::Tiled(mask);
342+
layer.masks = vec![mask];
343+
344+
draw_layer_over_image(&mut base_image, &layer);
345+
346+
layer.position.x -= 1.0;
347+
draw_layer_over_image(&mut base_image, &layer);
348+
349+
// base_image.save("/tmp/tiled_mask_negative.png").unwrap();
350+
351+
let expected_image = Image::open("tests/images/tiled_mask_negative.png").unwrap();
352+
353+
assert!(base_image.appears_equal_to(&expected_image));
354+
}
254355
}

‎src/composite/layer.rs‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,18 @@ impl<'a> Layer<'a> {
7070
positioned_mask.image.pixel_color(location)
7171
}
7272
Mask::Tiled(tiled_mask) => {
73+
if self.position.x >= 0.0 {
74+
location.x += self.position.x as i32;
75+
}
76+
if self.position.y >= 0.0 {
77+
location.y += self.position.y as i32;
78+
}
7379
location -= tiled_mask.offset;
7480
let width = tiled_mask.image.size.width as i32;
7581
let height = tiled_mask.image.size.height as i32;
82+
while location.x < 0 {
83+
location.x += width;
84+
}
7685
if location.x < 0 {
7786
location.x = width + (location.x % width);
7887
}

‎src/mask.rs‎

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use std::borrow::Cow;
33
use crate::{Image, Point, Rect};
44

55
/// Defines a mask.
6-
#[derive(Debug, Clone)]
6+
#[derive(Debug, Clone, PartialEq)]
77
pub enum Mask<'a> {
88
/// A positioned mask.
99
Positioned(PositionedMask<'a>),
@@ -21,17 +21,21 @@ pub trait BoundedMask {
2121
fn bounding_box(&self) -> Rect<i32>;
2222
}
2323

24-
#[derive(Debug, Clone)]
24+
#[derive(Debug, Clone, PartialEq)]
2525
/// A mask image that is contained within a bounding box.
2626
pub struct PositionedMask<'a> {
27+
/// The mask image.
2728
pub image: Cow<'a, Image>,
29+
/// The location at which to position the image on the canvas.
2830
pub origin: Point<i32>,
2931
}
3032

31-
#[derive(Debug, Clone)]
33+
#[derive(Debug, Clone, PartialEq)]
3234
/// A mask image that is tiled across the canvas.
3335
pub struct TiledMask<'a> {
36+
/// The image that represents the mask.
3437
pub image: Cow<'a, Image>,
38+
/// The offset location from which to start the tile.
3539
pub offset: Point<i32>,
3640
}
3741

113 Bytes
Loading
268 Bytes
Loading

0 commit comments

Comments
 (0)