diff --git a/Cargo.lock b/Cargo.lock index c58738e7..93591114 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -522,6 +522,12 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" +[[package]] +name = "bit_field" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" + [[package]] name = "bitflags" version = "1.3.2" @@ -623,6 +629,18 @@ version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" +[[package]] +name = "bytemuck" +version = "1.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2490600f404f2b94c167e31d3ed1d5f3c225a0f3b80230053b3e0b7b962bd9" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytes" version = "0.5.6" @@ -720,6 +738,12 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" +[[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.0" @@ -822,14 +846,36 @@ dependencies = [ ] [[package]] -name = "crossbeam-utils" -version = "0.8.17" +name = "crossbeam-deque" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d96137f14f244c37f989d9fff8f95e6c18b918e71f36638f8c49112e4c78f" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" dependencies = [ - "cfg-if 1.0.0", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", ] +[[package]] +name = "crossbeam-utils" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + [[package]] name = "crypto-common" version = "0.1.6" @@ -1129,6 +1175,22 @@ dependencies = [ "pin-project-lite 0.2.13", ] +[[package]] +name = "exr" +version = "1.72.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "887d93f60543e9a9362ef8a21beedd0a833c5d9610e18c67abe15a5963dcb1a4" +dependencies = [ + "bit_field", + "flume 0.11.0", + "half", + "lebe", + "miniz_oxide", + "rayon-core", + "smallvec", + "zune-inflate", +] + [[package]] name = "fastrand" version = "1.9.0" @@ -1144,6 +1206,15 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +[[package]] +name = "fdeflate" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f9bfee30e4dedf0ab8b422f03af778d9612b63f502710fc500a334ebe2de645" +dependencies = [ + "simd-adler32", +] + [[package]] name = "femme" version = "2.2.1" @@ -1187,6 +1258,15 @@ dependencies = [ "spinning_top", ] +[[package]] +name = "flume" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" +dependencies = [ + "spin", +] + [[package]] name = "fnv" version = "1.0.7" @@ -1214,24 +1294,24 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" dependencies = [ "futures-core", ] [[package]] name = "futures-core" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" [[package]] name = "futures-io" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" [[package]] name = "futures-lite" @@ -1263,9 +1343,9 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", @@ -1274,21 +1354,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" [[package]] name = "futures-task" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" [[package]] name = "futures-util" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ "futures-core", "futures-io", @@ -1351,6 +1431,16 @@ dependencies = [ "polyval", ] +[[package]] +name = "gif" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80792593675e051cf94a4b111980da2ba60d4a83e43e0048c5693baab3977045" +dependencies = [ + "color_quant", + "weezl", +] + [[package]] name = "globset" version = "0.4.14" @@ -1376,6 +1466,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "half" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc52e53916c08643f1b56ec082790d1e86a32e58dc5268f897f313fbae7b4872" +dependencies = [ + "cfg-if 1.0.0", + "crunchy", +] + [[package]] name = "hashbrown" version = "0.14.3" @@ -1530,6 +1630,24 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "image" +version = "0.24.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "034bbe799d1909622a74d1193aa50147769440040ff36cb2baa947609b0a4e23" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "exr", + "gif", + "jpeg-decoder", + "num-traits", + "png", + "qoi", + "tiff", +] + [[package]] name = "indexmap" version = "2.1.0" @@ -1577,7 +1695,7 @@ dependencies = [ "crossbeam-utils", "curl", "curl-sys", - "flume", + "flume 0.9.2", "futures-lite 1.13.0", "http", "log", @@ -1596,6 +1714,15 @@ version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +[[package]] +name = "jpeg-decoder" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" +dependencies = [ + "rayon", +] + [[package]] name = "js-sys" version = "0.3.66" @@ -1614,6 +1741,12 @@ dependencies = [ "log", ] +[[package]] +name = "lebe" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" + [[package]] name = "libc" version = "0.2.151" @@ -1757,6 +1890,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" dependencies = [ "adler", + "simd-adler32", ] [[package]] @@ -2001,6 +2135,19 @@ version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" +[[package]] +name = "png" +version = "0.17.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f6c3c3e617595665b8ea2ff95a86066be38fb121ff920a9c0eb282abcd1da5a" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + [[package]] name = "polling" version = "2.8.0" @@ -2105,6 +2252,15 @@ dependencies = [ "unicase", ] +[[package]] +name = "qoi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" +dependencies = [ + "bytemuck", +] + [[package]] name = "quick-xml" version = "0.30.0" @@ -2195,6 +2351,26 @@ dependencies = [ "rand_core 0.5.1", ] +[[package]] +name = "rayon" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7237101a77a10773db45d62004a272517633fbcc3df19d96455ede1122e051" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "redox_syscall" version = "0.4.1" @@ -2519,6 +2695,7 @@ dependencies = [ "atom_syndication", "chrono", "cooklang", + "image", "maud", "once_cell", "pulldown-cmark", @@ -2536,6 +2713,12 @@ dependencies = [ "libc", ] +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + [[package]] name = "siphasher" version = "0.3.11" @@ -2578,6 +2761,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + [[package]] name = "spinning_top" version = "0.2.5" @@ -2884,6 +3076,17 @@ dependencies = [ "tide", ] +[[package]] +name = "tiff" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba1310fcea54c6a9a4fd1aad794ecc02c31682f6bfbecdf460bf19533eed1e3e" +dependencies = [ + "flate2", + "jpeg-decoder", + "weezl", +] + [[package]] name = "time" version = "0.2.27" @@ -3279,6 +3482,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "weezl" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" + [[package]] name = "winapi" version = "0.3.9" @@ -3476,3 +3685,12 @@ name = "yansi" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" + +[[package]] +name = "zune-inflate" +version = "0.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" +dependencies = [ + "simd-adler32", +] diff --git a/serve/build.rs b/serve/build.rs index 5cca8e78..b50c9e27 100644 --- a/serve/build.rs +++ b/serve/build.rs @@ -1,6 +1,6 @@ use shared::{ assets, build, path, - types::{cocktails, entries, movies, places, records}, + types::{self, cocktails, entries, images, movies, places, records}, }; #[derive(rust_embed::RustEmbed)] @@ -26,6 +26,7 @@ enum BuildError { Movie(std::path::PathBuf, movies::FromError), Record(std::path::PathBuf, records::FromError), Place(std::path::PathBuf, places::FromError), + Image(std::path::PathBuf, images::ImageError), } impl std::fmt::Display for BuildError { @@ -38,6 +39,7 @@ impl std::fmt::Display for BuildError { BuildError::Movie(path, error) => write!(f, "{}: {error}", path.display()), BuildError::Record(path, error) => write!(f, "{}: {error}", path.display()), BuildError::Place(path, error) => write!(f, "{}: {error}", path.display()), + BuildError::Image(path, error) => write!(f, "{}: {error}", path.display()), } } } @@ -85,6 +87,22 @@ fn build() -> Result<(), BuildError> { }) .collect::, _>>()?; + let (cocktail_images, assets): (Vec<_>, Vec<_>) = assets.into_iter().partition(|asset| { + asset.path.starts_with("cocktails/") + && asset.path.extension() == Some(std::ffi::OsStr::new("jpeg")) + }); + let cocktail_images = cocktail_images + .iter() + .map(|asset| { + types::images::Image::try_from(asset) + .map(|image| vec![image.resize(Some(200), None), image.resize(Some(800), None)]) + .map_err(|err| BuildError::Image(asset.path.clone(), err)) + }) + .collect::, _>>()? + .into_iter() + .flatten() + .collect::>(); + let (movies, assets): (Vec<_>, Vec<_>) = assets.into_iter().partition(|asset| { asset.path.starts_with("movies/") && asset.path.extension() == Some(std::ffi::OsStr::new("json")) @@ -96,6 +114,19 @@ fn build() -> Result<(), BuildError> { }) .collect::, _>>()?; + let (movie_posters, assets): (Vec<_>, Vec<_>) = assets.into_iter().partition(|asset| { + asset.path.starts_with("movies/") + && asset.path.extension() == Some(std::ffi::OsStr::new("jpg")) + }); + let movie_posters = movie_posters + .iter() + .map(|asset| { + types::images::Image::try_from(asset) + .map(|image| image.resize(Some(70), None)) + .map_err(|err| BuildError::Image(asset.path.clone(), err)) + }) + .collect::, _>>()?; + let (records, assets): (Vec<_>, Vec<_>) = assets.into_iter().partition(|asset| { asset.path.starts_with("records/") && asset.path.extension() == Some(std::ffi::OsStr::new("json")) @@ -108,6 +139,19 @@ fn build() -> Result<(), BuildError> { }) .collect::, _>>()?; + let (record_covers, assets): (Vec<_>, Vec<_>) = assets.into_iter().partition(|asset| { + asset.path.starts_with("records/") + && asset.path.extension() == Some(std::ffi::OsStr::new("jpeg")) + }); + let record_covers = record_covers + .iter() + .map(|asset| { + types::images::Image::try_from(asset) + .map(|image| image.resize(Some(200), None)) + .map_err(|err| BuildError::Image(asset.path.clone(), err)) + }) + .collect::, _>>()?; + let (places, assets): (Vec<_>, Vec<_>) = assets.into_iter().partition(|asset| { asset.path.starts_with("places/") && asset.path.extension() == Some(std::ffi::OsStr::new("json")) @@ -157,6 +201,16 @@ fn build() -> Result<(), BuildError> { ) .map_err(BuildError::Io)?; + for cover in record_covers { + write( + join(&output, &cover.path), + &cover + .data() + .map_err(|err| BuildError::Image(cover.path.clone(), err))?, + ) + .map_err(BuildError::Io)?; + } + write( join(&output, "cocktails/index.html"), build::html::cocktails(cocktails.as_slice()) @@ -164,6 +218,15 @@ fn build() -> Result<(), BuildError> { .as_bytes(), ) .map_err(BuildError::Io)?; + for image in cocktail_images { + write( + join(&output, &image.path), + &image + .data() + .map_err(|err| BuildError::Image(image.path.clone(), err))?, + ) + .map_err(BuildError::Io)?; + } write( join(&output, "restaurants_and_cafes/index.html"), @@ -185,6 +248,15 @@ fn build() -> Result<(), BuildError> { .as_bytes(), ) .map_err(BuildError::Io)?; + for poster in movie_posters { + write( + join(&output, &poster.path), + &poster + .data() + .map_err(|err| BuildError::Image(poster.path.clone(), err))?, + ) + .map_err(BuildError::Io)?; + } for post in posts { for alias in &post.frontmatter.aliases { diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 037dd701..725296db 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -16,3 +16,4 @@ cooklang = "0.13.0" once_cell = "1.19.0" serde_json = "1.0.108" atom_syndication = "0.12.2" +image = { version = "0.24.8", features = ["webp", "jpeg", "png"] } diff --git a/shared/src/build/html.rs b/shared/src/build/html.rs index 66ec9b16..ee397c29 100644 --- a/shared/src/build/html.rs +++ b/shared/src/build/html.rs @@ -71,7 +71,7 @@ fn new( pub fn cocktail(cocktail: &cocktails::Cocktail) -> maud::Markup { let html = maud::html! { aside { - img src=(format!("./{}.jpeg", cocktail.frontmatter.title)) loading="lazy" alt=(cocktail.frontmatter.title); + img src=(format!("./{}.800x0@2x.webp", cocktail.frontmatter.title)) loading="lazy" alt=(cocktail.frontmatter.title); } (cocktail.body) }; @@ -149,20 +149,20 @@ pub fn cocktails(cocktails: &[cocktails::Cocktail]) -> maud::Markup { .map(|cocktail| { ( cocktail.path.display().to_string(), - format!("./{}.jpeg", cocktail.frontmatter.title), cocktail.frontmatter.title.clone(), ) }) .collect::>(); - cocktails.sort_by(|a, b| a.2.cmp(&b.2)); + cocktails.sort_by(|a, b| a.1.cmp(&b.1)); let html = maud::html! { ul { - @for (href, image_href, title) in cocktails { + @for (href, title) in cocktails { li { a href=(href) { figure { - img src=(image_href) loading="lazy" alt=(title); + img width="200px" + src=(format!("./{title}.200x0@2x.webp")) loading="lazy" alt=(title); figcaption { center { (title) } } } } @@ -188,7 +188,7 @@ pub fn movies(movies: &[movies::Entry]) -> maud::Markup { .map(|movie| { ( movie.date.format("%Y-%m-%d").to_string(), - format!("./{}.jpg", movie.title_slug.replace('/', "-")), + format!("./{}.70x0@2x.webp", movie.title_slug.replace('/', "-")), movie.href.clone(), movie.title.clone(), movie.is_liked, @@ -232,22 +232,17 @@ pub fn movies(movies: &[movies::Entry]) -> maud::Markup { #[must_use] pub fn records(records: &[records::Record]) -> maud::Markup { let mut records = records - .into_iter() - .filter_map(|record| { - std::path::Path::new(&record.basic_information.cover_image) - .extension() - .and_then(|ext| ext.to_str()) - .map(|ext| { - ( - record.id, - record.basic_information.artists[0].name.clone(), - record.basic_information.title.clone(), - format!( - "./{}.{ext}", - record.basic_information.title.replace('/', "-") - ), - ) - }) + .iter() + .map(|record| { + ( + record.id, + record.basic_information.artists[0].name.clone(), + record.basic_information.title.clone(), + format!( + "./{}.200x0@2x.webp", + record.basic_information.title.replace('/', "-") + ), + ) }) .collect::>(); records.sort_by(|a, b| a.2.cmp(&b.2)); @@ -259,7 +254,7 @@ pub fn records(records: &[records::Record]) -> maud::Markup { li { a href=(format!("https://www.discogs.com/release/{}", id)) { figure { - img src=(cover_href) loading="lazy" alt=(title); + img width="200" src=(cover_href) loading="lazy" alt=(title); figcaption { center { span { (artist) } diff --git a/shared/src/types.rs b/shared/src/types.rs index 8e42fdfb..99af06dc 100644 --- a/shared/src/types.rs +++ b/shared/src/types.rs @@ -1,5 +1,6 @@ pub mod cocktails; pub mod entries; +pub mod images; pub mod movies; pub mod places; pub mod records; diff --git a/shared/src/types/images.rs b/shared/src/types/images.rs new file mode 100644 index 00000000..24b2ebad --- /dev/null +++ b/shared/src/types/images.rs @@ -0,0 +1,66 @@ +use crate::{assets, path}; + +pub struct Image { + pub path: std::path::PathBuf, + img: image::DynamicImage, +} + +impl TryFrom<&assets::Asset> for Image { + type Error = ImageError; + + fn try_from(value: &assets::Asset) -> Result { + let img = image::load_from_memory(&value.data).map_err(ImageError)?; + Ok(Self { + path: path::normalize(&value.path), + img, + }) + } +} + +impl Image { + #[must_use] + pub fn resize(&self, width: Option, height: Option) -> Image { + let img = self.img.resize( + width.map_or_else(|| self.img.width(), |width| width * 2), + height.map_or_else(|| self.img.height(), |height| height * 2), + image::imageops::FilterType::Triangle, + ); + + let file_stem = self + .path + .file_stem() + .and_then(|file_stem| file_stem.to_str()) + .unwrap_or_default(); + + let path = self.path.with_file_name(format!( + "{file_stem}.{}x{}@2x.webp", + width.unwrap_or(0), + height.unwrap_or(0), + )); + + Image { path, img } + } + + #[allow(clippy::missing_errors_doc)] + pub fn data(&self) -> Result, ImageError> { + let mut data = vec![]; + self.img + .write_to( + &mut std::io::Cursor::new(&mut data), + image::ImageOutputFormat::WebP, + ) + .map_err(ImageError)?; + Ok(data) + } +} + +#[derive(Debug)] +pub struct ImageError(image::ImageError); + +impl std::fmt::Display for ImageError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + +impl std::error::Error for ImageError {}