use {
color_eyre::eyre::{Context, Result, bail},
image::{DynamicImage, ImageReader},
macroni_n_cheese::Construct,
std::{io::Cursor, path::Path},
};
#[derive(Debug, Clone, Construct)]
pub struct ImageData {
pub rgb_data: Vec<u8>,
pub width: usize,
pub height: usize,
}
impl ImageData {
pub fn from_dynamic_image(img: DynamicImage) -> Self {
let rgba8 = img.to_rgba8();
let (width, height) = rgba8.dimensions();
let rgb_data = rgba8.into_raw();
Self {
rgb_data,
width: width as usize,
height: height as usize,
}
}
}
pub enum ImageSource {
Bytes(Vec<u8>),
Path(std::path::PathBuf),
Image(DynamicImage),
}
impl ImageSource {
pub async fn from_url(url: &str) -> Result<Self> {
let bytes = fetch_remote_file(url).await?;
Ok(Self::Bytes(bytes))
}
pub fn from_path(path: &Path) -> Result<Self> {
if !path.exists() {
bail!("File does not exist: {}", path.display());
}
Ok(Self::Path(path.to_path_buf()))
}
pub fn from_bytes(bytes: Vec<u8>) -> Self {
Self::Bytes(bytes)
}
pub fn from_image(image: DynamicImage) -> Self {
Self::Image(image)
}
pub fn load(self) -> Result<DynamicImage> {
match self {
Self::Bytes(bytes) => {
let cursor = Cursor::new(bytes);
ImageReader::new(cursor)
.with_guessed_format()
.context("Failed to guess image format")?
.decode()
.context("Failed to decode image")
}
Self::Path(path) => ImageReader::open(&path)
.with_context(|| format!("Failed to open {}", path.display()))?
.decode()
.with_context(|| format!("Failed to decode {}", path.display())),
Self::Image(img) => Ok(img),
}
}
}
pub async fn fetch_remote_file(url: &str) -> Result<Vec<u8>> {
let response = reqwest::get(url)
.await
.with_context(|| format!("Failed to fetch {}", url))?;
if !response.status().is_success() {
bail!(
"HTTP request failed with status: {} for URL: {}",
response.status(),
url
);
}
response
.bytes()
.await
.context("Failed to read response body")
.map(|bytes| bytes.to_vec())
}