resvg/
image.rs

1// Copyright 2018 the Resvg Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4pub fn render(
5    image: &usvg::Image,
6    transform: tiny_skia::Transform,
7    pixmap: &mut tiny_skia::PixmapMut,
8) {
9    if !image.is_visible() {
10        return;
11    }
12
13    render_inner(image.kind(), transform, image.rendering_mode(), pixmap);
14}
15
16pub fn render_inner(
17    image_kind: &usvg::ImageKind,
18    transform: tiny_skia::Transform,
19    #[allow(unused_variables)] rendering_mode: usvg::ImageRendering,
20    pixmap: &mut tiny_skia::PixmapMut,
21) {
22    match image_kind {
23        usvg::ImageKind::SVG(ref tree) => {
24            render_vector(tree, transform, pixmap);
25        }
26        #[cfg(feature = "raster-images")]
27        _ => {
28            raster_images::render_raster(image_kind, transform, rendering_mode, pixmap);
29        }
30        #[cfg(not(feature = "raster-images"))]
31        _ => {
32            log::warn!("Images decoding was disabled by a build feature.");
33        }
34    }
35}
36
37fn render_vector(
38    tree: &usvg::Tree,
39    transform: tiny_skia::Transform,
40    pixmap: &mut tiny_skia::PixmapMut,
41) -> Option<()> {
42    let mut sub_pixmap = tiny_skia::Pixmap::new(pixmap.width(), pixmap.height()).unwrap();
43    crate::render(tree, transform, &mut sub_pixmap.as_mut());
44    pixmap.draw_pixmap(
45        0,
46        0,
47        sub_pixmap.as_ref(),
48        &tiny_skia::PixmapPaint::default(),
49        tiny_skia::Transform::default(),
50        None,
51    );
52
53    Some(())
54}
55
56#[cfg(feature = "raster-images")]
57mod raster_images {
58    use crate::OptionLog;
59    use usvg::ImageRendering;
60
61    fn decode_raster(image: &usvg::ImageKind) -> Option<tiny_skia::Pixmap> {
62        match image {
63            usvg::ImageKind::SVG(_) => None,
64            usvg::ImageKind::JPEG(ref data) => {
65                decode_jpeg(data).log_none(|| log::warn!("Failed to decode a JPEG image."))
66            }
67            usvg::ImageKind::PNG(ref data) => {
68                decode_png(data).log_none(|| log::warn!("Failed to decode a PNG image."))
69            }
70            usvg::ImageKind::GIF(ref data) => {
71                decode_gif(data).log_none(|| log::warn!("Failed to decode a GIF image."))
72            }
73            usvg::ImageKind::WEBP(ref data) => {
74                decode_webp(data).log_none(|| log::warn!("Failed to decode a WebP image."))
75            }
76        }
77    }
78
79    fn decode_png(data: &[u8]) -> Option<tiny_skia::Pixmap> {
80        tiny_skia::Pixmap::decode_png(data).ok()
81    }
82
83    fn decode_jpeg(data: &[u8]) -> Option<tiny_skia::Pixmap> {
84        use zune_jpeg::zune_core::colorspace::ColorSpace;
85        use zune_jpeg::zune_core::options::DecoderOptions;
86
87        let options = DecoderOptions::default().jpeg_set_out_colorspace(ColorSpace::RGBA);
88        let mut decoder = zune_jpeg::JpegDecoder::new_with_options(data, options);
89        decoder.decode_headers().ok()?;
90        let output_cs = decoder.get_output_colorspace()?;
91
92        let img_data = {
93            let data = decoder.decode().ok()?;
94            match output_cs {
95                ColorSpace::RGBA => data,
96                // `set_output_color_space` is not guaranteed to actually always set the output space
97                // to RGBA (its docs say "we do not guarantee the decoder can convert to all colorspaces").
98                // In particular, it seems like it doesn't work for luma JPEGs,
99                // so we convert them manually.
100                ColorSpace::Luma => data
101                    .into_iter()
102                    .flat_map(|p| [p, p, p, 255])
103                    .collect::<Vec<_>>(),
104                _ => return None,
105            }
106        };
107
108        let info = decoder.info()?;
109
110        let size = tiny_skia::IntSize::from_wh(info.width as u32, info.height as u32)?;
111        tiny_skia::Pixmap::from_vec(img_data, size)
112    }
113
114    fn decode_gif(data: &[u8]) -> Option<tiny_skia::Pixmap> {
115        let mut decoder = gif::DecodeOptions::new();
116        decoder.set_color_output(gif::ColorOutput::RGBA);
117        let mut decoder = decoder.read_info(data).ok()?;
118        let first_frame = decoder.read_next_frame().ok()??;
119
120        let size = tiny_skia::IntSize::from_wh(
121            u32::from(first_frame.width),
122            u32::from(first_frame.height),
123        )?;
124
125        let (w, h) = size.dimensions();
126        let mut pixmap = tiny_skia::Pixmap::new(w, h)?;
127        rgba_to_pixmap(&first_frame.buffer, &mut pixmap);
128        Some(pixmap)
129    }
130
131    fn decode_webp(data: &[u8]) -> Option<tiny_skia::Pixmap> {
132        let mut decoder = image_webp::WebPDecoder::new(std::io::Cursor::new(data)).ok()?;
133        let mut first_frame = vec![0; decoder.output_buffer_size()?];
134        decoder.read_image(&mut first_frame).ok()?;
135
136        let (w, h) = decoder.dimensions();
137        let mut pixmap = tiny_skia::Pixmap::new(w, h)?;
138
139        if decoder.has_alpha() {
140            rgba_to_pixmap(&first_frame, &mut pixmap);
141        } else {
142            rgb_to_pixmap(&first_frame, &mut pixmap);
143        }
144
145        Some(pixmap)
146    }
147
148    fn rgb_to_pixmap(data: &[u8], pixmap: &mut tiny_skia::Pixmap) {
149        use rgb::FromSlice;
150
151        let mut i = 0;
152        let dst = pixmap.data_mut();
153        for p in data.as_rgb() {
154            dst[i + 0] = p.r;
155            dst[i + 1] = p.g;
156            dst[i + 2] = p.b;
157            dst[i + 3] = 255;
158
159            i += tiny_skia::BYTES_PER_PIXEL;
160        }
161    }
162
163    fn rgba_to_pixmap(data: &[u8], pixmap: &mut tiny_skia::Pixmap) {
164        use rgb::FromSlice;
165
166        let mut i = 0;
167        let dst = pixmap.data_mut();
168        for p in data.as_rgba() {
169            let a = p.a as f64 / 255.0;
170            dst[i + 0] = (p.r as f64 * a + 0.5) as u8;
171            dst[i + 1] = (p.g as f64 * a + 0.5) as u8;
172            dst[i + 2] = (p.b as f64 * a + 0.5) as u8;
173            dst[i + 3] = p.a;
174
175            i += tiny_skia::BYTES_PER_PIXEL;
176        }
177    }
178
179    pub(crate) fn render_raster(
180        image: &usvg::ImageKind,
181        transform: tiny_skia::Transform,
182        rendering_mode: usvg::ImageRendering,
183        pixmap: &mut tiny_skia::PixmapMut,
184    ) -> Option<()> {
185        let raster = decode_raster(image)?;
186
187        let rect = tiny_skia::Size::from_wh(raster.width() as f32, raster.height() as f32)?
188            .to_rect(0.0, 0.0)?;
189
190        let quality = match rendering_mode {
191            ImageRendering::OptimizeQuality => tiny_skia::FilterQuality::Bicubic,
192            ImageRendering::OptimizeSpeed => tiny_skia::FilterQuality::Nearest,
193            ImageRendering::Smooth => tiny_skia::FilterQuality::Bilinear,
194            ImageRendering::HighQuality => tiny_skia::FilterQuality::Bicubic,
195            ImageRendering::CrispEdges => tiny_skia::FilterQuality::Nearest,
196            ImageRendering::Pixelated => tiny_skia::FilterQuality::Nearest,
197        };
198
199        let pattern = tiny_skia::Pattern::new(
200            raster.as_ref(),
201            tiny_skia::SpreadMode::Pad,
202            quality,
203            1.0,
204            tiny_skia::Transform::default(),
205        );
206        let mut paint = tiny_skia::Paint::default();
207        paint.shader = pattern;
208
209        pixmap.fill_rect(rect, &paint, transform, None);
210
211        Some(())
212    }
213}