1pub 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}