1 | // Copyright 2018 the Resvg Authors |
2 | // SPDX-License-Identifier: Apache-2.0 OR MIT |
3 | |
4 | pub 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 | |
16 | pub 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: &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 | |
37 | fn render_vector( |
38 | tree: &usvg::Tree, |
39 | transform: tiny_skia::Transform, |
40 | pixmap: &mut tiny_skia::PixmapMut, |
41 | ) -> Option<()> { |
42 | let mut sub_pixmap: 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 | x:0, |
46 | y:0, |
47 | sub_pixmap.as_ref(), |
48 | &tiny_skia::PixmapPaint::default(), |
49 | tiny_skia::Transform::default(), |
50 | mask:None, |
51 | ); |
52 | |
53 | Some(()) |
54 | } |
55 | |
56 | #[cfg (feature = "raster-images" )] |
57 | mod 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 | } |
214 | |