Skip to main content

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(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 std::io::Cursor;
60    use usvg::ImageRendering;
61
62    fn decode_raster(image: &usvg::ImageKind) -> Option<tiny_skia::Pixmap> {
63        match image {
64            usvg::ImageKind::SVG(_) => None,
65            usvg::ImageKind::JPEG(data) => {
66                decode_jpeg(data).log_none(|| log::warn!("Failed to decode a JPEG image."))
67            }
68            usvg::ImageKind::PNG(data) => {
69                decode_png(data).log_none(|| log::warn!("Failed to decode a PNG image."))
70            }
71            usvg::ImageKind::GIF(data) => {
72                decode_gif(data).log_none(|| log::warn!("Failed to decode a GIF image."))
73            }
74            usvg::ImageKind::WEBP(data) => {
75                decode_webp(data).log_none(|| log::warn!("Failed to decode a WebP image."))
76            }
77        }
78    }
79
80    fn decode_png(data: &[u8]) -> Option<tiny_skia::Pixmap> {
81        tiny_skia::Pixmap::decode_png(data).ok()
82    }
83
84    fn decode_jpeg(data: &[u8]) -> Option<tiny_skia::Pixmap> {
85        use zune_jpeg::zune_core::colorspace::ColorSpace;
86        use zune_jpeg::zune_core::options::DecoderOptions;
87
88        let cursor = Cursor::new(data);
89        let options = DecoderOptions::default().jpeg_set_out_colorspace(ColorSpace::RGBA);
90        let mut decoder = zune_jpeg::JpegDecoder::new_with_options(cursor, options);
91        decoder.decode_headers().ok()?;
92        let output_cs = decoder.output_colorspace()?;
93
94        let img_data = {
95            let data = decoder.decode().ok()?;
96            match output_cs {
97                ColorSpace::RGBA => data,
98                _ => return None,
99            }
100        };
101
102        let info = decoder.info()?;
103
104        let size = tiny_skia::IntSize::from_wh(info.width as u32, info.height as u32)?;
105        tiny_skia::Pixmap::from_vec(img_data, size)
106    }
107
108    fn decode_gif(data: &[u8]) -> Option<tiny_skia::Pixmap> {
109        let mut decoder = gif::DecodeOptions::new();
110        decoder.set_color_output(gif::ColorOutput::RGBA);
111        let mut decoder = decoder.read_info(data).ok()?;
112        let first_frame = decoder.read_next_frame().ok()??;
113
114        let size = tiny_skia::IntSize::from_wh(
115            u32::from(first_frame.width),
116            u32::from(first_frame.height),
117        )?;
118
119        let (w, h) = size.dimensions();
120        let mut pixmap = tiny_skia::Pixmap::new(w, h)?;
121        rgba_to_pixmap(&first_frame.buffer, &mut pixmap);
122        Some(pixmap)
123    }
124
125    fn decode_webp(data: &[u8]) -> Option<tiny_skia::Pixmap> {
126        let mut decoder = image_webp::WebPDecoder::new(std::io::Cursor::new(data)).ok()?;
127        let mut first_frame = vec![0; decoder.output_buffer_size()?];
128        decoder.read_image(&mut first_frame).ok()?;
129
130        let (w, h) = decoder.dimensions();
131        let mut pixmap = tiny_skia::Pixmap::new(w, h)?;
132
133        if decoder.has_alpha() {
134            rgba_to_pixmap(&first_frame, &mut pixmap);
135        } else {
136            rgb_to_pixmap(&first_frame, &mut pixmap);
137        }
138
139        Some(pixmap)
140    }
141
142    fn rgb_to_pixmap(data: &[u8], pixmap: &mut tiny_skia::Pixmap) {
143        use rgb::FromSlice;
144
145        let mut i = 0;
146        let dst = pixmap.data_mut();
147        for p in data.as_rgb() {
148            dst[i + 0] = p.r;
149            dst[i + 1] = p.g;
150            dst[i + 2] = p.b;
151            dst[i + 3] = 255;
152
153            i += tiny_skia::BYTES_PER_PIXEL;
154        }
155    }
156
157    fn rgba_to_pixmap(data: &[u8], pixmap: &mut tiny_skia::Pixmap) {
158        use rgb::FromSlice;
159
160        let mut i = 0;
161        let dst = pixmap.data_mut();
162        for p in data.as_rgba() {
163            let a = p.a as f64 / 255.0;
164            dst[i + 0] = (p.r as f64 * a + 0.5) as u8;
165            dst[i + 1] = (p.g as f64 * a + 0.5) as u8;
166            dst[i + 2] = (p.b as f64 * a + 0.5) as u8;
167            dst[i + 3] = p.a;
168
169            i += tiny_skia::BYTES_PER_PIXEL;
170        }
171    }
172
173    pub(crate) fn render_raster(
174        image: &usvg::ImageKind,
175        transform: tiny_skia::Transform,
176        rendering_mode: usvg::ImageRendering,
177        pixmap: &mut tiny_skia::PixmapMut,
178    ) -> Option<()> {
179        let raster = decode_raster(image)?;
180
181        let rect = tiny_skia::Size::from_wh(raster.width() as f32, raster.height() as f32)?
182            .to_rect(0.0, 0.0)?;
183
184        let quality = match rendering_mode {
185            ImageRendering::OptimizeQuality => tiny_skia::FilterQuality::Bicubic,
186            ImageRendering::OptimizeSpeed => tiny_skia::FilterQuality::Nearest,
187            ImageRendering::Smooth => tiny_skia::FilterQuality::Bilinear,
188            ImageRendering::HighQuality => tiny_skia::FilterQuality::Bicubic,
189            ImageRendering::CrispEdges => tiny_skia::FilterQuality::Nearest,
190            ImageRendering::Pixelated => tiny_skia::FilterQuality::Nearest,
191        };
192
193        let pattern = tiny_skia::Pattern::new(
194            raster.as_ref(),
195            tiny_skia::SpreadMode::Pad,
196            quality,
197            1.0,
198            tiny_skia::Transform::default(),
199        );
200        let mut paint = tiny_skia::Paint::default();
201        paint.shader = pattern;
202
203        pixmap.fill_rect(rect, &paint, transform, None);
204
205        Some(())
206    }
207}