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
5use crate::render::TinySkiaPixmapMutExt;
6
7pub 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
25pub 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
47fn 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")]
82mod 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