1use image::DynamicImage;
2use tokio::{
3 fs::{self, File},
4 io::AsyncReadExt,
5};
6
7pub async fn parse_image_url(url_unparsed: &str) -> Result<DynamicImage, anyhow::Error> {
8 let url = if let Ok(url) = url::Url::parse(url_unparsed) {
9 url
10 } else if File::open(url_unparsed).await.is_ok() {
11 url::Url::from_file_path(std::path::absolute(url_unparsed)?)
12 .map_err(|_| anyhow::anyhow!("Could not parse file path: {}", url_unparsed))?
13 } else {
14 url::Url::parse(&format!("data:image/png;base64,{}", url_unparsed))
15 .map_err(|_| anyhow::anyhow!("Could not parse as base64 data: {}", url_unparsed))?
16 };
17
18 let bytes = if url.scheme() == "http" || url.scheme() == "https" {
19 match reqwest::get(url.clone()).await {
21 Ok(http_resp) => http_resp.bytes().await?.to_vec(),
22 Err(e) => anyhow::bail!(e),
23 }
24 } else if url.scheme() == "file" {
25 let path = url
26 .to_file_path()
27 .map_err(|_| anyhow::anyhow!("Could not parse file path: {}", url))?;
28
29 if let Ok(mut f) = File::open(&path).await {
30 let metadata = fs::metadata(&path).await?;
32 let mut buffer = vec![0; metadata.len() as usize];
33 f.read_exact(&mut buffer).await?;
34 buffer
35 } else {
36 anyhow::bail!("Could not open file at path: {}", url);
37 }
38 } else if url.scheme() == "data" {
39 let data_url = data_url::DataUrl::process(url.as_str())?;
41 data_url.decode_to_vec()?.0
42 } else {
43 anyhow::bail!("Unsupported URL scheme: {}", url.scheme());
44 };
45
46 Ok(image::load_from_memory(&bytes)?)
47}
48
49#[cfg(test)]
50mod tests {
51 use image::GenericImageView;
52
53 use super::*;
54
55 #[tokio::test]
56 async fn test_parse_image_url() {
57 let url = "https://www.rust-lang.org/logos/rust-logo-32x32.png";
59 let image = parse_image_url(url).await.unwrap();
60 assert_eq!(image.dimensions(), (32, 32));
61
62 let url = "http://www.rust-lang.org/logos/rust-logo-32x32.png";
63 let image = parse_image_url(url).await.unwrap();
64 assert_eq!(image.dimensions(), (32, 32));
65
66 let url = "resources/rust-logo-32x32.png";
68 let image = parse_image_url(url).await.unwrap();
69 assert_eq!(image.dimensions(), (32, 32));
70
71 let absolute_path = std::path::absolute(url).unwrap();
73 let url = format!("file://{}", absolute_path.as_os_str().to_str().unwrap());
74 let image = parse_image_url(&url).await.unwrap();
75 assert_eq!(image.dimensions(), (32, 32));
76
77 let url = "
79 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
114 ";
115 let image = parse_image_url(url).await.unwrap();
116 assert_eq!(image.dimensions(), (32, 32));
117
118 let url = format!("data:image/png;base64,{}", url);
119 let image = parse_image_url(&url).await.unwrap();
120 assert_eq!(image.dimensions(), (32, 32));
121 }
122}