1use crate::diagnostics::BuildDiagnostics;
5use crate::embedded_resources::*;
6use crate::expression_tree::{Expression, ImageReference};
7use crate::object_tree::*;
8use crate::EmbedResourcesKind;
9#[cfg(feature = "software-renderer")]
10use image::GenericImageView;
11use smol_str::SmolStr;
12use std::cell::RefCell;
13use std::collections::{BTreeMap, HashMap};
14use std::future::Future;
15use std::pin::Pin;
16use std::rc::Rc;
17
18pub async fn embed_images(
19 doc: &Document,
20 embed_files: EmbedResourcesKind,
21 scale_factor: f64,
22 resource_url_mapper: &Option<Rc<dyn Fn(&str) -> Pin<Box<dyn Future<Output = Option<String>>>>>>,
23 diag: &mut BuildDiagnostics,
24) {
25 if embed_files == EmbedResourcesKind::Nothing && resource_url_mapper.is_none() {
26 return;
27 }
28
29 let global_embedded_resources = &doc.embedded_file_resources;
30
31 let mut all_components = Vec::new();
32 doc.visit_all_used_components(|c| all_components.push(c.clone()));
33 let all_components = all_components;
34
35 let mapped_urls = {
36 let mut urls = HashMap::<SmolStr, Option<SmolStr>>::new();
37
38 if let Some(mapper) = resource_url_mapper {
39 for component in &all_components {
41 visit_all_expressions(component, |e, _| {
42 collect_image_urls_from_expression(e, &mut urls)
43 });
44 }
45
46 for i in urls.iter_mut() {
48 *i.1 = (*mapper)(i.0).await.map(SmolStr::new);
49 }
50 }
51
52 urls
53 };
54
55 for component in &all_components {
57 visit_all_expressions(component, |e, _| {
58 embed_images_from_expression(
59 e,
60 &mapped_urls,
61 global_embedded_resources,
62 embed_files,
63 scale_factor,
64 diag,
65 )
66 });
67 }
68}
69
70fn collect_image_urls_from_expression(
71 e: &Expression,
72 urls: &mut HashMap<SmolStr, Option<SmolStr>>,
73) {
74 if let Expression::ImageReference { ref resource_ref, .. } = e {
75 if let ImageReference::AbsolutePath(path) = resource_ref {
76 urls.insert(path.clone(), None);
77 }
78 };
79
80 e.visit(|e| collect_image_urls_from_expression(e, urls));
81}
82
83fn embed_images_from_expression(
84 e: &mut Expression,
85 urls: &HashMap<SmolStr, Option<SmolStr>>,
86 global_embedded_resources: &RefCell<BTreeMap<SmolStr, EmbeddedResources>>,
87 embed_files: EmbedResourcesKind,
88 scale_factor: f64,
89 diag: &mut BuildDiagnostics,
90) {
91 if let Expression::ImageReference { ref mut resource_ref, source_location, nine_slice: _ } = e {
92 if let ImageReference::AbsolutePath(path) = resource_ref {
93 let mapped_path =
95 urls.get(path).unwrap_or(&Some(path.clone())).clone().unwrap_or(path.clone());
96 *path = mapped_path;
97 if embed_files != EmbedResourcesKind::Nothing
98 && (embed_files != EmbedResourcesKind::OnlyBuiltinResources
99 || path.starts_with("builtin:/"))
100 {
101 let image_ref = embed_image(
102 global_embedded_resources,
103 embed_files,
104 path,
105 scale_factor,
106 diag,
107 source_location,
108 );
109 if embed_files != EmbedResourcesKind::ListAllResources {
110 *resource_ref = image_ref;
111 }
112 }
113 }
114 };
115
116 e.visit_mut(|e| {
117 embed_images_from_expression(
118 e,
119 urls,
120 global_embedded_resources,
121 embed_files,
122 scale_factor,
123 diag,
124 )
125 });
126}
127
128fn embed_image(
129 global_embedded_resources: &RefCell<BTreeMap<SmolStr, EmbeddedResources>>,
130 embed_files: EmbedResourcesKind,
131 path: &str,
132 _scale_factor: f64,
133 diag: &mut BuildDiagnostics,
134 source_location: &Option<crate::diagnostics::SourceLocation>,
135) -> ImageReference {
136 let mut resources = global_embedded_resources.borrow_mut();
137 let maybe_id = resources.len();
138 let e = match resources.entry(path.into()) {
139 std::collections::btree_map::Entry::Occupied(e) => e.into_mut(),
140 std::collections::btree_map::Entry::Vacant(e) => {
141 if embed_files == EmbedResourcesKind::ListAllResources {
143 e.insert(EmbeddedResources { id: maybe_id, kind: EmbeddedResourcesKind::ListOnly });
145 return ImageReference::None;
146 } else if let Some(_file) = crate::fileaccess::load_file(std::path::Path::new(path)) {
147 #[allow(unused_mut)]
148 let mut kind = EmbeddedResourcesKind::RawData;
149 #[cfg(feature = "software-renderer")]
150 if embed_files == EmbedResourcesKind::EmbedTextures {
151 match load_image(_file, _scale_factor) {
152 Ok((img, source_format, original_size)) => {
153 kind = EmbeddedResourcesKind::TextureData(generate_texture(
154 img,
155 source_format,
156 original_size,
157 ))
158 }
159 Err(err) => {
160 diag.push_error(
161 format!("Cannot load image file {path}: {err}"),
162 source_location,
163 );
164 return ImageReference::None;
165 }
166 }
167 }
168 e.insert(EmbeddedResources { id: maybe_id, kind })
169 } else {
170 diag.push_error(format!("Cannot find image file {path}"), source_location);
171 return ImageReference::None;
172 }
173 }
174 };
175
176 match e.kind {
177 #[cfg(feature = "software-renderer")]
178 EmbeddedResourcesKind::TextureData { .. } => {
179 ImageReference::EmbeddedTexture { resource_id: e.id }
180 }
181 _ => ImageReference::EmbeddedData {
182 resource_id: e.id,
183 extension: std::path::Path::new(path)
184 .extension()
185 .and_then(|e| e.to_str())
186 .map(|x| x.to_string())
187 .unwrap_or_default(),
188 },
189 }
190}
191
192#[cfg(feature = "software-renderer")]
193trait Pixel {
194 fn is_transparent(&self) -> bool;
197}
198#[cfg(feature = "software-renderer")]
199impl Pixel for image::Rgba<u8> {
200 fn is_transparent(&self) -> bool {
203 self[3] <= 1
204 }
205}
206
207#[cfg(feature = "software-renderer")]
208fn generate_texture(
209 image: image::RgbaImage,
210 source_format: SourceFormat,
211 original_size: Size,
212) -> Texture {
213 let mut top = 0;
215 let is_line_transparent = |y| {
216 for x in 0..image.width() {
217 if !image.get_pixel(x, y).is_transparent() {
218 return false;
219 }
220 }
221 true
222 };
223 while top < image.height() && is_line_transparent(top) {
224 top += 1;
225 }
226 if top == image.height() {
227 return Texture::new_empty();
228 }
229 let mut bottom = image.height() - 1;
230 while is_line_transparent(bottom) {
231 bottom -= 1;
232 assert!(bottom > top); }
234 let is_column_transparent = |x| {
235 for y in top..=bottom {
236 if !image.get_pixel(x, y).is_transparent() {
237 return false;
238 }
239 }
240 true
241 };
242 let mut left = 0;
243 while is_column_transparent(left) {
244 left += 1;
245 assert!(left < image.width()); }
247 let mut right = image.width() - 1;
248 while is_column_transparent(right) {
249 right -= 1;
250 assert!(right > left); }
252 let mut is_opaque = true;
253 enum ColorState {
254 Unset,
255 Different,
256 Rgb([u8; 3]),
257 }
258 let mut color = ColorState::Unset;
259 'outer: for y in top..=bottom {
260 for x in left..=right {
261 let p = image.get_pixel(x, y);
262 let alpha = p[3];
263 if alpha != 255 {
264 is_opaque = false;
265 }
266 if alpha == 0 {
267 continue;
268 }
269 let get_pixel = || match source_format {
270 SourceFormat::RgbaPremultiplied => <[u8; 3]>::try_from(&p.0[0..3])
271 .unwrap()
272 .map(|v| (v as u16 * 255 / alpha as u16) as u8),
273 SourceFormat::Rgba => p.0[0..3].try_into().unwrap(),
274 };
275 match color {
276 ColorState::Unset => {
277 color = ColorState::Rgb(get_pixel());
278 }
279 ColorState::Different => {
280 if !is_opaque {
281 break 'outer;
282 }
283 }
284 ColorState::Rgb([a, b, c]) => {
285 let abs_diff = |t, u| {
286 if t < u {
287 u - t
288 } else {
289 t - u
290 }
291 };
292 let px = get_pixel();
293 if abs_diff(a, px[0]) > 2 || abs_diff(b, px[1]) > 2 || abs_diff(c, px[2]) > 2 {
294 color = ColorState::Different
295 }
296 }
297 }
298 }
299 }
300
301 let format = if let ColorState::Rgb(c) = color {
302 PixelFormat::AlphaMap(c)
303 } else if is_opaque {
304 PixelFormat::Rgb
305 } else {
306 PixelFormat::RgbaPremultiplied
307 };
308
309 let rect = Rect::from_ltrb(left as _, top as _, (right + 1) as _, (bottom + 1) as _).unwrap();
310 Texture {
311 total_size: Size { width: image.width(), height: image.height() },
312 original_size,
313 rect,
314 data: convert_image(image, source_format, format, rect),
315 format,
316 }
317}
318
319#[cfg(feature = "software-renderer")]
320fn convert_image(
321 image: image::RgbaImage,
322 source_format: SourceFormat,
323 format: PixelFormat,
324 rect: Rect,
325) -> Vec<u8> {
326 let i = image::SubImage::new(&image, rect.x() as _, rect.y() as _, rect.width(), rect.height());
327 match (source_format, format) {
328 (_, PixelFormat::Rgb) => {
329 i.pixels().flat_map(|(_, _, p)| IntoIterator::into_iter(p.0).take(3)).collect()
330 }
331 (SourceFormat::RgbaPremultiplied, PixelFormat::RgbaPremultiplied)
332 | (SourceFormat::Rgba, PixelFormat::Rgba) => {
333 i.pixels().flat_map(|(_, _, p)| IntoIterator::into_iter(p.0)).collect()
334 }
335 (SourceFormat::Rgba, PixelFormat::RgbaPremultiplied) => i
336 .pixels()
337 .flat_map(|(_, _, p)| {
338 let a = p.0[3] as u32;
339 IntoIterator::into_iter(p.0)
340 .take(3)
341 .map(move |x| (x as u32 * a / 255) as u8)
342 .chain(std::iter::once(a as u8))
343 })
344 .collect(),
345 (SourceFormat::RgbaPremultiplied, PixelFormat::Rgba) => i
346 .pixels()
347 .flat_map(|(_, _, p)| {
348 let a = p.0[3] as u32;
349 IntoIterator::into_iter(p.0)
350 .take(3)
351 .map(move |x| (x as u32 * 255 / a) as u8)
352 .chain(std::iter::once(a as u8))
353 })
354 .collect(),
355 (_, PixelFormat::AlphaMap(_)) => i.pixels().map(|(_, _, p)| p[3]).collect(),
356 }
357}
358
359#[cfg(feature = "software-renderer")]
360enum SourceFormat {
361 RgbaPremultiplied,
362 Rgba,
363}
364
365#[cfg(feature = "software-renderer")]
366fn load_image(
367 file: crate::fileaccess::VirtualFile,
368 scale_factor: f64,
369) -> image::ImageResult<(image::RgbaImage, SourceFormat, Size)> {
370 use resvg::{tiny_skia, usvg};
371 use std::ffi::OsStr;
372 if file.canon_path.extension() == Some(OsStr::new("svg"))
373 || file.canon_path.extension() == Some(OsStr::new("svgz"))
374 {
375 let tree = {
376 let option = usvg::Options::default();
377 match file.builtin_contents {
378 Some(data) => usvg::Tree::from_data(data, &option),
379 None => usvg::Tree::from_data(
380 std::fs::read(&file.canon_path).map_err(image::ImageError::IoError)?.as_slice(),
381 &option,
382 ),
383 }
384 .map_err(|e| {
385 image::ImageError::Decoding(image::error::DecodingError::new(
386 image::error::ImageFormatHint::Name("svg".into()),
387 e,
388 ))
389 })
390 }?;
391 let scale_factor = scale_factor as f32;
392 let original_size = tree.size();
394 let width = original_size.width() * scale_factor;
395 let height = original_size.height() * scale_factor;
396
397 let mut buffer = vec![0u8; width as usize * height as usize * 4];
398 let size_error = || {
399 image::ImageError::Limits(image::error::LimitError::from_kind(
400 image::error::LimitErrorKind::DimensionError,
401 ))
402 };
403 let mut skia_buffer =
404 tiny_skia::PixmapMut::from_bytes(buffer.as_mut_slice(), width as u32, height as u32)
405 .ok_or_else(size_error)?;
406 resvg::render(
407 &tree,
408 tiny_skia::Transform::from_scale(scale_factor as _, scale_factor as _),
409 &mut skia_buffer,
410 );
411 return image::RgbaImage::from_raw(width as u32, height as u32, buffer)
412 .ok_or_else(size_error)
413 .map(|img| {
414 (
415 img,
416 SourceFormat::RgbaPremultiplied,
417 Size { width: original_size.width() as _, height: original_size.height() as _ },
418 )
419 });
420 }
421 if let Some(buffer) = file.builtin_contents {
422 image::load_from_memory(buffer)
423 } else {
424 image::open(file.canon_path)
425 }
426 .map(|mut image| {
427 let (original_width, original_height) = image.dimensions();
428
429 if scale_factor < 1. {
430 image = image.resize_exact(
431 (original_width as f64 * scale_factor) as u32,
432 (original_height as f64 * scale_factor) as u32,
433 image::imageops::FilterType::Gaussian,
434 );
435 }
436
437 (
438 image.to_rgba8(),
439 SourceFormat::Rgba,
440 Size { width: original_width, height: original_height },
441 )
442 })
443}