diff options
| author | Laurenz <laurmaedje@gmail.com> | 2021-08-09 11:06:37 +0200 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2021-08-09 11:26:41 +0200 |
| commit | 3932bb2cb93be95d67fc56998423eb9ce047fdfa (patch) | |
| tree | c36bd4df1d2c74f8ae100d2f3bd3a0b232b797f5 /src/image.rs | |
| parent | 3c92bad9a7cd6b880de197806443ffcce2cac9d8 (diff) | |
New source loading architecture
Diffstat (limited to 'src/image.rs')
| -rw-r--r-- | src/image.rs | 167 |
1 files changed, 91 insertions, 76 deletions
diff --git a/src/image.rs b/src/image.rs index f041fac1..f98c7b1b 100644 --- a/src/image.rs +++ b/src/image.rs @@ -2,122 +2,137 @@ use std::collections::{hash_map::Entry, HashMap}; use std::fmt::{self, Debug, Formatter}; -use std::io::Cursor; +use std::io; +use std::path::Path; use std::rc::Rc; use image::io::Reader as ImageReader; use image::{DynamicImage, GenericImageView, ImageFormat}; use serde::{Deserialize, Serialize}; -use crate::loading::{FileId, Loader}; +use crate::loading::{FileHash, Loader}; -/// A loaded image. -pub struct Image { - /// The original format the image was encoded in. - pub format: ImageFormat, - /// The decoded image. - pub buf: DynamicImage, -} +/// A unique identifier for a loaded image. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +#[derive(Serialize, Deserialize)] +pub struct ImageId(u32); -impl Image { - /// Parse an image from raw data in a supported format (PNG or JPEG). +impl ImageId { + /// Create an image id from the raw underlying value. /// - /// The image format is determined automatically. - pub fn parse(data: &[u8]) -> Option<Self> { - let cursor = Cursor::new(data); - let reader = ImageReader::new(cursor).with_guessed_format().ok()?; - let format = reader.format()?; - let buf = reader.decode().ok()?; - Some(Self { format, buf }) - } - - /// The width of the image. - pub fn width(&self) -> u32 { - self.buf.width() - } - - /// The height of the image. - pub fn height(&self) -> u32 { - self.buf.height() + /// This should only be called with values returned by + /// [`into_raw`](Self::into_raw). + pub const fn from_raw(v: u32) -> Self { + Self(v) } -} -impl Debug for Image { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.debug_struct("Image") - .field("format", &self.format) - .field("color", &self.buf.color()) - .field("width", &self.width()) - .field("height", &self.height()) - .finish() + /// Convert into the raw underlying value. + pub const fn into_raw(self) -> u32 { + self.0 } } -/// Caches decoded images. -pub struct ImageCache { +/// Storage for loaded and decoded images. +pub struct ImageStore { loader: Rc<dyn Loader>, - images: HashMap<ImageId, Image>, + files: HashMap<FileHash, ImageId>, + images: Vec<Image>, on_load: Option<Box<dyn Fn(ImageId, &Image)>>, } -impl ImageCache { - /// Create a new, empty image cache. +impl ImageStore { + /// Create a new, empty image store. pub fn new(loader: Rc<dyn Loader>) -> Self { Self { loader, - images: HashMap::new(), + files: HashMap::new(), + images: vec![], on_load: None, } } + /// Register a callback which is invoked each time an image is loaded. + pub fn on_load<F>(&mut self, f: F) + where + F: Fn(ImageId, &Image) + 'static, + { + self.on_load = Some(Box::new(f)); + } + /// Load and decode an image file from a path. - pub fn load(&mut self, file: FileId) -> Option<ImageId> { - let id = ImageId(file.into_raw()); - if let Entry::Vacant(entry) = self.images.entry(id) { - let buffer = self.loader.load_file(file).ok()?; - let image = Image::parse(&buffer)?; - if let Some(callback) = &self.on_load { - callback(id, &image); + pub fn load(&mut self, path: &Path) -> io::Result<ImageId> { + let hash = self.loader.resolve(path)?; + Ok(*match self.files.entry(hash) { + Entry::Occupied(entry) => entry.into_mut(), + Entry::Vacant(entry) => { + let buffer = self.loader.load(path)?; + let image = Image::parse(&buffer)?; + let id = ImageId(self.images.len() as u32); + if let Some(callback) = &self.on_load { + callback(id, &image); + } + self.images.push(image); + entry.insert(id) } - entry.insert(image); - } - Some(id) + }) } /// Get a reference to a loaded image. /// /// This panics if no image with this id was loaded. This function should - /// only be called with ids returned by [`load()`](Self::load). + /// only be called with ids returned by this store's [`load()`](Self::load) + /// method. #[track_caller] pub fn get(&self, id: ImageId) -> &Image { - &self.images[&id] - } - - /// Register a callback which is invoked each time an image is loaded. - pub fn on_load<F>(&mut self, f: F) - where - F: Fn(ImageId, &Image) + 'static, - { - self.on_load = Some(Box::new(f)); + &self.images[id.0 as usize] } } -/// A unique identifier for a loaded image. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -#[derive(Serialize, Deserialize)] -pub struct ImageId(u64); +/// A loaded image. +pub struct Image { + /// The original format the image was encoded in. + pub format: ImageFormat, + /// The decoded image. + pub buf: DynamicImage, +} -impl ImageId { - /// Create an image id from the raw underlying value. +impl Image { + /// Parse an image from raw data in a supported format (PNG or JPEG). /// - /// This should only be called with values returned by - /// [`into_raw`](Self::into_raw). - pub const fn from_raw(v: u64) -> Self { - Self(v) + /// The image format is determined automatically. + pub fn parse(data: &[u8]) -> io::Result<Self> { + let cursor = io::Cursor::new(data); + let reader = ImageReader::new(cursor).with_guessed_format()?; + + let format = reader.format().ok_or_else(|| { + io::Error::new(io::ErrorKind::InvalidData, "unknown image format") + })?; + + let buf = reader + .decode() + .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?; + + Ok(Self { format, buf }) } - /// Convert into the raw underlying value. - pub const fn into_raw(self) -> u64 { - self.0 + /// The width of the image. + pub fn width(&self) -> u32 { + self.buf.width() + } + + /// The height of the image. + pub fn height(&self) -> u32 { + self.buf.height() + } +} + +impl Debug for Image { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.debug_struct("Image") + .field("format", &self.format) + .field("color", &self.buf.color()) + .field("width", &self.width()) + .field("height", &self.height()) + .finish() } } |
