kroki_rs/utils/
image_converter.rs1use anyhow::{Context, Result};
2use image::{codecs::webp::WebPEncoder, ImageEncoder};
3use resvg::{tiny_skia, usvg};
4use std::io::Cursor;
5
6#[derive(Debug, Clone, Copy)]
8pub enum WebpQuality {
9 Lossless,
11 Lossy(u8),
13}
14
15fn create_webp_encoder(
17 output: &mut Cursor<Vec<u8>>,
18 quality: WebpQuality,
19) -> WebPEncoder<&mut Cursor<Vec<u8>>> {
20 match quality {
21 WebpQuality::Lossless => WebPEncoder::new_lossless(output),
22 WebpQuality::Lossy(_q) => {
23 tracing::warn!("Lossy WebP requested but not yet supported (image crate limitation). Falling back to lossless.");
24 WebPEncoder::new_lossless(output)
25 }
26 }
27}
28
29pub async fn svg_to_webp(
31 svg_bytes: &[u8],
32 quality: WebpQuality,
33 fonts: &[String],
34 cache_dir: Option<&std::path::Path>,
35) -> Result<Vec<u8>> {
36 let mut opt = usvg::Options::default();
37 opt.fontdb_mut().load_system_fonts();
38
39 if !fonts.is_empty() {
40 let font_mgr = crate::utils::font_manager::FontManager::new(cache_dir)?;
41 let downloaded_dir = font_mgr.prepare_fonts(fonts).await?;
42 opt.fontdb_mut().load_fonts_dir(&downloaded_dir);
43 }
44
45 let tree = usvg::Tree::from_data(svg_bytes, &opt)
46 .context("Failed to parse SVG for WebP conversion")?;
47
48 let size = tree.size();
49 let mut pixmap = tiny_skia::Pixmap::new(size.width() as u32, size.height() as u32)
50 .context("Failed to allocate Pixmap for WebP conversion")?;
51
52 resvg::render(&tree, tiny_skia::Transform::default(), &mut pixmap.as_mut());
53
54 let mut output = Cursor::new(Vec::new());
55
56 let (width, height) = (pixmap.width(), pixmap.height());
57 let data = pixmap.data(); let encoder = create_webp_encoder(&mut output, quality);
60 encoder
61 .write_image(data, width, height, image::ExtendedColorType::Rgba8)
62 .context("Failed to encode WebP")?;
63
64 Ok(output.into_inner())
65}
66
67pub async fn png_to_webp(png_bytes: &[u8], quality: WebpQuality) -> Result<Vec<u8>> {
69 let img = image::load_from_memory_with_format(png_bytes, image::ImageFormat::Png)
70 .context("Failed to decode PNG for WebP conversion")?;
71
72 let mut output = Cursor::new(Vec::new());
73
74 let encoder = create_webp_encoder(&mut output, quality);
75 encoder
76 .write_image(
77 img.as_bytes(),
78 img.width(),
79 img.height(),
80 img.color().into(),
81 )
82 .context("Failed to encode WebP from PNG")?;
83
84 Ok(output.into_inner())
85}