summaryrefslogtreecommitdiff
path: root/rust/crates/mask-defs/src/paint.rs
blob: 0b122132207256fc7d614634dccf1d581c14b2d1 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
use scene::StrokeAlign;
use tiny_skia::{Color, Paint, Path, Pixmap, PremultipliedColorU8, Stroke, Transform};

use crate::MaskRenderError;

pub fn white_paint() -> Paint<'static> {
    Paint {
        shader: tiny_skia::Shader::SolidColor(Color::from_rgba8(255, 255, 255, 255)),
        anti_alias: true,
        ..Paint::default()
    }
}

pub fn fill_white(path: &Option<Path>, pixmap: &mut Pixmap) {
    if let Some(path) = path {
        pixmap.fill_path(
            path,
            &white_paint(),
            tiny_skia::FillRule::Winding,
            Transform::default(),
            None,
        );
    }
}

pub fn parse_stroke_color(value: &str) -> Result<Color, MaskRenderError> {
    let hex = value
        .trim()
        .strip_prefix('#')
        .ok_or_else(|| MaskRenderError::InvalidStrokeColor(value.to_string()))?;
    let expand = |c: u8| (c << 4) | c;
    let (r, g, b, a) = match hex.len() {
        3 => {
            let v = u32::from_str_radix(hex, 16)
                .map_err(|_| MaskRenderError::InvalidStrokeColor(value.to_string()))?;
            (
                expand(((v >> 8) & 0xF) as u8),
                expand(((v >> 4) & 0xF) as u8),
                expand((v & 0xF) as u8),
                255,
            )
        }
        4 => {
            let v = u32::from_str_radix(hex, 16)
                .map_err(|_| MaskRenderError::InvalidStrokeColor(value.to_string()))?;
            (
                expand(((v >> 12) & 0xF) as u8),
                expand(((v >> 8) & 0xF) as u8),
                expand(((v >> 4) & 0xF) as u8),
                expand((v & 0xF) as u8),
            )
        }
        6 => {
            let v = u32::from_str_radix(hex, 16)
                .map_err(|_| MaskRenderError::InvalidStrokeColor(value.to_string()))?;
            (((v >> 16) & 0xFF) as u8, ((v >> 8) & 0xFF) as u8, (v & 0xFF) as u8, 255)
        }
        8 => {
            let v = u32::from_str_radix(hex, 16)
                .map_err(|_| MaskRenderError::InvalidStrokeColor(value.to_string()))?;
            (
                ((v >> 24) & 0xFF) as u8,
                ((v >> 16) & 0xFF) as u8,
                ((v >> 8) & 0xFF) as u8,
                (v & 0xFF) as u8,
            )
        }
        _ => return Err(MaskRenderError::InvalidStrokeColor(value.to_string())),
    };
    Ok(Color::from_rgba8(r, g, b, a))
}

pub fn canvas_stroke(stroke_width: f64) -> Stroke {
    Stroke {
        width: stroke_width as f32,
        miter_limit: 10.0,
        line_cap: tiny_skia::LineCap::Butt,
        line_join: tiny_skia::LineJoin::Miter,
        ..Stroke::default()
    }
}

pub fn round_stroke(stroke_width: f64) -> Stroke {
    Stroke {
        width: stroke_width as f32,
        line_cap: tiny_skia::LineCap::Round,
        line_join: tiny_skia::LineJoin::Round,
        ..Stroke::default()
    }
}

pub fn stroke_path_with(
    path: &Option<Path>,
    stroke: &Stroke,
    color: Color,
    pixmap: &mut Pixmap,
) {
    if let Some(path) = path {
        let paint = Paint {
            shader: tiny_skia::Shader::SolidColor(color),
            anti_alias: true,
            ..Paint::default()
        };
        pixmap.stroke_path(path, &paint, stroke, Transform::default(), None);
    }
}

pub fn apply_stroke_align(stroke: &mut Pixmap, fill: &Pixmap, align: StrokeAlign) {
    let keep_inside = match align {
        StrokeAlign::Center => return,
        StrokeAlign::Inside => true,
        StrokeAlign::Outside => false,
    };
    for (dst, src) in stroke.pixels_mut().iter_mut().zip(fill.pixels().iter()) {
        let coverage = if keep_inside {
            src.alpha() as u16
        } else {
            255 - src.alpha() as u16
        };
        let scale = |channel: u8| ((channel as u16 * coverage + 127) / 255) as u8;
        let scaled = PremultipliedColorU8::from_rgba(
            scale(dst.red()),
            scale(dst.green()),
            scale(dst.blue()),
            scale(dst.alpha()),
        );
        if let Some(scaled) = scaled {
            *dst = scaled;
        } else {
            *dst = PremultipliedColorU8::from_rgba(0, 0, 0, 0).unwrap();
        }
    }
}