1 | // This Source Code Form is subject to the terms of the Mozilla Public |
2 | // License, v. 2.0. If a copy of the MPL was not distributed with this |
3 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. |
4 | |
5 | use crate::render::TinySkiaPixmapMutExt; |
6 | |
7 | pub fn render( |
8 | image: &usvg::Image, |
9 | transform: tiny_skia::Transform, |
10 | pixmap: &mut tiny_skia::PixmapMut, |
11 | ) { |
12 | if image.visibility() != usvg::Visibility::Visible { |
13 | return; |
14 | } |
15 | |
16 | render_inner( |
17 | image_kind:image.kind(), |
18 | image.view_box(), |
19 | transform, |
20 | image.rendering_mode(), |
21 | pixmap, |
22 | ); |
23 | } |
24 | |
25 | pub fn render_inner( |
26 | image_kind: &usvg::ImageKind, |
27 | view_box: usvg::ViewBox, |
28 | transform: tiny_skia::Transform, |
29 | #[allow (unused_variables)] rendering_mode: usvg::ImageRendering, |
30 | pixmap: &mut tiny_skia::PixmapMut, |
31 | ) { |
32 | match image_kind { |
33 | usvg::ImageKind::SVG(ref tree: &Tree) => { |
34 | render_vector(tree, &view_box, transform, pixmap); |
35 | } |
36 | #[cfg (feature = "raster-images" )] |
37 | _ => { |
38 | raster_images::render_raster(image_kind, view_box, transform, rendering_mode, pixmap); |
39 | } |
40 | #[cfg (not(feature = "raster-images" ))] |
41 | _ => { |
42 | log::warn!("Images decoding was disabled by a build feature." ); |
43 | } |
44 | } |
45 | } |
46 | |
47 | fn render_vector( |
48 | tree: &usvg::Tree, |
49 | view_box: &usvg::ViewBox, |
50 | transform: tiny_skia::Transform, |
51 | pixmap: &mut tiny_skia::PixmapMut, |
52 | ) -> Option<()> { |
53 | let img_size = tree.size().to_int_size(); |
54 | let (ts, clip) = crate::geom::view_box_to_transform_with_clip(&view_box, img_size); |
55 | |
56 | let mut sub_pixmap = tiny_skia::Pixmap::new(pixmap.width(), pixmap.height()).unwrap(); |
57 | |
58 | let source_transform = transform; |
59 | let transform = transform.pre_concat(ts); |
60 | |
61 | crate::render(tree, transform, &mut sub_pixmap.as_mut()); |
62 | |
63 | let mask = if let Some(clip) = clip { |
64 | pixmap.create_rect_mask(source_transform, clip.to_rect()) |
65 | } else { |
66 | None |
67 | }; |
68 | |
69 | pixmap.draw_pixmap( |
70 | 0, |
71 | 0, |
72 | sub_pixmap.as_ref(), |
73 | &tiny_skia::PixmapPaint::default(), |
74 | tiny_skia::Transform::identity(), |
75 | mask.as_ref(), |
76 | ); |
77 | |
78 | Some(()) |
79 | } |
80 | |
81 | #[cfg (feature = "raster-images" )] |
82 | mod raster_images { |
83 | use crate::render::TinySkiaPixmapMutExt; |
84 | use crate::OptionLog; |
85 | |
86 | fn decode_raster(image: &usvg::ImageKind) -> Option<tiny_skia::Pixmap> { |
87 | match image { |
88 | usvg::ImageKind::SVG(_) => None, |
89 | usvg::ImageKind::JPEG(ref data) => { |
90 | decode_jpeg(data).log_none(|| log::warn!("Failed to decode a JPEG image." )) |
91 | } |
92 | usvg::ImageKind::PNG(ref data) => { |
93 | decode_png(data).log_none(|| log::warn!("Failed to decode a PNG image." )) |
94 | } |
95 | usvg::ImageKind::GIF(ref data) => { |
96 | decode_gif(data).log_none(|| log::warn!("Failed to decode a GIF image." )) |
97 | } |
98 | } |
99 | } |
100 | |
101 | fn decode_png(data: &[u8]) -> Option<tiny_skia::Pixmap> { |
102 | tiny_skia::Pixmap::decode_png(data).ok() |
103 | } |
104 | |
105 | fn decode_jpeg(data: &[u8]) -> Option<tiny_skia::Pixmap> { |
106 | let mut decoder = jpeg_decoder::Decoder::new(data); |
107 | let img_data = decoder.decode().ok()?; |
108 | let info = decoder.info()?; |
109 | |
110 | let size = tiny_skia::IntSize::from_wh(info.width as u32, info.height as u32)?; |
111 | |
112 | let data = match info.pixel_format { |
113 | jpeg_decoder::PixelFormat::RGB24 => img_data, |
114 | jpeg_decoder::PixelFormat::L8 => { |
115 | let mut rgb_data: Vec<u8> = Vec::with_capacity(img_data.len() * 3); |
116 | for gray in img_data { |
117 | rgb_data.push(gray); |
118 | rgb_data.push(gray); |
119 | rgb_data.push(gray); |
120 | } |
121 | |
122 | rgb_data |
123 | } |
124 | _ => return None, |
125 | }; |
126 | |
127 | let (w, h) = size.dimensions(); |
128 | let mut pixmap = tiny_skia::Pixmap::new(w, h)?; |
129 | rgb_to_pixmap(&data, &mut pixmap); |
130 | Some(pixmap) |
131 | } |
132 | |
133 | fn decode_gif(data: &[u8]) -> Option<tiny_skia::Pixmap> { |
134 | let mut decoder = gif::DecodeOptions::new(); |
135 | decoder.set_color_output(gif::ColorOutput::RGBA); |
136 | let mut decoder = decoder.read_info(data).ok()?; |
137 | let first_frame = decoder.read_next_frame().ok()??; |
138 | |
139 | let size = tiny_skia::IntSize::from_wh( |
140 | u32::from(first_frame.width), |
141 | u32::from(first_frame.height), |
142 | )?; |
143 | |
144 | let (w, h) = size.dimensions(); |
145 | let mut pixmap = tiny_skia::Pixmap::new(w, h)?; |
146 | rgba_to_pixmap(&first_frame.buffer, &mut pixmap); |
147 | Some(pixmap) |
148 | } |
149 | |
150 | fn rgb_to_pixmap(data: &[u8], pixmap: &mut tiny_skia::Pixmap) { |
151 | use rgb::FromSlice; |
152 | |
153 | let mut i = 0; |
154 | let dst = pixmap.data_mut(); |
155 | for p in data.as_rgb() { |
156 | dst[i + 0] = p.r; |
157 | dst[i + 1] = p.g; |
158 | dst[i + 2] = p.b; |
159 | dst[i + 3] = 255; |
160 | |
161 | i += tiny_skia::BYTES_PER_PIXEL; |
162 | } |
163 | } |
164 | |
165 | fn rgba_to_pixmap(data: &[u8], pixmap: &mut tiny_skia::Pixmap) { |
166 | use rgb::FromSlice; |
167 | |
168 | let mut i = 0; |
169 | let dst = pixmap.data_mut(); |
170 | for p in data.as_rgba() { |
171 | let a = p.a as f64 / 255.0; |
172 | dst[i + 0] = (p.r as f64 * a + 0.5) as u8; |
173 | dst[i + 1] = (p.g as f64 * a + 0.5) as u8; |
174 | dst[i + 2] = (p.b as f64 * a + 0.5) as u8; |
175 | dst[i + 3] = p.a; |
176 | |
177 | i += tiny_skia::BYTES_PER_PIXEL; |
178 | } |
179 | } |
180 | |
181 | pub(crate) fn render_raster( |
182 | image: &usvg::ImageKind, |
183 | view_box: usvg::ViewBox, |
184 | transform: tiny_skia::Transform, |
185 | rendering_mode: usvg::ImageRendering, |
186 | pixmap: &mut tiny_skia::PixmapMut, |
187 | ) -> Option<()> { |
188 | let raster = decode_raster(image)?; |
189 | |
190 | let img_size = tiny_skia::IntSize::from_wh(raster.width(), raster.height())?; |
191 | let rect = image_rect(&view_box, img_size); |
192 | |
193 | let ts = tiny_skia::Transform::from_row( |
194 | rect.width() / raster.width() as f32, |
195 | 0.0, |
196 | 0.0, |
197 | rect.height() / raster.height() as f32, |
198 | rect.x(), |
199 | rect.y(), |
200 | ); |
201 | |
202 | let mut quality = tiny_skia::FilterQuality::Bicubic; |
203 | if rendering_mode == usvg::ImageRendering::OptimizeSpeed { |
204 | quality = tiny_skia::FilterQuality::Nearest; |
205 | } |
206 | |
207 | let pattern = tiny_skia::Pattern::new( |
208 | raster.as_ref(), |
209 | tiny_skia::SpreadMode::Pad, |
210 | quality, |
211 | 1.0, |
212 | ts, |
213 | ); |
214 | let mut paint = tiny_skia::Paint::default(); |
215 | paint.shader = pattern; |
216 | |
217 | let mask = if view_box.aspect.slice { |
218 | pixmap.create_rect_mask(transform, view_box.rect.to_rect()) |
219 | } else { |
220 | None |
221 | }; |
222 | |
223 | pixmap.fill_rect(rect.to_rect(), &paint, transform, mask.as_ref()); |
224 | |
225 | Some(()) |
226 | } |
227 | |
228 | /// Calculates an image rect depending on the provided view box. |
229 | fn image_rect( |
230 | view_box: &usvg::ViewBox, |
231 | img_size: tiny_skia::IntSize, |
232 | ) -> tiny_skia::NonZeroRect { |
233 | let new_size = crate::geom::fit_view_box(img_size.to_size(), view_box); |
234 | let (x, y) = usvg::utils::aligned_pos( |
235 | view_box.aspect.align, |
236 | view_box.rect.x(), |
237 | view_box.rect.y(), |
238 | view_box.rect.width() - new_size.width(), |
239 | view_box.rect.height() - new_size.height(), |
240 | ); |
241 | |
242 | new_size.to_non_zero_rect(x, y) |
243 | } |
244 | } |
245 | |