diff options
| author | Laurenz <laurmaedje@gmail.com> | 2022-09-19 12:49:36 +0200 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2022-09-19 12:49:36 +0200 |
| commit | 59f67b79c7ff50f0bc9a27373d0fa36d1523e08a (patch) | |
| tree | 4687eea50b231b630563dd14e5de85551f661efb /src/image.rs | |
| parent | 30be75c6687f1e03cf867d258b3ddba353cc7aa2 (diff) | |
Remove image store
Diffstat (limited to 'src/image.rs')
| -rw-r--r-- | src/image.rs | 290 |
1 files changed, 108 insertions, 182 deletions
diff --git a/src/image.rs b/src/image.rs index 8ef404fe..c2631477 100644 --- a/src/image.rs +++ b/src/image.rs @@ -1,213 +1,139 @@ //! Image handling. -use std::collections::{hash_map::Entry, HashMap}; -use std::ffi::OsStr; -use std::fmt::{self, Debug, Formatter}; use std::io; -use std::path::Path; -use std::sync::Arc; -use image::io::Reader as ImageReader; -use image::{DynamicImage, ImageFormat}; - -use crate::diag::{failed_to_load, StrResult}; -use crate::loading::{FileHash, Loader}; - -/// A unique identifier for a loaded image. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub struct ImageId(u32); - -impl ImageId { - /// Create an image id from the raw underlying value. - /// - /// This should only be called with values returned by - /// [`into_raw`](Self::into_raw). - pub const fn from_raw(v: u32) -> Self { - Self(v) - } - - /// Convert into the raw underlying value. - pub const fn into_raw(self) -> u32 { - self.0 - } +use crate::loading::Buffer; + +/// A raster or vector image. +/// +/// Values of this type are cheap to clone and hash. +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +pub struct Image { + /// The raw, undecoded image data. + data: Buffer, + /// The format of the encoded `buffer`. + format: ImageFormat, + /// The width in pixels. + width: u32, + /// The height in pixels. + height: u32, } -/// Storage for loaded and decoded images. -pub struct ImageStore { - loader: Arc<dyn Loader>, - files: HashMap<FileHash, ImageId>, - images: Vec<Image>, +/// A decoded image. +pub enum DecodedImage { + /// A pixel raster format, like PNG or JPEG. + Raster(image::DynamicImage), + /// An SVG vector graphic. + Svg(usvg::Tree), } -impl ImageStore { - /// Create a new, empty image store. - pub fn new(loader: Arc<dyn Loader>) -> Self { - Self { - loader, - files: HashMap::new(), - images: vec![], - } - } +/// A raster or vector image format. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +pub enum ImageFormat { + /// Raster format for illustrations and transparent graphics. + Png, + /// Lossy raster format suitable for photos. + Jpg, + /// Raster format that is typically used for short animated clips. + Gif, + /// The vector graphics format of the web. + Svg, +} - /// Get a reference to a loaded image. +impl Image { + /// Create an image from a raw buffer and a file extension. /// - /// This panics if no image with this `id` was loaded. This function should - /// 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.0 as usize] - } + /// The file extension is used to determine the format. + pub fn new(data: Buffer, ext: &str) -> io::Result<Self> { + let format = match ext { + "svg" | "svgz" => ImageFormat::Svg, + "png" => ImageFormat::Png, + "jpg" | "jpeg" => ImageFormat::Jpg, + "gif" => ImageFormat::Gif, + _ => { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + "unknown image format", + )); + } + }; - /// Load and decode an image file from a path relative to the compilation - /// environment's root. - pub fn load(&mut self, path: &Path) -> StrResult<ImageId> { - let mut try_load = || -> 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 ext = - path.extension().and_then(OsStr::to_str).unwrap_or_default(); - let image = Image::parse(&buffer, ext)?; - let id = ImageId(self.images.len() as u32); - self.images.push(image); - entry.insert(id) - } - }) + let (width, height) = match format { + ImageFormat::Svg => { + let opts = usvg::Options::default(); + let tree = + usvg::Tree::from_data(&data, &opts.to_ref()).map_err(invalid)?; + + let size = tree.svg_node().size; + let width = size.width().ceil() as u32; + let height = size.height().ceil() as u32; + (width, height) + } + _ => { + let cursor = io::Cursor::new(&data); + let format = convert_format(format); + let reader = image::io::Reader::with_format(cursor, format); + reader.into_dimensions().map_err(invalid)? + } }; - try_load().map_err(|err| failed_to_load("image", path, err)) + Ok(Self { data, format, width, height }) } -} -/// A loaded image. -#[derive(Debug)] -pub enum Image { - /// A pixel raster format, like PNG or JPEG. - Raster(RasterImage), - /// An SVG vector graphic. - Svg(Svg), -} + /// The raw image data. + pub fn data(&self) -> &Buffer { + &self.data + } -impl Image { - /// Parse an image from raw data. The file extension is used as a hint for - /// which error message describes the problem best. - pub fn parse(data: &[u8], ext: &str) -> io::Result<Self> { - match Svg::parse(data) { - Ok(svg) => return Ok(Self::Svg(svg)), - Err(err) if matches!(ext, "svg" | "svgz") => return Err(err), - Err(_) => {} - } - - match RasterImage::parse(data) { - Ok(raster) => return Ok(Self::Raster(raster)), - Err(err) if matches!(ext, "png" | "jpg" | "jpeg" | "gif") => return Err(err), - Err(_) => {} - } - - Err(io::Error::new( - io::ErrorKind::InvalidData, - "unknown image format", - )) + /// The format of the image. + pub fn format(&self) -> ImageFormat { + self.format } /// The width of the image in pixels. pub fn width(&self) -> u32 { - match self { - Self::Raster(image) => image.width(), - Self::Svg(image) => image.width(), - } + self.width } /// The height of the image in pixels. pub fn height(&self) -> u32 { - match self { - Self::Raster(image) => image.height(), - Self::Svg(image) => image.height(), - } + self.height + } + + /// Decode the image. + pub fn decode(&self) -> io::Result<DecodedImage> { + Ok(match self.format { + ImageFormat::Svg => { + let opts = usvg::Options::default(); + let tree = + usvg::Tree::from_data(&self.data, &opts.to_ref()).map_err(invalid)?; + DecodedImage::Svg(tree) + } + _ => { + let cursor = io::Cursor::new(&self.data); + let format = convert_format(self.format); + let reader = image::io::Reader::with_format(cursor, format); + let dynamic = reader.decode().map_err(invalid)?; + DecodedImage::Raster(dynamic) + } + }) } } -/// A raster image, supported through the image crate. -pub struct RasterImage { - /// The original format the image was encoded in. - pub format: ImageFormat, - /// The decoded image. - pub buf: DynamicImage, -} - -impl RasterImage { - /// Parse an image from raw data in a supported format (PNG, JPEG or GIF). - /// - /// 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::from(io::ErrorKind::InvalidData))?; - - let buf = reader - .decode() - .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?; - - Ok(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() - } -} - -impl Debug for RasterImage { - 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 a raster image format to the image crate's format. +fn convert_format(format: ImageFormat) -> image::ImageFormat { + match format { + ImageFormat::Png => image::ImageFormat::Png, + ImageFormat::Jpg => image::ImageFormat::Jpeg, + ImageFormat::Gif => image::ImageFormat::Gif, + ImageFormat::Svg => panic!("must be a raster format"), } } -/// An SVG image, supported through the usvg crate. -pub struct Svg(pub usvg::Tree); - -impl Svg { - /// Parse an SVG file from a data buffer. This also handles `.svgz` - /// compressed files. - pub fn parse(data: &[u8]) -> io::Result<Self> { - let usvg_opts = usvg::Options::default(); - usvg::Tree::from_data(data, &usvg_opts.to_ref()) - .map(Self) - .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err)) - } - - /// The width of the image in rounded-up nominal SVG pixels. - pub fn width(&self) -> u32 { - self.0.svg_node().size.width().ceil() as u32 - } - - /// The height of the image in rounded-up nominal SVG pixels. - pub fn height(&self) -> u32 { - self.0.svg_node().size.height().ceil() as u32 - } -} - -impl Debug for Svg { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.debug_struct("Svg") - .field("width", &self.0.svg_node().size.width()) - .field("height", &self.0.svg_node().size.height()) - .field("viewBox", &self.0.svg_node().view_box) - .finish() - } +/// Turn any error into an I/O error. +fn invalid<E>(error: E) -> io::Error +where + E: std::error::Error + Send + Sync + 'static, +{ + io::Error::new(io::ErrorKind::InvalidData, error) } |
