rustyms/glycan/render/
bitmap.rs

1use itertools::Itertools;
2use swash::{
3    FontRef,
4    scale::{Render, ScaleContext, Source},
5};
6use zeno::{Fill, Format, Mask, PathBuilder, Point, Scratch, Stroke, Vector};
7
8use crate::glycan::{RenderedGlycan, render::element::Element};
9
10use super::element::{TextAnchor, TextBaseline};
11
12impl RenderedGlycan {
13    /// Render this glycan as an RGBA bitmap.
14    ///  * `format`: the used strategy for antialiasing.
15    ///  * `font`: the font for rendering text.
16    ///  * `context`: the context for caching rendering text.
17    /// # Panics
18    /// If the glyph renderer failed. See [`swash::scale::Render::render`].
19    pub fn to_bitmap(
20        &self,
21        format: Format,
22        font: FontRef,
23        context: &mut ScaleContext,
24    ) -> (Vec<u8>, usize) {
25        let mask_factor = if format == Format::Alpha { 1 } else { 4 };
26        let image_width = self.size.0.ceil() as usize;
27        let mut image = std::iter::repeat_n(
28            [
29                self.background[0],
30                self.background[1],
31                self.background[2],
32                0,
33            ],
34            image_width * self.size.1.ceil() as usize,
35        )
36        .flatten()
37        .collect_vec();
38
39        let mut scratch = Scratch::new();
40        let mut stroke_mask = Vec::new();
41        let mut fill_mask = Vec::new();
42        for element in &self.elements {
43            // Draw into the mask
44            let (x, y, mask_width, fill, stroke) = match element {
45                Element::Line {
46                    from,
47                    to,
48                    stroke,
49                    stroke_size,
50                } => {
51                    let xmin = (from.0.min(to.0) - stroke_size).floor();
52                    let xmax = (from.0.max(to.0) + stroke_size).ceil();
53                    let ymin = (from.1.min(to.1) - stroke_size).floor();
54                    let ymax = (from.1.max(to.1) + stroke_size).ceil();
55                    let width = (xmax - xmin) as usize;
56                    let height = (ymax - ymin) as usize;
57                    let commands = vec![
58                        zeno::Command::MoveTo(Vector::new(
59                            from.0 - xmin + stroke_size / 2.0,
60                            from.1 - ymin + stroke_size / 2.0,
61                        )),
62                        zeno::Command::LineTo(Vector::new(
63                            to.0 - xmin + stroke_size / 2.0,
64                            to.1 - ymin + stroke_size / 2.0,
65                        )),
66                        zeno::Command::Close,
67                    ];
68                    stroke_mask.fill(0);
69                    stroke_mask.resize(height * width * mask_factor, 0);
70                    Mask::with_scratch(&commands, &mut scratch)
71                        .format(format)
72                        .style(Stroke::new(*stroke_size))
73                        .size(width as u32, height as u32)
74                        .render_into(&mut stroke_mask, None);
75                    (xmin as usize, ymin as usize, width, None, Some(*stroke))
76                }
77                Element::Circle {
78                    r,
79                    center,
80                    fill,
81                    stroke,
82                    stroke_size,
83                    svg_header: _,
84                } => {
85                    let width = (center.0.fract() + r * 2.0 + stroke_size).ceil() as usize;
86                    let height = (center.1.fract() + r * 2.0 + stroke_size).ceil() as usize;
87                    let mut commands = Vec::new();
88                    commands.add_circle(
89                        (
90                            center.0.fract() + r + stroke_size / 2.0,
91                            center.1.fract() + r + stroke_size / 2.0,
92                        ),
93                        *r,
94                    );
95                    if fill.is_some() {
96                        fill_mask.fill(0);
97                        fill_mask.resize(height * width * mask_factor, 0);
98                        Mask::with_scratch(&commands, &mut scratch)
99                            .format(format)
100                            .style(Fill::NonZero)
101                            .size(width as u32, height as u32)
102                            .render_into(&mut fill_mask, None);
103                    }
104                    stroke_mask.fill(0);
105                    stroke_mask.resize(height * width * mask_factor, 0);
106                    Mask::with_scratch(&commands, &mut scratch)
107                        .format(format)
108                        .style(Stroke::new(*stroke_size))
109                        .size(width as u32, height as u32)
110                        .render_into(&mut stroke_mask, None);
111                    (
112                        (center.0 - r) as usize,
113                        (center.1 - r) as usize,
114                        width,
115                        *fill,
116                        Some(*stroke),
117                    )
118                }
119                Element::Rectangle {
120                    top,
121                    w,
122                    h,
123                    fill,
124                    stroke,
125                    stroke_size,
126                    svg_header: _,
127                } => {
128                    let width = (top.0.fract() + w + stroke_size).ceil() as usize;
129                    let height = (top.1.fract() + h + stroke_size).ceil() as usize;
130                    let mut commands = Vec::new();
131                    commands.add_rect(
132                        (
133                            top.0.fract() + stroke_size / 2.0,
134                            top.1.fract() + stroke_size / 2.0,
135                        ),
136                        *w,
137                        *h,
138                    );
139                    fill_mask.fill(0);
140                    fill_mask.resize(height * width * mask_factor, 0);
141                    Mask::with_scratch(&commands, &mut scratch)
142                        .format(format)
143                        .style(Fill::NonZero)
144                        .size(width as u32, height as u32)
145                        .render_into(&mut fill_mask, None);
146                    stroke_mask.fill(0);
147                    stroke_mask.resize(height * width * mask_factor, 0);
148                    Mask::with_scratch(&commands, &mut scratch)
149                        .format(format)
150                        .style(Stroke::new(*stroke_size))
151                        .size(width as u32, height as u32)
152                        .render_into(&mut stroke_mask, None);
153                    (
154                        (top.0 - stroke_size / 2.0) as usize,
155                        (top.1 - stroke_size / 2.0) as usize,
156                        width,
157                        Some(*fill),
158                        Some(*stroke),
159                    )
160                }
161                Element::Polygon {
162                    points,
163                    fill,
164                    stroke,
165                    stroke_size,
166                    svg_header: _,
167                    bevel,
168                } => {
169                    let (xmin, xmax, ymin, ymax) = points
170                        .iter()
171                        .fold((f32::MAX, f32::MIN, f32::MAX, f32::MIN), |acc, (x, y)| {
172                            (acc.0.min(*x), acc.1.max(*x), acc.2.min(*y), acc.3.max(*y))
173                        });
174                    let xmin = (xmin - stroke_size).floor();
175                    let xmax = (xmax + stroke_size).ceil();
176                    let ymin = (ymin - stroke_size).floor();
177                    let ymax = (ymax + stroke_size).ceil();
178                    let width = (xmax - xmin) as usize;
179                    let height = (ymax - ymin) as usize;
180                    let mut commands = Vec::with_capacity(points.len() + 2);
181                    commands.push(zeno::Command::MoveTo(Point::new(
182                        points[0].0 - xmin + stroke_size / 2.0,
183                        points[0].1 - ymin + stroke_size / 2.0,
184                    )));
185                    for point in points {
186                        commands.push(zeno::Command::LineTo(Point::new(
187                            point.0 - xmin + stroke_size / 2.0,
188                            point.1 - ymin + stroke_size / 2.0,
189                        )));
190                    }
191                    commands.push(zeno::Command::Close);
192                    fill_mask.fill(0);
193                    fill_mask.resize(height * width * mask_factor, 0);
194                    Mask::with_scratch(&commands, &mut scratch)
195                        .format(format)
196                        .style(Fill::NonZero)
197                        .size(width as u32, height as u32)
198                        .render_into(&mut fill_mask, None);
199                    stroke_mask.fill(0);
200                    stroke_mask.resize(height * width * mask_factor, 0);
201                    Mask::with_scratch(&commands, &mut scratch)
202                        .format(format)
203                        .style(Stroke::new(*stroke_size).join(if *bevel {
204                            zeno::Join::Bevel
205                        } else {
206                            zeno::Join::Miter
207                        }))
208                        .size(width as u32, height as u32)
209                        .render_into(&mut stroke_mask, None);
210                    (
211                        xmin as usize,
212                        ymin as usize,
213                        width,
214                        Some(*fill),
215                        Some(*stroke),
216                    )
217                }
218                Element::Text {
219                    text,
220                    position,
221                    anchor,
222                    baseline,
223                    fill,
224                    size,
225                    italic: _, // Needs a separate font
226                } => {
227                    let mut scaler = context.builder(font).size(*size).hint(true).build();
228                    let metrics = font.metrics(&[]);
229                    let normalisation_factor = size / f32::from(metrics.units_per_em);
230                    let y_offset = (match baseline {
231                        TextBaseline::Hanging => metrics.ascent,
232                        TextBaseline::Middle => metrics.ascent - metrics.x_height / 2.0,
233                        TextBaseline::Ideographic => metrics.ascent + metrics.descent,
234                    })
235                    .mul_add(-normalisation_factor, position.1);
236                    let mut width = 0.0;
237                    for c in text.chars() {
238                        let id = font.charmap().map(c);
239                        width += font.glyph_metrics(&[]).advance_width(id);
240                    }
241
242                    let x_offset = (match anchor {
243                        TextAnchor::Start => 0.0,
244                        TextAnchor::Middle => width / 2.0,
245                        TextAnchor::End => width,
246                    })
247                    .mul_add(-normalisation_factor, position.0);
248
249                    let mut offset = 0.0;
250                    for c in text.chars() {
251                        let id = font.charmap().map(c);
252                        let glyph_metrics = font.glyph_metrics(&[]);
253                        let mask = Render::new(&[Source::Outline])
254                            .format(format)
255                            .offset(Vector::new(
256                                (x_offset + offset).fract(),
257                                y_offset.fract() - 1.0,
258                            ))
259                            .render(&mut scaler, id)
260                            .unwrap();
261                        draw_mask(
262                            (&mut image, image_width),
263                            (&mask.data, mask.placement.width as usize),
264                            (x_offset + offset + mask.placement.left as f32) as usize,
265                            (y_offset + mask.placement.top as f32) as usize,
266                            *fill,
267                            format,
268                        );
269
270                        offset += glyph_metrics.advance_width(id) * normalisation_factor;
271                    }
272                    (0, 0, 0, None, None)
273                }
274                Element::Curve {
275                    start,
276                    points,
277                    stroke,
278                    stroke_size,
279                } => {
280                    let (xmin, xmax, ymin, ymax) = points.iter().fold(
281                        (f32::MAX, f32::MIN, f32::MAX, f32::MIN),
282                        |acc, (a, b, x, y)| {
283                            (
284                                acc.0.min(*x).min(*a),
285                                acc.1.max(*x).max(*a),
286                                acc.2.min(*y).min(*b),
287                                acc.3.max(*y).max(*b),
288                            )
289                        },
290                    );
291                    let xmin = (xmin - stroke_size).floor();
292                    let xmax = (xmax + stroke_size).ceil();
293                    let ymin = (ymin - stroke_size).floor();
294                    let ymax = (ymax + stroke_size).ceil();
295                    let width = (xmax - xmin) as usize;
296                    let height = (ymax - ymin) as usize;
297                    let mut commands = Vec::with_capacity(points.len() + 1);
298                    commands.push(zeno::Command::MoveTo(Point::new(
299                        start.0 - xmin + stroke_size / 2.0,
300                        start.1 - ymin + stroke_size / 2.0,
301                    )));
302                    for point in points {
303                        commands.push(zeno::Command::QuadTo(
304                            Point::new(
305                                point.0 - xmin + stroke_size / 2.0,
306                                point.1 - ymin + stroke_size / 2.0,
307                            ),
308                            Point::new(
309                                point.2 - xmin + stroke_size / 2.0,
310                                point.3 - ymin + stroke_size / 2.0,
311                            ),
312                        ));
313                    }
314                    stroke_mask.fill(0);
315                    stroke_mask.resize(height * width * mask_factor, 0);
316                    Mask::with_scratch(&commands, &mut scratch)
317                        .format(format)
318                        .style(Stroke::new(*stroke_size))
319                        .size(width as u32, height as u32)
320                        .render_into(&mut stroke_mask, None);
321                    (xmin as usize, ymin as usize, width, None, Some(*stroke))
322                }
323            };
324            if let Some(fill) = fill {
325                draw_mask(
326                    (&mut image, image_width),
327                    (&fill_mask, mask_width),
328                    x,
329                    y,
330                    fill,
331                    format,
332                );
333            }
334            if let Some(stroke) = stroke {
335                draw_mask(
336                    (&mut image, image_width),
337                    (&stroke_mask, mask_width),
338                    x,
339                    y,
340                    stroke,
341                    format,
342                );
343            }
344        }
345        (image, image_width)
346    }
347}
348
349/// Draw the specified mask onto the specified image
350#[allow(clippy::identity_op, clippy::needless_pass_by_value)] // I like the + 0 in position calculations for symmetry reasons and image tuple looks makes no sense to pass by reference
351fn draw_mask(
352    image: (&mut [u8], usize),
353    mask: (&[u8], usize),
354    x: usize,
355    y: usize,
356    colour: [u8; 3],
357    format: Format,
358) {
359    let mask_factor = if format == Format::Alpha { 1 } else { 4 };
360    let mask_height = mask.0.len() / mask_factor / mask.1;
361    for r in 0..mask_height {
362        for w in 0..mask.1 {
363            let image_pos = ((r + y) * image.1 + (w + x)) * 4;
364            let mask_pos = (r * mask.1 + w) * mask_factor;
365
366            if image_pos >= image.0.len() || mask_pos >= mask.0.len() {
367                continue;
368            }
369
370            if format == Format::Alpha {
371                image.0[image_pos + 0] = blend(mask.0[mask_pos], colour[0], image.0[image_pos + 0]);
372                image.0[image_pos + 1] = blend(mask.0[mask_pos], colour[1], image.0[image_pos + 1]);
373                image.0[image_pos + 2] = blend(mask.0[mask_pos], colour[2], image.0[image_pos + 2]);
374            } else {
375                image.0[image_pos + 0] =
376                    blend(mask.0[mask_pos + 0], colour[0], image.0[image_pos + 0]);
377                image.0[image_pos + 1] =
378                    blend(mask.0[mask_pos + 1], colour[1], image.0[image_pos + 1]);
379                image.0[image_pos + 2] =
380                    blend(mask.0[mask_pos + 2], colour[2], image.0[image_pos + 2]);
381            }
382            image.0[image_pos + 3] = 255;
383        }
384    }
385}
386
387const fn blend(alpha: u8, foreground: u8, background: u8) -> u8 {
388    (((alpha as u16 * foreground as u16) + (255 - alpha) as u16 * background as u16) / 255) as u8
389}