use std::fs::{self, File};
use std::io::{BufReader, BufWriter};
use camino::{Utf8Path, Utf8PathBuf};
use image::codecs::webp::WebPEncoder;
use image::{ExtendedColorType, ImageReader};
use thiserror::Error;
use crate::error::{BuildError, HauchiwaError};
use crate::loader::{Assets, GlobAssetsTask, Input};
use crate::{Blueprint, Handle};
const STORE: &str = "/hash/img/";
const CACHE: &str = ".cache/hash/img/";
const DIST: &str = "dist/hash/img/";
#[derive(Debug, Error)]
pub enum ImageError {
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("Image processing error: {0}")]
Image(#[from] image::ImageError),
#[error("Build error: {0}")]
Build(#[from] BuildError),
}
#[derive(Clone)]
pub struct Image {
pub path: Utf8PathBuf,
}
impl<G> Blueprint<G>
where
G: Send + Sync + 'static,
{
pub fn load_images(
&mut self,
path_glob: &'static [&'static str],
) -> Result<Handle<Assets<Image>>, HauchiwaError> {
Ok(self.add_task_opaque(GlobAssetsTask::new(
path_glob.to_vec(),
path_glob.to_vec(),
move |_, _, input: Input| {
let path = build_image(&input)?;
Ok((input.path, Image { path }))
},
)?))
}
}
fn build_image(file: &Input) -> Result<Utf8PathBuf, ImageError> {
let hash = file.hash.to_hex();
let path_store = Utf8Path::new(STORE).join(&hash).with_extension("webp");
let path_cache = Utf8Path::new(CACHE).join(&hash).with_extension("webp");
let path_dist = Utf8Path::new(DIST).join(&hash).with_extension("webp");
if !path_cache.exists() {
fs::create_dir_all(CACHE)?;
let cache = File::create(&path_cache)?;
let mut writer = BufWriter::new(cache);
let source = File::open(&file.path)?;
let reader = BufReader::new(source);
process_image_to_writer(reader, &mut writer)?;
}
fs::create_dir_all(DIST)?;
if std::fs::hard_link(&path_cache, &path_dist).is_err() {
std::fs::copy(&path_cache, &path_dist)?;
}
Ok(path_store)
}
fn process_image_to_writer(
reader: impl std::io::BufRead + std::io::Seek,
writer: &mut impl std::io::Write,
) -> Result<(), ImageError> {
let img = ImageReader::new(reader).with_guessed_format()?.decode()?;
let w = img.width();
let h = img.height();
let rgba = img.into_rgba8();
WebPEncoder::new_lossless(writer).encode(&rgba, w, h, ExtendedColorType::Rgba8)?;
Ok(())
}