diff --git a/Cargo.lock b/Cargo.lock index 90fcb68..c5ecce9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -229,7 +229,7 @@ version = "0.71.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f58bf3d7db68cfbac37cfc485a8d711e87e064c3d0fe0435b92f7a407f9d6b3" dependencies = [ - "bitflags", + "bitflags 2.13.0", "cexpr", "clang-sys", "itertools", @@ -249,7 +249,7 @@ version = "0.72.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" dependencies = [ - "bitflags", + "bitflags 2.13.0", "cexpr", "clang-sys", "itertools", @@ -276,6 +276,12 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.13.0" @@ -339,7 +345,7 @@ version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c5e60b8c8d282c86360cab651ded04ab0335a7b5390c8d34145cbeab8cacf5f" dependencies = [ - "bitflags", + "bitflags 2.13.0", "btls-sys", "foreign-types", "libc", @@ -514,6 +520,12 @@ dependencies = [ "cc", ] +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + [[package]] name = "colorchoice" version = "1.0.5" @@ -1269,7 +1281,7 @@ dependencies = [ "tokio", "tokio-rustls", "tower-service", - "webpki-roots", + "webpki-roots 1.0.7", ] [[package]] @@ -1410,6 +1422,20 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd62e6b5e86ea8eeeb8db1de02880a6abc01a397b2ebb64b5d74ac255318f5cb" +[[package]] +name = "image" +version = "0.24.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "jpeg-decoder", + "num-traits", + "png 0.17.16", +] + [[package]] name = "indexmap" version = "2.14.0" @@ -1469,6 +1495,12 @@ dependencies = [ "libc", ] +[[package]] +name = "jpeg-decoder" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00810f1d8b74be64b13dbf3db89ac67740615d6c891f0e7b6179326533011a07" + [[package]] name = "js-sys" version = "0.3.102" @@ -1847,9 +1879,12 @@ name = "obscura-render" version = "0.1.0" dependencies = [ "ab_glyph", + "base64", + "image", "obscura-dom", "taffy", "tiny-skia", + "ureq", ] [[package]] @@ -2035,13 +2070,26 @@ version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" +[[package]] +name = "png" +version = "0.17.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + [[package]] name = "png" version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" dependencies = [ - "bitflags", + "bitflags 2.13.0", "crc32fast", "fdeflate", "flate2", @@ -2280,7 +2328,7 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags", + "bitflags 2.13.0", ] [[package]] @@ -2347,7 +2395,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "webpki-roots", + "webpki-roots 1.0.7", ] [[package]] @@ -2376,7 +2424,7 @@ version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags", + "bitflags 2.13.0", "errno", "libc", "linux-raw-sys 0.4.15", @@ -2389,7 +2437,7 @@ version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags", + "bitflags 2.13.0", "errno", "libc", "linux-raw-sys 0.12.1", @@ -2456,7 +2504,7 @@ version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd568a4c9bb598e291a08244a5c1f5a8a6650bee243b5b0f8dbb3d9cc1d87fe8" dependencies = [ - "bitflags", + "bitflags 2.13.0", "cssparser", "derive_more", "fxhash", @@ -2922,7 +2970,7 @@ dependencies = [ "bytemuck", "cfg-if", "log", - "png", + "png 0.18.1", "tiny-skia-path", ] @@ -3069,7 +3117,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" dependencies = [ "async-compression", - "bitflags", + "bitflags 2.13.0", "bytes", "futures-core", "futures-util", @@ -3247,6 +3295,21 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[package]] +name = "ureq" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02d1a66277ed75f640d608235660df48c8e3c19f3b4edb6a263315626cc3c01d" +dependencies = [ + "base64", + "log", + "once_cell", + "rustls", + "rustls-pki-types", + "url", + "webpki-roots 0.26.11", +] + [[package]] name = "url" version = "2.5.8" @@ -3296,7 +3359,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33995a1fee055ff743281cde33a41f0d618ee0bdbe8bdf6859e11864499c2595" dependencies = [ "bindgen 0.71.1", - "bitflags", + "bitflags 2.13.0", "fslock", "gzip-header", "home", @@ -3449,7 +3512,7 @@ version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ - "bitflags", + "bitflags 2.13.0", "hashbrown 0.15.5", "indexmap", "semver", @@ -3484,6 +3547,15 @@ dependencies = [ "rustls-pki-types", ] +[[package]] +name = "webpki-roots" +version = "0.26.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" +dependencies = [ + "webpki-roots 1.0.7", +] + [[package]] name = "webpki-roots" version = "1.0.7" @@ -3768,7 +3840,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", - "bitflags", + "bitflags 2.13.0", "indexmap", "log", "serde", diff --git a/crates/obscura-render/Cargo.toml b/crates/obscura-render/Cargo.toml index d7bdef5..937c02f 100644 --- a/crates/obscura-render/Cargo.toml +++ b/crates/obscura-render/Cargo.toml @@ -8,15 +8,17 @@ description = "Optional scoped render layer for Obscura: layout (taffy) and, wit [features] default = [] -# Rasterize the laid-out tree to a pixmap (tiny-skia) with text (ab_glyph). +# Rasterize the laid-out tree to a pixmap (tiny-skia) with text (ab_glyph) and images. # Off by default; the layout core (getBoundingClientRect etc.) works without it. -paint = ["dep:tiny-skia", "dep:ab_glyph"] +paint = ["dep:tiny-skia", "dep:ab_glyph", "dep:image", "dep:ureq", "dep:base64"] [dependencies] taffy = { workspace = true } obscura-dom = { workspace = true } tiny-skia = { workspace = true, optional = true } ab_glyph = { workspace = true, optional = true } - +image = { version = "0.24", default-features = false, features = ["jpeg", "png", "webp"], optional = true } +ureq = { version = "2.9", default-features = false, features = ["tls"], optional = true } +base64 = { workspace = true, optional = true } [lib] path = "src/lib.rs" diff --git a/crates/obscura-render/src/paint.rs b/crates/obscura-render/src/paint.rs index f8f4d3e..7d098c0 100644 --- a/crates/obscura-render/src/paint.rs +++ b/crates/obscura-render/src/paint.rs @@ -90,6 +90,12 @@ pub fn paint_dom(tree: &DomTree, viewport: (f32, f32)) -> Option { pixmap.fill_path(&path, &paint, FillRule::Winding, Transform::identity(), None); } } + + if name.local.as_ref() == "img" { + if let Some(src) = node.get_attribute("src") { + paint_image(src, &rect, &mut pixmap); + } + } } Some(pixmap) } @@ -183,6 +189,53 @@ fn draw_text(pixmap: &mut Pixmap, text: &str, x: f32, y: f32, color: [u8; 4]) { } } +fn paint_image(src: &str, rect: &crate::Rect, pixmap: &mut Pixmap) { + if rect.width <= 0.0 || rect.height <= 0.0 { + return; + } + + let bytes = if src.starts_with("data:image/") { + if let Some(comma_idx) = src.find(',') { + use base64::Engine; + base64::engine::general_purpose::STANDARD.decode(&src[comma_idx + 1..]).ok() + } else { None } + } else if src.starts_with("http://") || src.starts_with("https://") { + ureq::get(src).call().ok().and_then(|resp| { + let mut buf = Vec::new(); + use std::io::Read; + resp.into_reader().read_to_end(&mut buf).ok()?; + Some(buf) + }) + } else { + None + }; + + if let Some(bytes) = bytes { + if let Ok(img) = image::load_from_memory(&bytes) { + let img = img.to_rgba8(); + let resized = image::imageops::resize(&img, rect.width as u32, rect.height as u32, image::imageops::FilterType::Triangle); + + let mut raw = resized.into_raw(); + for pixel in raw.chunks_exact_mut(4) { + let a = pixel[3] as u32; + pixel[0] = ((pixel[0] as u32 * a) / 255) as u8; + pixel[1] = ((pixel[1] as u32 * a) / 255) as u8; + pixel[2] = ((pixel[2] as u32 * a) / 255) as u8; + } + if let Some(img_pixmap) = Pixmap::from_vec(raw, tiny_skia::IntSize::from_wh(rect.width as u32, rect.height as u32).unwrap()) { + pixmap.draw_pixmap( + rect.x as i32, + rect.y as i32, + img_pixmap.as_ref(), + &tiny_skia::PixmapPaint::default(), + Transform::identity(), + None, + ); + } + } + } +} + #[cfg(test)] mod tests { use super::*;