1use crate::EmbedResourcesKind;
5use crate::diagnostics::BuildDiagnostics;
6use crate::embedded_resources::*;
7use crate::expression_tree::{Expression, ImageReference};
8use crate::object_tree::*;
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: f32,
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 { resource_ref, .. } = e
75 && let ImageReference::AbsolutePath(path) = resource_ref
76 {
77 urls.insert(path.clone(), None);
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: f32,
89 diag: &mut BuildDiagnostics,
90) {
91 if let Expression::ImageReference { resource_ref, source_location, nine_slice: _ } = e
92 && let ImageReference::AbsolutePath(path) = resource_ref
93 {
94 let mapped_path =
96 urls.get(path).unwrap_or(&Some(path.clone())).clone().unwrap_or(path.clone());
97 *path = mapped_path;
98 if embed_files != EmbedResourcesKind::Nothing
99 && (embed_files != EmbedResourcesKind::OnlyBuiltinResources
100 || path.starts_with("builtin:/"))
101 {
102 let image_ref = embed_image(
103 global_embedded_resources,
104 embed_files,
105 path,
106 scale_factor,
107 diag,
108 source_location,
109 );
110 if embed_files != EmbedResourcesKind::ListAllResources {
111 *resource_ref = image_ref;
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: f32,
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 { u - t } else { t - u }
287 };
288 let px = get_pixel();
289 if abs_diff(a, px[0]) > 2 || abs_diff(b, px[1]) > 2 || abs_diff(c, px[2]) > 2 {
290 color = ColorState::Different
291 }
292 }
293 }
294 }
295 }
296
297 let format = if let ColorState::Rgb(c) = color {
298 PixelFormat::AlphaMap(c)
299 } else if is_opaque {
300 PixelFormat::Rgb
301 } else {
302 PixelFormat::RgbaPremultiplied
303 };
304
305 let rect = Rect::from_ltrb(left as _, top as _, (right + 1) as _, (bottom + 1) as _).unwrap();
306 Texture {
307 total_size: Size { width: image.width(), height: image.height() },
308 original_size,
309 rect,
310 data: convert_image(image, source_format, format, rect),
311 format,
312 }
313}
314
315#[cfg(feature = "software-renderer")]
316fn convert_image(
317 image: image::RgbaImage,
318 source_format: SourceFormat,
319 format: PixelFormat,
320 rect: Rect,
321) -> Vec<u8> {
322 let i = image::SubImage::new(&image, rect.x() as _, rect.y() as _, rect.width(), rect.height());
323 match (source_format, format) {
324 (_, PixelFormat::Rgb) => {
325 i.pixels().flat_map(|(_, _, p)| IntoIterator::into_iter(p.0).take(3)).collect()
326 }
327 (SourceFormat::RgbaPremultiplied, PixelFormat::RgbaPremultiplied)
328 | (SourceFormat::Rgba, PixelFormat::Rgba) => {
329 i.pixels().flat_map(|(_, _, p)| IntoIterator::into_iter(p.0)).collect()
330 }
331 (SourceFormat::Rgba, PixelFormat::RgbaPremultiplied) => i
332 .pixels()
333 .flat_map(|(_, _, p)| {
334 let a = p.0[3] as u32;
335 IntoIterator::into_iter(p.0)
336 .take(3)
337 .map(move |x| (x as u32 * a / 255) as u8)
338 .chain(std::iter::once(a as u8))
339 })
340 .collect(),
341 (SourceFormat::RgbaPremultiplied, PixelFormat::Rgba) => i
342 .pixels()
343 .flat_map(|(_, _, p)| {
344 let a = p.0[3] as u32;
345 IntoIterator::into_iter(p.0)
346 .take(3)
347 .map(move |x| (x as u32 * 255 / a) as u8)
348 .chain(std::iter::once(a as u8))
349 })
350 .collect(),
351 (_, PixelFormat::AlphaMap(_)) => i.pixels().map(|(_, _, p)| p[3]).collect(),
352 }
353}
354
355#[cfg(feature = "software-renderer")]
356enum SourceFormat {
357 RgbaPremultiplied,
358 Rgba,
359}
360
361#[cfg(feature = "software-renderer")]
362fn load_image(
363 file: crate::fileaccess::VirtualFile,
364 scale_factor: f32,
365) -> image::ImageResult<(image::RgbaImage, SourceFormat, Size)> {
366 use resvg::{tiny_skia, usvg};
367 use std::ffi::OsStr;
368 if file.canon_path.extension() == Some(OsStr::new("svg"))
369 || file.canon_path.extension() == Some(OsStr::new("svgz"))
370 {
371 let tree = {
372 let option = usvg::Options::default();
373 match file.builtin_contents {
374 Some(data) => usvg::Tree::from_data(data, &option),
375 None => usvg::Tree::from_data(
376 std::fs::read(&file.canon_path).map_err(image::ImageError::IoError)?.as_slice(),
377 &option,
378 ),
379 }
380 .map_err(|e| {
381 image::ImageError::Decoding(image::error::DecodingError::new(
382 image::error::ImageFormatHint::Name("svg".into()),
383 e,
384 ))
385 })
386 }?;
387 let scale_factor = scale_factor as f32;
388 let original_size = tree.size();
390 let width = original_size.width() * scale_factor;
391 let height = original_size.height() * scale_factor;
392
393 let mut buffer = vec![0u8; width as usize * height as usize * 4];
394 let size_error = || {
395 image::ImageError::Limits(image::error::LimitError::from_kind(
396 image::error::LimitErrorKind::DimensionError,
397 ))
398 };
399 let mut skia_buffer =
400 tiny_skia::PixmapMut::from_bytes(buffer.as_mut_slice(), width as u32, height as u32)
401 .ok_or_else(size_error)?;
402 resvg::render(
403 &tree,
404 tiny_skia::Transform::from_scale(scale_factor as _, scale_factor as _),
405 &mut skia_buffer,
406 );
407 return image::RgbaImage::from_raw(width as u32, height as u32, buffer)
408 .ok_or_else(size_error)
409 .map(|img| {
410 (
411 img,
412 SourceFormat::RgbaPremultiplied,
413 Size { width: original_size.width() as _, height: original_size.height() as _ },
414 )
415 });
416 }
417 if let Some(buffer) = file.builtin_contents {
418 image::load_from_memory(buffer)
419 } else {
420 image::open(file.canon_path)
421 }
422 .map(|mut image| {
423 let (original_width, original_height) = image.dimensions();
424
425 if scale_factor < 1. {
426 image = image.resize_exact(
427 (original_width as f32 * scale_factor) as u32,
428 (original_height as f32 * scale_factor) as u32,
429 image::imageops::FilterType::Gaussian,
430 );
431 }
432
433 (
434 image.to_rgba8(),
435 SourceFormat::Rgba,
436 Size { width: original_width, height: original_height },
437 )
438 })
439}