summaryrefslogtreecommitdiff
path: root/src/image.rs
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2021-08-09 11:06:37 +0200
committerLaurenz <laurmaedje@gmail.com>2021-08-09 11:26:41 +0200
commit3932bb2cb93be95d67fc56998423eb9ce047fdfa (patch)
treec36bd4df1d2c74f8ae100d2f3bd3a0b232b797f5 /src/image.rs
parent3c92bad9a7cd6b880de197806443ffcce2cac9d8 (diff)
New source loading architecture
Diffstat (limited to 'src/image.rs')
-rw-r--r--src/image.rs167
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()
}
}