use std::borrow::Cow;
#[cfg(feature = "image")]
use std::path::Path;
#[cfg(feature = "image")]
use image::{DynamicImage, RgbImage, RgbaImage};
use crate::Error;
#[derive(Debug)]
pub struct ImageData<'a> {
width: u32,
height: u32,
data: Cow<'a, [u8]>,
}
impl<'a> ImageData<'a> {
pub fn new(width: u32, height: u32, data: &'a [u8]) -> Result<Self, Error> {
if data.len() as u32 != width * height * 4 {
return Err(Error::InvalidImageData);
}
Ok(Self {
width,
height,
data: Cow::Borrowed(data),
})
}
#[cfg(feature = "image")]
pub fn load<P>(path: P) -> Result<Self, Error>
where
P: AsRef<Path>,
{
let image = image::open(path).map_err(|cause| Error::ImageLoadError { cause })?;
Self::try_from(&image)
}
#[must_use]
pub fn width(&self) -> u32 {
self.width
}
#[must_use]
pub fn height(&self) -> u32 {
self.height
}
#[must_use]
pub fn data(&self) -> &[u8] {
&self.data
}
}
#[cfg(feature = "image")]
impl TryFrom<&DynamicImage> for ImageData<'_> {
type Error = Error;
fn try_from(image: &DynamicImage) -> Result<Self, Self::Error> {
match image {
DynamicImage::ImageRgb8(image) => Ok(Self::from(image)),
DynamicImage::ImageRgba8(image) => Ok(Self::from(image)),
_ => Err(Error::UnsupportedImage),
}
}
}
#[cfg(feature = "image")]
impl From<&RgbImage> for ImageData<'_> {
fn from(image: &RgbImage) -> Self {
let (width, height) = image.dimensions();
let size = (width * height) as usize;
let data = image
.pixels()
.fold(Vec::with_capacity(size * 4), |mut pixels, pixel| {
pixels.extend_from_slice(&[pixel[0], pixel[1], pixel[2], 255]);
pixels
});
Self {
width,
height,
data: data.into(),
}
}
}
#[cfg(feature = "image")]
impl From<&RgbaImage> for ImageData<'_> {
fn from(image: &RgbaImage) -> Self {
let (width, height) = image.dimensions();
let data = image.to_vec();
Self {
width,
height,
data: data.into(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new() {
let pixels = [
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, ];
let actual = ImageData::new(2, 2, &pixels).unwrap();
assert_eq!(actual.width(), 2);
assert_eq!(actual.height(), 2);
assert_eq!(actual.data(), &pixels);
}
#[test]
fn test_new_empty_data() {
let pixels = [];
let actual = ImageData::new(0, 0, &pixels);
assert!(actual.is_ok());
}
#[test]
fn test_new_invalid_data() {
let pixels = [255, 255, 255, 255];
let actual = ImageData::new(2, 2, &pixels);
assert!(actual.is_err());
}
#[cfg(feature = "image")]
#[test]
fn test_load_rgba_image() {
let actual = ImageData::load("../../gfx/olympic_logo.png").unwrap();
assert_eq!(actual.width(), 320);
assert_eq!(actual.height(), 213);
assert_eq!(actual.data().len(), 320 * 213 * 4);
}
#[cfg(feature = "image")]
#[test]
fn test_load_rgb_image() {
let actual = ImageData::load("../../gfx/holly-booth-hLZWGXy5akM-unsplash.jpg").unwrap();
assert_eq!(actual.width(), 480);
assert_eq!(actual.height(), 722);
assert_eq!(actual.data().len(), 480 * 722 * 4);
}
#[cfg(feature = "image")]
#[test]
fn test_load_invalid_path() {
let actual = ImageData::load("../../gfx/unknown.png");
assert!(actual.is_err());
}
#[cfg(feature = "image")]
#[test]
fn test_load_invalid_file() {
let actual = ImageData::load("../../gfx/colors/invalid.jpg");
assert!(actual.is_err());
}
}