rvimage_domain/
canvas.rs

1use image::{ImageBuffer, Luma, Pixel};
2use imageproc::drawing::draw_filled_circle_mut;
3use serde::{ser::SerializeStruct, Deserialize, Serialize};
4use std::mem;
5
6use crate::{color_with_intensity, result::RvResult, rverr, OutOfBoundsMode, ShapeI};
7
8use super::{
9    line::render_line, BbF, BbI, BrushLine, Point, PtF, PtI, RenderTargetOrShape, TPtF, TPtI,
10};
11
12fn line_to_mask(
13    line: &BrushLine,
14    orig_shape: Option<ShapeI>,
15    buffer: Option<Vec<u8>>,
16) -> RvResult<(Vec<u8>, BbI)> {
17    let bb = line.bb(orig_shape)?;
18    let color = Luma([1]);
19    let bbi = BbI::from_arr(&[
20        bb.x as u32,
21        bb.y as u32,
22        bb.w.ceil() as u32,
23        bb.h.ceil() as u32,
24    ]);
25    let is_none = buffer.is_none();
26    let mut buffer = if let Some(mut buffer) =
27        buffer.filter(|buffer| buffer.len() >= (bbi.w * bbi.h) as usize)
28    {
29        buffer.fill(0);
30        ImageBuffer::from_vec(bbi.w, bbi.h, buffer)
31            .unwrap_or_else(|| RenderTargetOrShape::Shape(bbi.shape()).make_buffer())
32    } else {
33        tracing::debug!(
34            "(re-)creating buffer, buffer is {}",
35            if is_none { "None" } else { "Some" }
36        );
37        RenderTargetOrShape::Shape(bbi.shape()).make_buffer()
38    };
39    let im = if line.line.points.len() == 1 {
40        let center = Point {
41            x: (line.line.points[0].x - bb.x) as i32,
42            y: (line.line.points[0].y - bb.y) as i32,
43        };
44
45        let thickness_half = line.thickness * 0.5;
46        if line.thickness <= 1.1 {
47            buffer.put_pixel(center.x as u32, center.y as u32, color);
48        } else {
49            let r = if thickness_half.floor() == thickness_half {
50                (thickness_half - 1.0) as i32
51            } else {
52                thickness_half as i32
53            };
54            draw_filled_circle_mut(&mut buffer, (center.x, center.y), r, color);
55        }
56        buffer
57    } else {
58        render_line(
59            line.line
60                .points_iter()
61                .filter(|p| bb.contains(*p))
62                .map(|p| PtF {
63                    x: p.x - bb.x,
64                    y: p.y - bb.y,
65                }),
66            1.0,
67            line.thickness,
68            RenderTargetOrShape::Image(buffer),
69            color,
70        )
71    };
72    Ok((im.to_vec(), bbi))
73}
74
75#[must_use]
76pub fn mask_to_rle(mask: &[u8], mask_w: u32, mask_h: u32) -> Vec<u32> {
77    let mut rle = Vec::new();
78    let mut current_run = 0;
79    let mut current_value = 0;
80    for y in 0..mask_h {
81        for x in 0..mask_w {
82            let value = mask[(y * mask_w + x) as usize];
83            if value == current_value {
84                current_run += 1;
85            } else {
86                rle.push(current_run);
87                current_run = 1;
88                current_value = value;
89            }
90        }
91    }
92    rle.push(current_run);
93    rle
94}
95
96pub fn rle_to_mask_inplace(rle: &[u32], mask: &mut [u8], w: u32) {
97    for (i, &run) in rle.iter().enumerate() {
98        let value = i % 2;
99        let start = rle.iter().take(i).sum::<u32>();
100        for idx in start..(start + run) {
101            let x = idx % w;
102            let y = idx / w;
103            let idx = (y * w + x) as usize;
104            if idx < mask.len() {
105                mask[idx] = value as u8;
106            }
107        }
108    }
109}
110
111#[must_use]
112pub fn rle_to_mask(rle: &[u32], w: u32, h: u32) -> Vec<u8> {
113    let mut mask = vec![0; (w * h) as usize];
114    rle_to_mask_inplace(rle, &mut mask, w);
115    mask
116}
117
118fn idx_bb_to_pixim(idx_bb: u32, bb: BbI) -> PtI {
119    PtI {
120        y: idx_bb / bb.w,
121        x: idx_bb % bb.w,
122    } + bb.min()
123}
124
125fn idx_bb_to_im(idx_bb: u32, bb: BbI, w_im: TPtI) -> u32 {
126    let p_im = idx_bb_to_pixim(idx_bb, bb);
127    p_im.y * w_im + p_im.x
128}
129
130fn idx_im_to_bb(idx_im: u32, bb: BbI, w_im: TPtI) -> Option<u32> {
131    let p_im = PtI {
132        x: idx_im % w_im,
133        y: idx_im / w_im,
134    };
135    if bb.contains(p_im) {
136        let p = p_im - bb.min();
137        Some(p.y * bb.w + p.x)
138    } else {
139        None
140    }
141}
142/// The input rle is computed with respect to the bounding box coordinates
143/// the result is with respect to image coordinates
144pub fn rle_bb_to_image(rle_bb: &[u32], bb: BbI, shape_im: ShapeI) -> RvResult<Vec<u32>> {
145    if !bb.is_contained_in_image(shape_im) {
146        Err(rverr!(
147            "Bounding box {} is not contained in image with shape {:?}",
148            bb,
149            shape_im
150        ))
151    } else {
152        // degenerate cases with all zeros
153        if rle_bb.len() == 1 {
154            return Ok(vec![shape_im.w * shape_im.h]);
155        }
156        // or leading rows with complete zeros
157        let n_zero_rows = rle_bb[0] / bb.w;
158        let bb = BbI::from_arr(&[bb.x, bb.y + n_zero_rows, bb.w, bb.h - n_zero_rows]);
159        let rle_0_correction = n_zero_rows * bb.w;
160        // or zeros at the end
161        let n_zero_rows = if rle_bb.len() % 2 == 1 {
162            rle_bb.iter().last().unwrap() / bb.w
163        } else {
164            0
165        };
166        let bb = BbI::from_arr(&[bb.x, bb.y, bb.w, bb.h - n_zero_rows]);
167        let rle_1_correction = n_zero_rows * bb.w;
168
169        let mut rle_im = vec![];
170        let offset = idx_bb_to_im(0, bb, shape_im.w);
171        rle_im.push(offset + rle_bb[0] - rle_0_correction);
172        let mut prev_idx = rle_im[0] - 1;
173        for i in 1..rle_bb.len() {
174            let sum_correction = rle_0_correction
175                + if i == rle_bb.len() - 1 {
176                    rle_1_correction
177                } else {
178                    0
179                };
180            let im_idx = idx_bb_to_im(
181                rle_bb[..=i].iter().sum::<u32>() - 1 - sum_correction,
182                bb,
183                shape_im.w,
184            );
185            let p = PtI {
186                x: im_idx % shape_im.w,
187                y: im_idx / shape_im.w,
188            };
189            let p_prev = PtI {
190                x: prev_idx % shape_im.w,
191                y: prev_idx / shape_im.w,
192            };
193            let is_foreground_run = i % 2 == 1;
194            let row_span = p.y - p_prev.y;
195            if is_foreground_run {
196                if row_span == 0 {
197                    rle_im.push(p.x - p_prev.x);
198                } else {
199                    let n_elts = bb.max().x - p_prev.x;
200                    // in case of complete zero rows this can be zero
201                    if n_elts > 0 {
202                        rle_im.push(n_elts);
203                        for _ in 0..(row_span - 1) {
204                            rle_im.push(shape_im.w - bb.w);
205                            rle_im.push(bb.w);
206                        }
207                        rle_im.push(shape_im.w - bb.w);
208                    }
209                    rle_im.push(p.x + 1 - bb.x);
210                }
211                if i == rle_bb.len() - 1 {
212                    rle_im.push(
213                        bb.x + bb.w - 1 - p.x + shape_im.w * (shape_im.h - p.y - 1) + shape_im.w
214                            - (bb.w + bb.x),
215                    );
216                }
217            } else {
218                let n_elts = if row_span == 0 {
219                    p.x - p_prev.x
220                } else {
221                    bb.x_max() + 1 - p_prev.x + (row_span - 1) * shape_im.w + shape_im.w - bb.w
222                        + p.x
223                        - bb.x
224                };
225                let n_elts = if p.x == bb.x_max() && i < rle_bb.len() - 1 {
226                    n_elts + shape_im.w - bb.w
227                } else {
228                    n_elts
229                };
230                let n_elts = if i == rle_bb.len() - 1 {
231                    n_elts + shape_im.w - (bb.w + bb.x) + shape_im.w * (shape_im.h - p.y - 1)
232                } else {
233                    n_elts
234                };
235                rle_im.push(n_elts);
236            }
237            prev_idx = im_idx;
238        }
239        Ok(rle_im)
240    }
241}
242/// The input rle is computed with respect to the image coordinates
243/// the result is with respect to bounding box coordinates
244pub fn rle_image_to_bb(rle_im: &[u32], bb: BbI, shape_im: ShapeI) -> RvResult<Vec<u32>> {
245    if !bb.is_contained_in_image(shape_im) {
246        Err(rverr!(
247            "Bounding box {} is not contained in image with shape {:?}",
248            bb,
249            shape_im
250        ))
251    } else {
252        // degenerate cases with all zeros
253        if rle_im.len() == 1 {
254            return Ok(vec![bb.w * bb.h]);
255        }
256        let mut mask = vec![0; (bb.w * bb.h) as usize];
257
258        for (i, run) in rle_im.iter().enumerate() {
259            let is_foreground_run = i % 2 == 1;
260            if is_foreground_run {
261                let start = rle_im.iter().take(i).sum::<u32>();
262                for idx in start..(start + run) {
263                    if let Some(idx_bb) = idx_im_to_bb(idx, bb, shape_im.w) {
264                        mask[idx_bb as usize] = 1;
265                    }
266                }
267            }
268        }
269        Ok(mask_to_rle(&mask, bb.w, bb.h))
270    }
271}
272
273/// Get the 1d-index inside a bounding box from image coordinates
274pub fn access_bb_idx(bb: BbI, p: PtI) -> usize {
275    if bb.contains(p) {
276        ((p.y - bb.y) * bb.w + p.x - bb.x) as usize
277    } else {
278        0
279    }
280}
281
282/// Access a mask with coordinates for the image containing the mask
283#[must_use]
284pub fn access_mask_abs(mask: &[u8], bb: BbI, p: PtI) -> u8 {
285    if bb.contains(p) {
286        mask[access_bb_idx(bb, p)]
287    } else {
288        0
289    }
290}
291#[must_use]
292pub fn access_mask_rel(mask: &[u8], x: u32, y: u32, w: u32, h: u32) -> u8 {
293    if x < w && y < h {
294        mask[(y * w + x) as usize]
295    } else {
296        0
297    }
298}
299
300#[derive(Clone, Debug, Default, PartialEq)]
301pub struct Canvas {
302    pub mask: Vec<u8>,
303    pub bb: BbI,
304    pub intensity: TPtF,
305}
306
307impl Canvas {
308    pub fn from_line_extended(
309        line: &BrushLine,
310        orig_shape: ShapeI,
311        extension_factor: f64,
312        lower_buffer_bound: usize,
313    ) -> RvResult<Self> {
314        if extension_factor < 1.0 {
315            return Err(rverr!("extension factor {extension_factor} smaller 1"));
316        }
317        let bb = line.bb(Some(orig_shape))?;
318        let new_w = (bb.w * extension_factor).ceil() as usize;
319        let new_h = (bb.h * extension_factor).ceil() as usize;
320        let new_size = (new_w * new_h)
321            .min(orig_shape.w as usize * orig_shape.h as usize)
322            .max(lower_buffer_bound);
323        let buffer = vec![0; new_size];
324        Self::new(line, orig_shape, Some(buffer))
325    }
326    pub fn new(line: &BrushLine, orig_shape: ShapeI, buffer: Option<Vec<u8>>) -> RvResult<Self> {
327        let (mask, bb) = line_to_mask(line, Some(orig_shape), buffer)?;
328        Ok(Self {
329            mask,
330            bb,
331            intensity: line.intensity,
332        })
333    }
334    pub fn from_box(bb: BbI, intensity: TPtF) -> Self {
335        Self {
336            bb,
337            mask: vec![1; (bb.w * bb.h) as usize],
338            intensity,
339        }
340    }
341    #[must_use]
342    pub fn merge(mut self, other: &Canvas) -> Self {
343        let old_self_bb = self.bb;
344        self.bb = self.bb.merge(other.bb);
345        self.mask.resize((self.bb.w * self.bb.h) as usize, 0);
346        // move self-mask to new positions
347        for y in (0..old_self_bb.h).rev() {
348            for x in (0..old_self_bb.w).rev() {
349                let p = PtI { x, y } + old_self_bb.min();
350                let old_idx = (y * old_self_bb.w + x) as usize;
351                let new_idx = access_bb_idx(self.bb, p);
352                let val = self.mask[old_idx];
353                self.mask[old_idx] = 0;
354                self.mask[new_idx] = val;
355            }
356        }
357        // incorporate the other mask
358        for y in 0..other.bb.h {
359            for x in 0..other.bb.w {
360                let p = PtI { x, y } + other.bb.min();
361                let val_self = access_mask_abs(&self.mask, self.bb, p);
362                let val_other = other.mask[(y * other.bb.w + x) as usize];
363                let val = val_self.max(val_other);
364                self.mask[((p.y - self.bb.y) * self.bb.w + (p.x - self.bb.x)) as usize] = val;
365            }
366        }
367        self.intensity = self.intensity.max(other.intensity);
368        self
369    }
370    pub fn draw_circle(&mut self, center: PtF, thickness: TPtF, color: u8) -> RvResult<()> {
371        let im = ImageBuffer::<Luma<u8>, Vec<u8>>::from_vec(
372            self.bb.w,
373            self.bb.h,
374            mem::take(&mut self.mask),
375        );
376        if let Some(mut im) = im {
377            let color = Luma([color]);
378            let center = Point {
379                x: (center.x - TPtF::from(self.bb.x)) as i32,
380                y: (center.y - TPtF::from(self.bb.y)) as i32,
381            };
382
383            if thickness <= 1.1 {
384                im.put_pixel(center.x as u32, center.y as u32, color);
385            } else {
386                draw_filled_circle_mut(
387                    &mut im,
388                    (center.x, center.y),
389                    (thickness * 0.5) as i32,
390                    color,
391                );
392            }
393            self.mask = im.into_vec();
394            Ok(())
395        } else {
396            Err(rverr!(
397                "Could not create image buffer for canvas at {:?}",
398                self.bb
399            ))
400        }
401    }
402    /// This function does check the for out of bounds. We assume valid data has been serialized.
403    pub fn from_serialized_brush_line(bl: &BrushLine) -> RvResult<Self> {
404        let (mask, bb) = line_to_mask(bl, None, None)?;
405        Ok(Self {
406            mask,
407            bb,
408            intensity: bl.intensity,
409        })
410    }
411    pub fn follow_movement(&mut self, from: PtF, to: PtF, shape: ShapeI) {
412        let x_shift = (to.x - from.x) as TPtF;
413        let y_shift = (to.y - from.y) as TPtF;
414        let bb: BbF = self.bb.into();
415        let bb = bb.translate(x_shift, y_shift, shape, OutOfBoundsMode::Deny);
416        if let Some(bb) = bb {
417            self.bb = bb.into();
418        }
419    }
420}
421
422impl Serialize for Canvas {
423    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
424    where
425        S: serde::Serializer,
426    {
427        let mut state = serializer.serialize_struct("Canvas", 3)?;
428        state.serialize_field("rle", &mask_to_rle(&self.mask, self.bb.w, self.bb.h))?;
429        state.serialize_field("bb", &self.bb)?;
430        state.serialize_field("intensity", &self.intensity)?;
431        state.end()
432    }
433}
434impl<'de> Deserialize<'de> for Canvas {
435    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
436    where
437        D: serde::Deserializer<'de>,
438    {
439        #[derive(Deserialize)]
440        struct CanvasDe {
441            rle: Vec<u32>,
442            bb: BbI,
443            intensity: TPtF,
444        }
445        #[derive(Deserialize)]
446        #[serde(untagged)]
447        enum CanvasOrBl {
448            Canvas(CanvasDe),
449            BrushLine(BrushLine),
450        }
451        let read = CanvasOrBl::deserialize(deserializer)?;
452        match read {
453            CanvasOrBl::Canvas(canvas_de) => {
454                let mask = rle_to_mask(&canvas_de.rle, canvas_de.bb.w, canvas_de.bb.h);
455                Ok(Self {
456                    mask,
457                    bb: canvas_de.bb,
458                    intensity: canvas_de.intensity,
459                })
460            }
461            CanvasOrBl::BrushLine(bl) => {
462                Canvas::from_serialized_brush_line(&bl).map_err(serde::de::Error::custom)
463            }
464        }
465    }
466}
467
468pub fn canvases_to_image<'a, CLR>(
469    canvases: impl Iterator<Item = &'a Canvas>,
470    image_or_shape: RenderTargetOrShape<CLR>,
471    color: CLR,
472) -> ImageBuffer<CLR, Vec<u8>>
473where
474    CLR: Pixel<Subpixel = u8>,
475{
476    let mut im = image_or_shape.make_buffer();
477    for cv in canvases {
478        let color = color_with_intensity(color, cv.intensity);
479        for y in cv.bb.y_range() {
480            for x in cv.bb.x_range() {
481                let p_idx = PtI { x, y };
482                let is_fg = access_mask_abs(&cv.mask, cv.bb, p_idx) > 0;
483                if is_fg {
484                    im.put_pixel(x, y, color);
485                }
486            }
487        }
488    }
489    im
490}
491
492#[cfg(test)]
493use super::{Line, BB};
494#[test]
495fn test_canvas_single() {
496    let orig_shape = ShapeI::new(30, 30);
497    let bl = BrushLine {
498        line: Line {
499            points: vec![PtF { x: 5.0, y: 5.0 }],
500        },
501        intensity: 0.5,
502        thickness: 3.0,
503    };
504    let cv = Canvas::new(&bl, orig_shape, None).unwrap();
505    assert!(cv.mask.iter().sum::<u8>() > 0);
506    let buffer = vec![43; 100];
507    let orig_shape = ShapeI::new(30, 30);
508    let bl = BrushLine {
509        line: Line {
510            points: vec![PtF { x: 5.0, y: 5.0 }],
511        },
512        intensity: 0.5,
513        thickness: 3.0,
514    };
515    let cv2 = Canvas::new(&bl, orig_shape, Some(buffer)).unwrap();
516    assert_eq!(cv.mask.iter().sum::<u8>(), cv2.mask.iter().sum::<u8>());
517}
518
519#[test]
520fn test_rle() {
521    fn test(bb: BbI, shape: ShapeI, rle_bb: &[u32], rle_im_ref: &[u32], skip_rec: bool) {
522        let rle_im = rle_bb_to_image(rle_bb, bb, shape).unwrap();
523        assert_eq!(rle_im, rle_im_ref);
524        assert_eq!(rle_im.iter().sum::<u32>(), shape.w * shape.h);
525        let rle_bb_rec = rle_image_to_bb(&rle_im, bb, shape).unwrap();
526        if !skip_rec {
527            assert_eq!(rle_bb_rec, rle_bb);
528        }
529    }
530    let rle_bb = [1, 1, 4, 1, 1];
531    let bb = BbI::from_arr(&[1, 1, 2, 4]);
532    let shape = ShapeI::new(4, 6);
533    let rle_im_ref = [6, 1, 10, 1, 6];
534    test(bb, shape, &rle_bb, &rle_im_ref, false);
535
536    let rle_bb = [0, 3, 1, 2];
537    let bb = BbI::from_arr(&[3, 2, 2, 3]);
538    let shape = ShapeI::new(6, 6);
539    let rle_im_ref = [15, 2, 4, 1, 5, 2, 7];
540    test(bb, shape, &rle_bb, &rle_im_ref, false);
541
542    let rle_bb = [0, 1, 3];
543    let bb = BbI::from_arr(&[2, 2, 2, 2]);
544    let shape = ShapeI::new(6, 6);
545    let rle_im_ref = [14, 1, 21];
546    test(bb, shape, &rle_bb, &rle_im_ref, true);
547
548    let rle_bb = [1, 2, 1];
549    let bb = BbI::from_arr(&[1, 1, 2, 2]);
550    let shape = ShapeI::new(6, 4);
551    let rle_im_ref = [8, 1, 4, 1, 10];
552    test(bb, shape, &rle_bb, &rle_im_ref, false);
553
554    let rle_bb = vec![0, 2, 2, 2];
555    let bb = BbI::from_arr(&[3, 2, 2, 3]);
556    let shape = ShapeI::new(6, 6);
557    let rle_im_ref = vec![15, 2, 10, 2, 7];
558    test(bb, shape, &rle_bb, &rle_im_ref, false);
559
560    let rle_bb = vec![3, 1];
561    let bb = BbI::from_arr(&[1, 1, 2, 2]);
562    let shape = ShapeI::new(6, 6);
563    let rle_im_ref = vec![14, 1, 21];
564    test(bb, shape, &rle_bb, &rle_im_ref, true);
565
566    let rle_bb = vec![6];
567    let bb = BbI::from_arr(&[2, 1, 2, 3]);
568    let shape = ShapeI::new(6, 6);
569    let rle_im_ref = vec![36];
570    test(bb, shape, &rle_bb, &rle_im_ref, false);
571
572    let rle_bb = vec![0, 6];
573    let bb = BbI::from_arr(&[2, 1, 2, 3]);
574    let shape = ShapeI::new(6, 6);
575    let rle_im_ref = vec![8, 2, 4, 2, 4, 2, 14];
576    test(bb, shape, &rle_bb, &rle_im_ref, false);
577
578    let rle_bb = vec![0, 1, 2, 1];
579    let bb = BbI::from_arr(&[1, 1, 2, 2]);
580    let shape = ShapeI::new(6, 8);
581    let rle_im_ref = vec![7, 1, 6, 1, 33];
582    test(bb, shape, &rle_bb, &rle_im_ref, false);
583
584    let rle_bb = vec![1, 4, 1];
585    let bb = BbI::from_arr(&[1, 1, 2, 3]);
586    let shape = ShapeI::new(6, 6);
587    let rle_im_ref = vec![8, 1, 4, 2, 4, 1, 16];
588    test(bb, shape, &rle_bb, &rle_im_ref, false);
589
590    let mask = vec![0, 1, 0, 0, 0, 0, 1, 0];
591    let rle = mask_to_rle(&mask, 2, 4);
592    assert_eq!(rle, vec![1, 1, 4, 1, 1]);
593
594    let mask = vec![0, 0, 0, 0, 0, 0, 0, 0, 0];
595    let rle = mask_to_rle(&mask, 3, 3);
596    assert_eq!(rle, vec![9]);
597    let mask2 = rle_to_mask(&rle, 3, 3);
598    assert_eq!(mask, mask2);
599
600    let mask = vec![1, 1, 1, 1, 1, 1, 1, 1, 1];
601    let rle = mask_to_rle(&mask, 3, 3);
602    assert_eq!(rle, vec![0, 9]);
603    let mask2 = rle_to_mask(&rle, 3, 3);
604    assert_eq!(mask, mask2);
605
606    let mask = vec![1, 0, 0, 1, 1, 1, 0, 0, 0];
607    let rle = mask_to_rle(&mask, 3, 3);
608    assert_eq!(rle, vec![0, 1, 2, 3, 3]);
609    let mask2 = rle_to_mask(&rle, 3, 3);
610    assert_eq!(mask, mask2);
611
612    let bb = BbI::from_arr(&[5, 10, 4, 8]);
613    let shape_im = ShapeI::new(100, 200);
614    let x = idx_bb_to_im(0, bb, shape_im.w);
615    assert_eq!(x, 1005);
616    let x = idx_bb_to_im(1, bb, shape_im.w);
617    assert_eq!(x, 1006);
618    let x = idx_bb_to_im(3, bb, shape_im.w);
619    assert_eq!(x, 1008);
620}
621
622#[test]
623fn test_canvas_serde() {
624    let orig_shape = ShapeI::new(30, 30);
625    let bl = BrushLine {
626        line: Line {
627            points: vec![PtF { x: 5.0, y: 5.0 }],
628        },
629        intensity: 0.5,
630        thickness: 3.0,
631    };
632    let cv = Canvas::new(&bl, orig_shape, None).unwrap();
633    let s = serde_json::to_string(&cv).unwrap();
634    let cv_read: Canvas = serde_json::from_str(&s).unwrap();
635    assert_eq!(cv, cv_read);
636}
637
638#[test]
639fn test_line_to_mask() {
640    fn test(mask_zeros: &[u8], mask_sum: u8, bb: BbI, bl: &BrushLine) {
641        let (mask2, bb2) = line_to_mask(bl, None, None).unwrap();
642
643        assert_eq!(bb, bb2);
644        assert_eq!(mask2.iter().sum::<u8>(), mask_sum);
645        for i in mask_zeros {
646            assert_eq!(mask2[*i as usize], 0);
647        }
648    }
649
650    let bl = BrushLine {
651        line: Line {
652            points: vec![PtF { x: 4.7, y: 4.7 }],
653        },
654        intensity: 0.5,
655        thickness: 3.0,
656    };
657    test(&[0, 2, 6, 8], 5, BB::from_arr(&[3, 3, 3, 3]), &bl);
658
659    let bl = BrushLine {
660        line: Line {
661            points: vec![PtF { x: 5.3, y: 5.3 }],
662        },
663        intensity: 0.5,
664        thickness: 3.0,
665    };
666    test(&[0, 2, 6, 8], 5, BB::from_arr(&[3, 3, 3, 3]), &bl);
667    let bl = BrushLine {
668        line: Line {
669            points: vec![PtF { x: 5.0, y: 5.0 }],
670        },
671        intensity: 0.5,
672        thickness: 3.0,
673    };
674    test(&[0, 2, 6, 8], 5, BB::from_arr(&[3, 3, 3, 3]), &bl);
675    let center = PtF { x: 5.0, y: 5.0 };
676    let bl = BrushLine {
677        line: Line {
678            points: vec![center],
679        },
680        intensity: 0.5,
681        thickness: 5.0,
682    };
683    test(&[], 21, BB::from_arr(&[2, 2, 5, 5]), &bl);
684    let mut canvas = Canvas::new(&bl, ShapeI::new(30, 30), None).unwrap();
685    canvas.draw_circle(center, 5.0, 0).unwrap();
686    // maybe we didn't delete all but a significant portion due to rounding errors
687    assert!(canvas.mask.iter().sum::<u8>() < 21 / 2);
688}
689
690#[test]
691fn test_merge() {
692    let c1 = Canvas {
693        bb: BbI::from_arr(&[0, 0, 2, 2]),
694        mask: vec![1, 0, 0, 1],
695        intensity: 0.5,
696    };
697    let c2 = Canvas {
698        bb: BbI::from_arr(&[0, 0, 2, 2]),
699        mask: vec![1, 0, 0, 1],
700        intensity: 0.7,
701    };
702    let merged = c1.clone().merge(&c2);
703    assert_eq!(merged.mask, c1.mask);
704    assert_eq!(merged.bb, c1.bb);
705    assert_eq!(merged.intensity, c2.intensity);
706    let c1 = Canvas {
707        bb: BbI::from_arr(&[0, 0, 2, 2]),
708        mask: vec![0, 1, 1, 0],
709        intensity: 0.5,
710    };
711    let c2 = Canvas {
712        bb: BbI::from_arr(&[0, 0, 2, 2]),
713        mask: vec![1, 0, 0, 1],
714        intensity: 0.7,
715    };
716    let merged = c1.merge(&c2);
717    assert_eq!(merged.mask, vec![1, 1, 1, 1]);
718
719    let c1 = Canvas {
720        bb: BbI::from_arr(&[0, 0, 3, 2]),
721        mask: vec![1, 0, 0, 0, 1, 0],
722        intensity: 0.5,
723    };
724    let c2 = Canvas {
725        bb: BbI::from_arr(&[2, 2, 2, 2]),
726        mask: vec![1, 1, 1, 1],
727        intensity: 0.7,
728    };
729    let merged = c1.merge(&c2);
730    assert_eq!(c2.intensity, merged.intensity);
731    assert_eq!(merged.mask.len(), 16);
732    assert_eq!(merged.bb, BbI::from_arr(&[0, 0, 4, 4]));
733    let mask_reference = vec![1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1];
734    assert_eq!(merged.mask, mask_reference);
735
736    let c1 = Canvas {
737        bb: BbI::from_arr(&[1, 1, 3, 2]),
738        mask: vec![1, 0, 0, 0, 1, 0],
739        intensity: 0.5,
740    };
741    let c2 = Canvas {
742        bb: BbI::from_arr(&[3, 3, 2, 2]),
743        mask: vec![1, 1, 1, 1],
744        intensity: 0.7,
745    };
746    let merged = c1.merge(&c2);
747    assert_eq!(c2.intensity, merged.intensity);
748    assert_eq!(merged.mask.len(), 16);
749    assert_eq!(merged.bb, BbI::from_arr(&[1, 1, 4, 4]));
750    let mask_reference = vec![1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1];
751    assert_eq!(merged.mask, mask_reference);
752}
753
754#[test]
755fn test_from_box() {
756    let bb = BbI::from_arr(&[7, 8, 2, 2]);
757    let i = 1.0;
758    let c = Canvas::from_box(bb, i);
759    assert_eq!(c.mask.len(), (bb.w * bb.h) as usize);
760    assert_eq!(c.bb, bb);
761    assert_eq!(c.intensity, i);
762}