#![allow(clippy::wildcard_imports)]
use crate::{
draw::Draw,
error::{
Error::{self, InvalidExtension},
Result,
},
pixel::*,
Dynamic, DynamicFrameIterator,
};
#[cfg(feature = "gif")]
use crate::encodings::gif;
#[cfg(feature = "jpeg")]
use crate::encodings::jpeg;
#[cfg(feature = "png")]
use crate::encodings::png;
#[cfg(feature = "webp")]
use crate::encodings::webp;
#[cfg(feature = "resize")]
use crate::ResizeAlgorithm;
#[cfg(any(feature = "png", feature = "gif", feature = "jpeg", feature = "webp"))]
use crate::{Decoder, Encoder};
use num_traits::{SaturatingAdd, SaturatingSub};
use std::{
ffi::OsStr,
fmt::{self, Display},
fs::File,
io::{Read, Write},
num::NonZeroU32,
path::Path,
};
/// The behavior to use when overlaying images on top of each other.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum OverlayMode {
/// Replace alpha values with the alpha values of the overlay image. This is the default
/// behavior.
Replace,
/// Merge the alpha values of overlay image with the alpha values of the base image.
Merge,
}
impl Default for OverlayMode {
fn default() -> Self {
Self::Replace
}
}
impl Display for OverlayMode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Merge => write!(f, "merge"),
Self::Replace => write!(f, "replace"),
}
}
}
/// A high-level image representation.
///
/// This represents a static, single-frame image.
/// See [`ImageSequence`] for information on opening animated or multi-frame images.
#[derive(Clone)]
pub struct Image<P: Pixel = Dynamic> {
pub(crate) width: NonZeroU32,
pub(crate) height: NonZeroU32,
/// A 1-dimensional vector of pixels representing all pixels in the image. This is shaped
/// according to the image's width and height to form the image.
///
/// This data is a low-level, raw representation of the image. You can see the various pixel
/// mapping functions, or use the [`pixels`] method directly for higher level representations
/// of the data.
pub data: Vec<P>,
pub(crate) format: ImageFormat,
pub(crate) overlay: OverlayMode,
pub(crate) palette: Option<Box<[P::Color]>>,
}
macro_rules! assert_nonzero {
($width:expr) => {{
debug_assert_ne!($width, 0, "width must be non-zero");
}};
($width:expr, $height:expr) => {{
assert_nonzero!($width);
debug_assert_ne!($height, 0, "height must be non-zero");
}};
}
impl<P: Pixel> Image<P> {
/// Creates a new image with the given width and height, with all pixels being set
/// intially to `fill`.
///
/// Both the width and height must be non-zero, or else this will panic. You should validate
/// the width and height before calling this function.
///
/// # Panics
/// * `width` or `height` is zero.
///
/// # Example
/// ```
/// # use ril::prelude::*;
/// # fn main() -> ril::Result<()> {
/// // 16x16 RGB image with all pixels set to white
/// let image = Image::new(16, 16, Rgb::white());
///
/// assert_eq!(image.width(), 16);
/// assert_eq!(image.height(), 16);
/// assert_eq!(image.pixel(0, 0), &Rgb::white());
/// # Ok(())
/// # }
/// ```
#[must_use]
pub fn new(width: u32, height: u32, fill: P) -> Self {
assert_nonzero!(width, height);
Self {
width: NonZeroU32::new(width).unwrap(),
height: NonZeroU32::new(height).unwrap(),
data: vec![fill; (width * height) as usize],
format: ImageFormat::default(),
overlay: OverlayMode::default(),
palette: None,
}
}
/// Creates a new image with the given width and height. The pixels are then resolved through
/// then given callback function which takes two parameters - the x and y coordinates of
/// a pixel - and returns a pixel.
///
/// # Example
/// ```
/// # use ril::prelude::*;
/// # fn main() -> ril::Result<()> {
/// let gradient = Image::from_fn(256, 256, |x, _y| L(x as u8));
///
/// assert_eq!(gradient.pixel(0, 0), &L(0));
/// assert_eq!(gradient.pixel(255, 0), &L(255));
/// # Ok(())
/// # }
/// ```
#[must_use]
pub fn from_fn(width: u32, height: u32, f: impl Fn(u32, u32) -> P) -> Self {
Self::new(width, height, P::default()).map_pixels_with_coords(|x, y, _| f(x, y))
}
/// Creates a new image shaped with the given width and a 1-dimensional sequence of pixels
/// which will be shaped according to the width.
///
/// # Panics
/// * The length of the pixels is not a multiple of the width.
///
/// # Example
/// ```
/// # use ril::prelude::*;
/// # fn main() -> ril::Result<()> {
/// let image = Image::from_pixels(2, &[L(0), L(1), L(2), L(3)]);
///
/// assert_eq!(image.width(), 2);
/// assert_eq!(image.height(), 2);
/// assert_eq!(image.pixel(1, 1), &L(3));
/// # Ok(())
/// # }
/// ```
#[must_use]
pub fn from_pixels(width: u32, pixels: impl AsRef<[P]>) -> Self {
assert_nonzero!(width);
let pixels = pixels.as_ref();
assert_eq!(
pixels.len() % width as usize,
0,
"length of pixels must be a multiple of the image width",
);
Self {
width: NonZeroU32::new(width).unwrap(),
// SAFETY: We have already asserted the width being non-zero above with addition to
// the height being a multiple of the width, meaning that the height cannot be zero.
height: unsafe { NonZeroU32::new_unchecked(pixels.len() as u32 / width) },
data: pixels.to_vec(),
format: ImageFormat::default(),
overlay: OverlayMode::default(),
palette: None,
}
}
/// Creates a new image shaped with the given width and a 1-dimensional sequence of paletted
/// pixels which will be shaped according to the width.
///
/// # Panics
/// * The length of the pixels is not a multiple of the width.
/// * The palette is empty.
/// * The a pixel index is out of bounds with regards to the palette.
///
/// # Example
/// ```
/// # use ril::prelude::*;
/// # fn main() -> ril::Result<()> {
/// let image = Image::<PalettedRgb>::from_paletted_pixels(
/// 2,
/// vec![Rgb::white(), Rgb::black()],
/// &[0, 1, 0, 1],
/// );
/// assert_eq!(image.pixel(1, 1).color(), Rgb::black());
/// # Ok(())
/// # }
/// ```
#[must_use]
pub fn from_paletted_pixels<'p>(
width: u32,
palette: impl ToOwned<Owned = Vec<P::Color>> + 'p,
pixels: impl AsRef<[P::Subpixel]>,
) -> Self
where
P: Paletted<'p>,
{
assert_nonzero!(width);
let pixels = pixels.as_ref();
debug_assert_eq!(
pixels.len() % width as usize,
0,
"length of pixels must be a multiple of the image width",
);
#[allow(clippy::redundant_clone)]
let palette = palette.to_owned().into_boxed_slice();
debug_assert!(!palette.is_empty(), "palette must not be empty");
let mut slf = Self {
width: NonZeroU32::new(width).unwrap(),
// SAFETY: We have already asserted the width being non-zero above with addition to
// the height being a multiple of the width, meaning that the height cannot be zero.
height: unsafe { NonZeroU32::new_unchecked(pixels.len() as u32 / width) },
data: Vec::new(),
format: ImageFormat::default(),
overlay: OverlayMode::default(),
palette: Some(palette),
};
let palette = unsafe {
slf.palette
.as_deref()
// SAFETY: references will be dropped when `Self` is dropped; we can guarantee that
// 'p is only valid for the lifetime of `Self`.
.map(|slice| std::slice::from_raw_parts(slice.as_ptr(), slice.len()))
// SAFETY: declared palette as `Some` in struct declaration
.unwrap_unchecked()
};
slf.data = pixels
.iter()
.map(|&p| P::from_palette(palette, p))
.collect();
slf
}
/// Decodes an image with the explicitly given image encoding from the raw byte stream.
///
/// # Errors
/// * `DecodingError`: The image could not be decoded, maybe it is corrupt.
///
/// # Example
/// ```no_run
/// # use ril::prelude::*;
/// # fn main() -> ril::Result<()> {
/// let file = std::fs::File::open("image.png")?;
/// let image = Image::<Rgb>::from_reader(ImageFormat::Png, file)?;
/// # Ok(())
/// # }
/// ```
pub fn from_reader(format: ImageFormat, bytes: impl Read) -> Result<Self> {
format.run_decoder(bytes)
}
/// Decodes an image from the given read stream of bytes, inferring its encoding.
///
/// # Errors
/// * `DecodingError`: The image could not be decoded, maybe it is corrupt.
/// * `UnknownEncodingFormat`: Could not infer the encoding from the image. Try explicitly
/// specifying it.
///
/// # Panics
/// * No decoder implementation for the given encoding format.
///
/// # Example
/// ```no_run
/// # use ril::prelude::*;
/// # fn main() -> ril::Result<()> {
/// let file = std::fs::File::open("image.png")?;
/// let image = Image::<Rgb>::from_reader_inferred(file)?;
/// # Ok(())
/// # }
/// ```
pub fn from_reader_inferred(mut bytes: impl Read) -> Result<Self> {
let buf = &mut [0; 12];
let n = bytes.read(buf)?;
match ImageFormat::infer_encoding(buf) {
ImageFormat::Unknown => Err(Error::UnknownEncodingFormat),
format => format.run_decoder((&buf[..n]).chain(bytes)),
}
}
/// Decodes an image with the explicitly given image encoding from the given bytes.
/// Could be useful in conjunction with the `include_bytes!` macro.
///
/// Currently, this is not any different from [`from_reader`].
///
/// # Errors
/// * `DecodingError`: The image could not be decoded, maybe it is corrupt.
///
/// # Panics
/// * No decoder implementation for the given encoding format.
///
/// # Examples
/// ```no_run,ignore
/// # use ril::prelude::*;
/// # fn main() -> ril::Result<()> {
/// let bytes = include_bytes!("sample.png") as &[u8];
/// let image = Image::<Rgb>::from_bytes(ImageFormat::Png, bytes)?;
/// # Ok(())
/// # }
/// ```
pub fn from_bytes(format: ImageFormat, bytes: impl AsRef<[u8]>) -> Result<Self> {
format.run_decoder(bytes.as_ref())
}
/// Decodes an image from the given bytes, inferring its encoding.
/// Could be useful in conjunction with the `include_bytes!` macro.
///
/// This is more efficient than [`from_reader_inferred`].
///
/// # Errors
/// * `DecodingError`: The image could not be decoded, maybe it is corrupt.
/// * `UnknownEncodingFormat`: Could not infer the encoding from the image. Try explicitly
/// specifying it.
///
/// # Panics
/// * No decoder implementation for the given encoding format.
///
/// # Examples
/// ```no_run,ignore
/// # use ril::prelude::*;
/// # fn main() -> ril::Result<()> {
/// let bytes = include_bytes!("sample.png") as &[u8];
/// let image = Image::<Rgb>::from_bytes_inferred(bytes)?;
/// # Ok(())
/// # }
/// ```
pub fn from_bytes_inferred(bytes: impl AsRef<[u8]>) -> Result<Self> {
match ImageFormat::infer_encoding(bytes.as_ref()) {
ImageFormat::Unknown => Err(Error::UnknownEncodingFormat),
format => format.run_decoder(bytes.as_ref()),
}
}
/// Opens a file from the given path and decodes it into an image.
///
/// The encoding of the image is automatically inferred. You can explicitly pass in an encoding
/// by using the [`from_reader`] method.
///
/// # Errors
/// * `DecodingError`: The image could not be decoded, maybe it is corrupt.
/// * `UnknownEncodingFormat`: Could not infer the encoding from the image. Try explicitly
/// specifying it.
/// * `IoError`: The file could not be opened.
///
/// # Panics
/// * No decoder implementation for the given encoding format.
///
/// # Example
/// ```no_run
/// # use ril::prelude::*;
/// # fn main() -> ril::Result<()> {
/// let image = Image::<Rgb>::open("sample.png")?;
/// println!("Image dimensions: {}x{}", image.width(), image.height());
/// # Ok(())
/// # }
/// ```
pub fn open(path: impl AsRef<Path>) -> Result<Self> {
let buffer = &mut Vec::new();
let mut file = File::open(path.as_ref())?;
file.read_to_end(buffer)?;
let format = match ImageFormat::from_path(path)? {
ImageFormat::Unknown => match ImageFormat::infer_encoding(&buffer[0..12]) {
ImageFormat::Unknown => return Err(Error::UnknownEncodingFormat),
format => format,
},
format => format,
};
format.run_decoder(buffer.as_slice())
}
/// Encodes the image with the given encoding and writes it to the given write buffer.
///
/// # Errors
/// * An error occured during encoding.
///
/// # Panics
/// * No encoder implementation for the given encoding format.
///
/// # Example
/// ```no_run
/// # use ril::prelude::*;
/// # fn main() -> ril::Result<()> {
/// let image = Image::new(100, 100, Rgb::new(255, 0, 0));
/// let mut out = Vec::new();
/// image.encode(ImageFormat::Png, &mut out)?;
/// # Ok(())
/// # }
/// ```
pub fn encode(&self, encoding: ImageFormat, dest: &mut impl Write) -> Result<()> {
encoding.run_encoder(self, dest)
}
/// Saves the image with the given encoding to the given path.
/// You can try saving to a memory buffer by using the [`encode`] method.
///
/// # Errors
/// * An error occured during encoding.
///
/// # Panics
/// * No encoder implementation for the given encoding format.
///
/// # Example
/// ```no_run
/// # use ril::prelude::*;
/// # fn main() -> ril::Result<()> {
/// let image = Image::new(100, 100, Rgb::new(255, 0, 0));
/// image.save(ImageFormat::Png, "out.png")?;
/// # Ok(())
/// # }
/// ```
pub fn save(&self, encoding: ImageFormat, path: impl AsRef<Path>) -> Result<()> {
let mut file = File::create(path).map_err(Error::IOError)?;
self.encode(encoding, &mut file)
}
/// Saves the image to the given path, inferring the encoding from the path/filename extension.
///
/// This is obviously slower than [`save`] since this method itself uses it. You should only
/// use this method if the filename is dynamic, or if you do not know the desired encoding
/// before runtime.
///
/// See [`save`] for more information on how saving works.
///
/// # Errors
/// * Could not infer encoding format.
/// * An error occured during encoding.
///
/// # Panics
/// * No encoder implementation for the given encoding format.
///
/// # Example
/// ```no_run
/// # use ril::prelude::*;
/// # fn main() -> ril::Result<()> {
/// let image = Image::new(100, 100, Rgb::new(255, 0, 0));
/// image.save_inferred("out.png")?;
/// # Ok(())
/// # }
/// ```
pub fn save_inferred(&self, path: impl AsRef<Path>) -> Result<()> {
let encoding = ImageFormat::from_path(path.as_ref())?;
match encoding {
ImageFormat::Unknown => Err(Error::UnknownEncodingFormat),
_ => self.save(encoding, path),
}
}
#[inline]
#[must_use]
const fn resolve_coordinate(&self, x: u32, y: u32) -> usize {
if x >= self.width() || y >= self.height() {
usize::MAX
} else {
(y * self.width() + x) as usize
}
}
/// Returns the width of the image.
#[inline]
#[must_use]
pub const fn width(&self) -> u32 {
self.width.get()
}
/// Returns the height of the image.
#[inline]
#[must_use]
pub const fn height(&self) -> u32 {
self.height.get()
}
/// Returns the nearest pixel coordinates to the center of the image.
///
/// This uses integer division which means if an image dimension is not even, then the value is
/// rounded down - e.g. a 5x5 image returns ``(2, 2)``, rounded down from ``(2.5, 2.5)``.
#[inline]
#[must_use]
pub const fn center(&self) -> (u32, u32) {
(self.width() / 2, self.height() / 2)
}
/// Returns an iterator of slices representing the pixels of the image.
/// Each slice in the Vec is a row. The returned slice should be of ``Vec<&[P; width]>``.
#[inline]
pub fn pixels(&self) -> impl Iterator<Item = &[P]> {
self.data.chunks_exact(self.width() as usize)
}
/// Returns the encoding format of the image. This is nothing more but metadata about the image.
/// When saving the image, you will still have to explicitly specify the encoding format.
#[inline]
#[must_use]
pub const fn format(&self) -> ImageFormat {
self.format
}
/// Returns the overlay mode of the image.
#[inline]
#[must_use]
pub const fn overlay_mode(&self) -> OverlayMode {
self.overlay
}
/// Returns the same image with its overlay mode set to the given value.
#[must_use]
pub const fn with_overlay_mode(mut self, mode: OverlayMode) -> Self {
self.overlay = mode;
self
}
/// Returns the dimensions of the image.
#[inline]
#[must_use]
pub const fn dimensions(&self) -> (u32, u32) {
(self.width(), self.height())
}
/// Returns the amount of pixels in the image.
#[inline]
#[must_use]
pub const fn len(&self) -> u32 {
self.width() * self.height()
}
/// Returns true if the image contains no pixels.
#[inline]
#[must_use]
pub const fn is_empty(&self) -> bool {
self.len() == 0
}
/// Returns a reference of the pixel at the given coordinates.
#[inline]
#[must_use]
pub fn pixel(&self, x: u32, y: u32) -> &P {
&self.data[self.resolve_coordinate(x, y)]
}
/// Returns a reference of the pixel at the given coordinates, but only if it exists.
#[inline]
#[must_use]
pub fn get_pixel(&self, x: u32, y: u32) -> Option<&P> {
self.data.get(self.resolve_coordinate(x, y))
}
/// Returns a mutable reference to the pixel at the given coordinates.
#[inline]
pub fn pixel_mut(&mut self, x: u32, y: u32) -> &mut P {
let pos = self.resolve_coordinate(x, y);
&mut self.data[pos]
}
/// Sets the pixel at the given coordinates to the given pixel.
#[inline]
pub fn set_pixel(&mut self, x: u32, y: u32, pixel: P) {
let pos = self.resolve_coordinate(x, y);
self.data[pos] = pixel;
}
/// Overlays the pixel at the given coordinates with the given pixel according to the overlay
/// mode.
#[inline]
pub fn overlay_pixel(&mut self, x: u32, y: u32, pixel: P) {
self.overlay_pixel_with_mode(x, y, pixel, self.overlay);
}
/// Overlays the pixel at the given coordinates with the given pixel according to the specified
/// overlay mode.
///
/// If the pixel is out of bounds, nothing occurs. This is expected, use [`set_pixel`] if you
/// want this to panic, or to use a custom overlay mode use [`pixel_mut`].
#[inline]
pub fn overlay_pixel_with_mode(&mut self, x: u32, y: u32, pixel: P, mode: OverlayMode) {
let pos = self.resolve_coordinate(x, y);
if let Some(target) = self.data.get_mut(pos) {
*target = target.overlay(pixel, mode);
}
}
/// Overlays the pixel at the given coordinates with the given alpha intensity. This does not
/// regard the overlay mode, since this is usually used for anti-aliasing.
///
/// If the pixel is out of bounds, nothing occurs. This is expected, use [`set_pixel`] if you
/// want this to panic, or to use a custom overlay mode use [`pixel_mut`].
#[inline]
pub fn overlay_pixel_with_alpha(
&mut self,
x: u32,
y: u32,
pixel: P,
mode: OverlayMode,
alpha: u8,
) {
let pos = self.resolve_coordinate(x, y);
if let Some(target) = self.data.get_mut(pos) {
*target = target.overlay_with_alpha(pixel, mode, alpha);
}
}
/// Inverts this image in place.
pub fn invert(&mut self) {
self.data.iter_mut().for_each(|p| *p = p.inverted());
}
/// Takes this image and inverts it. Useful for method chaining.
#[must_use]
pub fn inverted(self) -> Self {
self.map_pixels(|pixel| pixel.inverted())
}
/// Brightens the image by increasing all pixels by the specified amount of subpixels in place.
/// See [`darken`] to darken the image, since this usually does not take any negative values.
///
/// A subpixel is a value of a pixel's component, for example in RGB, each subpixel is a value
/// of either R, G, or B.
///
/// For anything with alpha, alpha is not brightened.
pub fn brighten(&mut self, amount: P::Subpixel)
where
P::Subpixel: SaturatingAdd + Copy,
{
self.data
.iter_mut()
.for_each(|p| *p = p.map_subpixels(|value| value.saturating_add(&amount), |a| a));
}
/// Darkens the image by decreasing all pixels by the specified amount of subpixels in place.
/// See [`brighten`] to brighten the image, since this usually does not take any negative values.
///
/// A subpixel is a value of a pixel's component, for example in RGB, each subpixel is a value
/// of either R, G, or B.
///
/// For anything with alpha, alpha is not brightened.
pub fn darken(&mut self, amount: P::Subpixel)
where
P::Subpixel: SaturatingSub + Copy,
{
self.data
.iter_mut()
.for_each(|p| *p = p.map_subpixels(|value| value.saturating_sub(&amount), |a| a));
}
/// Takes this image and brightens it by increasing all pixels by the specified amount of
/// subpixels. Negative values will darken the image. Useful for method chaining.
///
/// See [`darkened`] to darken the image, since this usually does not take any negative values.
///
/// A subpixel is a value of a pixel's component, for example in RGB, each subpixel is a value.
///
/// For anything with alpha, alpha is not brightened.
#[must_use]
pub fn brightened(self, amount: P::Subpixel) -> Self
where
P::Subpixel: SaturatingAdd + Copy,
{
self.map_pixels(|pixel| pixel.map_subpixels(|value| value.saturating_add(&amount), |a| a))
}
/// Takes this image and darkens it by decreasing all pixels by the specified amount of
/// subpixels. Negative values will brighten the image. Useful for method chaining.
///
/// See [`brightened`] to brighten the image, since this usually does not take any negative
/// values.
///
/// A subpixel is a value of a pixel's component, for example in RGB, each subpixel is a value.
///
/// For anything with alpha, alpha is not brightened.
#[must_use]
pub fn darkened(self, amount: P::Subpixel) -> Self
where
P::Subpixel: SaturatingSub + Copy,
{
self.map_pixels(|pixel| pixel.map_subpixels(|value| value.saturating_sub(&amount), |a| a))
}
#[allow(clippy::cast_lossless)]
fn prepare_hue_matrix(degrees: i32) -> (f64, f64, f64, f64, f64, f64, f64, f64, f64) {
let degrees = (degrees % 360) as f64;
let radians = degrees.to_radians();
let sinv = radians.sin();
let cosv = radians.cos();
(
sinv.mul_add(-0.213, cosv.mul_add(0.787, 0.213)),
sinv.mul_add(-0.715, cosv.mul_add(-0.715, 0.715)),
sinv.mul_add(0.928, cosv.mul_add(-0.072, 0.072)),
sinv.mul_add(0.143, cosv.mul_add(-0.213, 0.213)),
sinv.mul_add(0.140, cosv.mul_add(0.285, 0.715)),
sinv.mul_add(-0.283, cosv.mul_add(-0.072, 0.072)),
sinv.mul_add(-0.787, cosv.mul_add(-0.213, 0.213)),
sinv.mul_add(0.715, cosv.mul_add(-0.715, 0.715)),
sinv.mul_add(0.072, cosv.mul_add(0.928, 0.072)),
)
}
/// Hue rotates the image by the specified amount of degrees in place.
///
/// The hue is a standard angle degree, that is a value between 0 and 360, although values
/// below and above will be wrapped using the modulo operator.
///
/// For anything with alpha, alpha is not rotated.
pub fn hue_rotate(&mut self, degrees: i32)
where
P: TrueColor,
{
let mat = Self::prepare_hue_matrix(degrees);
self.data.iter_mut().for_each(|p| {
let (r, g, b, a) = p.as_rgba_tuple();
let (r, g, b) = (f64::from(r), f64::from(g), f64::from(b));
*p = P::from_rgba_tuple((
mat.2.mul_add(b, mat.0.mul_add(r, mat.1 * g)) as u8,
mat.5.mul_add(b, mat.3.mul_add(r, mat.4 * g)) as u8,
mat.8.mul_add(b, mat.6.mul_add(r, mat.7 * g)) as u8,
a,
));
});
}
/// Takes this image and hue rotates it by the specified amount of degrees.
/// Useful for method chaining.
///
/// See [`Self::hue_rotate`] for more information.
#[must_use]
pub fn hue_rotated(mut self, degrees: i32) -> Self
where
P: TrueColor,
{
self.hue_rotate(degrees);
self
}
/// Returns the image replaced with the given data. It is up to you to make sure
/// the data is the correct size.
///
/// The function should take the current image data and return the new data.
///
/// # Note
/// This will *not* work for paletted images, nor will it work for conversion to paletted
/// images. For conversion from paletted images, see the [`Self::flatten`] method to flatten
/// the palette fist. For conversion to paletted images, try quantizing the image.
pub fn map_data<T: Pixel>(self, f: impl FnOnce(Vec<P>) -> Vec<T>) -> Image<T> {
Image {
width: self.width,
height: self.height,
data: f(self.data),
format: self.format,
overlay: self.overlay,
palette: None,
}
}
/// Sets the data of this image to the new data. This is used a lot internally,
/// but should rarely be used by you.
///
/// # Panics
/// * Panics if the data is malformed.
pub fn set_data(&mut self, data: Vec<P>) {
assert_eq!(
self.width() * self.height(),
data.len() as u32,
"malformed data"
);
self.data = data;
}
/// Returns the image with each pixel in the image mapped to the given function.
///
/// The function should take the pixel and return another pixel.
pub fn map_pixels<T: Pixel>(self, f: impl Fn(P) -> T) -> Image<T> {
self.map_data(|data| data.into_iter().map(f).collect())
}
/// Returns the image with the each pixel in the image mapped to the given function, with
/// the function taking additional data of the pixel.
///
/// The function should take the x and y coordinates followed by the pixel and return the new
/// pixel.
pub fn map_pixels_with_coords<T: Pixel>(self, f: impl Fn(u32, u32, P) -> T) -> Image<T> {
let width = self.width;
self.map_data(|data| {
data.into_iter()
.zip(0..)
.map(|(p, i)| f(i % width, i / width, p))
.collect()
})
}
/// Similar to [`map_pixels_with_coords`], but this maps the pixels in place.
///
/// This means that the output pixel type must be the same.
pub fn map_in_place(&mut self, f: impl Fn(u32, u32, &mut P)) {
let width = self.width;
self.data
.iter_mut()
.zip(0..)
.for_each(|(p, i)| f(i % width, i / width, p));
}
/// Returns the image with each row of pixels represented as a slice mapped to the given
/// function.
///
/// The function should take the y coordinate followed by the row of pixels
/// (represented as a slice) and return an Iterator of pixels.
pub fn map_rows<I, T: Pixel>(self, f: impl Fn(u32, &[P]) -> I) -> Image<T>
where
I: IntoIterator<Item = T>,
{
let width = self.width();
self.map_data(|data| {
data.chunks(width as usize)
.into_iter()
.zip(0..)
.flat_map(|(row, y)| f(y, row))
.collect()
})
}
/// Iterates over each row of pixels in the image.
pub fn rows(&self) -> impl Iterator<Item = &[P]> {
self.data.chunks_exact(self.width() as usize)
}
/// Converts the image into an image with the given pixel type.
///
/// # Note
/// Currently there is a slight inconsistency with paletted images - if you would like to
/// convert from a paletted image to a paletted image with a different pixel type, you cannot
/// use this method and must instead use the `From`/`Into` trait instead.
///
/// That said, you can also use the `From`/`Into` trait regardless of the pixel type.
#[must_use]
pub fn convert<T: Pixel + From<P>>(self) -> Image<T> {
self.map_pixels(T::from)
}
/// Sets the encoding format of this image. Note that when saving the file,
/// an encoding format will still have to be explicitly specified.
/// This is more or less image metadata.
pub fn set_format(&mut self, format: ImageFormat) {
self.format = format;
}
/// Crops this image in place to the given bounding box.
///
/// # Panics
/// * The width or height of the bounding box is less than 1.
pub fn crop(&mut self, x1: u32, y1: u32, x2: u32, y2: u32) {
self.data = self
.pixels()
.skip(y1 as usize)
.zip(y1..y2)
.flat_map(|(row, _)| &row[x1 as usize..x2 as usize])
.copied()
.collect();
self.width = NonZeroU32::new(x2 - x1).unwrap();
self.height = NonZeroU32::new(y2 - y1).unwrap();
}
/// Takes this image and crops it to the given box. Useful for method chaining.
#[must_use]
pub fn cropped(mut self, x1: u32, y1: u32, x2: u32, y2: u32) -> Self {
self.crop(x1, y1, x2, y2);
self
}
/// Mirrors, or flips this image horizontally (about the y-axis) in place.
pub fn mirror(&mut self) {
let width = self.width();
self.data
.chunks_exact_mut(width as usize)
.for_each(<[P]>::reverse);
}
/// Takes this image and flips it horizontally (about the y-axis). Useful for method chaining.
#[must_use]
pub fn mirrored(mut self) -> Self {
self.mirror();
self
}
/// Flips this image vertically (about the x-axis) in place.
pub fn flip(&mut self) {
self.mirror();
self.rotate_180();
}
/// Takes this image and flips it vertically, or about the x-axis. Useful for method chaining.
#[must_use]
pub fn flipped(mut self) -> Self {
self.flip();
self
}
fn rotate_iterator(&self) -> impl Iterator<Item = P> + DoubleEndedIterator + '_ {
(0..self.width() as usize).flat_map(move |i| {
(0..self.height() as usize)
.map(move |j| self.data[j * self.width() as usize + i])
.rev()
})
}
/// Rotates this image by 90 degrees clockwise, or 270 degrees counterclockwise, in place.
///
/// # See Also
/// - [`Self::rotate`] for a version that can take any arbitrary amount of degrees
/// - [`Self::rotated`] for the above method which does operate in-place - useful for method
/// chaining
pub fn rotate_90(&mut self) {
self.data = self.rotate_iterator().collect();
std::mem::swap(&mut self.width, &mut self.height);
}
/// Rotates this image by 180 degrees in place.
///
/// # See Also
/// - [`Self::rotate`] for a version that can take any arbitrary amount of degrees
/// - [`Self::rotated`] for the above method which does operate in-place - useful for method
/// chaining
pub fn rotate_180(&mut self) {
self.data.reverse();
}
/// Rotates this image by 270 degrees clockwise, or 90 degrees counterclockwise, in place.
///
/// # See Also
/// - [`Self::rotate`] for a version that can take any arbitrary amount of degrees
/// - [`Self::rotated`] for the above method which does operate in-place - useful for method
/// chaining
pub fn rotate_270(&mut self) {
self.data = self.rotate_iterator().rev().collect();
std::mem::swap(&mut self.width, &mut self.height);
}
/// Rotates this image in place about its center. There are optimized rotating algorithms for
/// 90, 180, and 270 degree rotations (clockwise).
///
/// As mentioned, the argument is specified in degrees.
///
/// # See Also
/// - [`Self::rotated`] for this method which does operate in-place - useful for method chaining
pub fn rotate(&mut self, mut degrees: i32) {
degrees %= 360;
match degrees {
0 => (),
90 => self.rotate_90(),
180 => self.rotate_180(),
270 => self.rotate_270(),
_ => unimplemented!(
"currently only rotations of 0, 90, 180, and 270 degrees are supported",
),
}
}
/// Takes the image and rotates it by the specified amount of degrees about its center. Useful
/// for method chaining. There are optimized rotating algorithms for 90, 180, and 270 degree
/// rotations.
#[must_use]
pub fn rotated(mut self, degrees: i32) -> Self {
self.rotate(degrees);
self
}
/// Resizes this image in place to the given dimensions using the given resizing algorithm
/// in place.
///
/// `width` and `height` must be greater than 0, otherwise this method will panic. You should
/// validate user input before calling this method.
///
/// # Panics
/// * `width` or `height` is zero.
///
/// # Example
/// ```
/// # use ril::prelude::*;
/// # fn main() -> ril::Result<()> {
/// let mut image = Image::new(256, 256, Rgb::white());
/// assert_eq!(image.dimensions(), (256, 256));
///
/// image.resize(64, 64, ResizeAlgorithm::Lanczos3);
/// assert_eq!(image.dimensions(), (64, 64));
/// # Ok(())
/// # }
/// ```
#[cfg(feature = "resize")]
pub fn resize(&mut self, width: u32, height: u32, algorithm: ResizeAlgorithm) {
assert_nonzero!(width, height);
let width = NonZeroU32::new(width).unwrap();
let height = NonZeroU32::new(height).unwrap();
self.data = crate::resize::resize(
&self.data,
self.width,
self.height,
width,
height,
algorithm,
);
self.width = width;
self.height = height;
}
/// Takes this image and resizes this image to the given dimensions using the given
/// resizing algorithm. Useful for method chaining.
///
/// `width` and `height` must be greater than 0, otherwise this method will panic. You should
/// validate user input before calling this method.
///
/// # Panics
/// * `width` or `height` is zero.
///
/// # See Also
/// * [`Self::resize`] for a version that operates in-place
#[must_use]
#[cfg(feature = "resize")]
pub fn resized(mut self, width: u32, height: u32, algorithm: ResizeAlgorithm) -> Self {
self.resize(width, height, algorithm);
self
}
/// Draws an object or shape onto this image.
///
/// # Example
/// ```
/// # use ril::prelude::*;
/// # fn main() -> ril::Result<()> {
/// let mut image = Image::new(256, 256, Rgb::white());
/// let rectangle = Rectangle::new()
/// .with_position(64, 64)
/// .with_size(128, 128)
/// .with_fill(Rgb::black());
///
/// image.draw(&rectangle);
/// # Ok(())
/// # }
/// ```
pub fn draw(&mut self, entity: &impl Draw<P>) {
entity.draw(self);
}
/// Takes this image, draws the given object or shape onto it, and returns it.
/// Useful for method chaining and drawing multiple objects at once.
///
/// # See Also
/// * [`Self::draw`] for a version that operates in-place
#[must_use]
pub fn with(mut self, entity: &impl Draw<P>) -> Self {
self.draw(entity);
self
}
/// Pastes the given image onto this image at the given x and y coordinates.
/// This is a shorthand for using the [`draw`] method with [`Paste`].
///
/// # Example
/// ```no_run
/// # use ril::prelude::*;
/// # fn main() -> ril::Result<()> {
/// let mut image = Image::new(256, 256, Rgb::white());
/// let overlay_image = Image::open("overlay.png")?;
///
/// image.paste(64, 64, &overlay_image);
/// # Ok(())
/// # }
/// ```
pub fn paste(&mut self, x: u32, y: u32, image: &Self) {
self.draw(&crate::Paste::new(image).with_position(x, y));
}
/// Pastes the given image onto this image at the given x and y coordinates,
/// masked with the given masking image.
///
/// Currently, only [`BitPixel`] images are supported for the masking image.
///
/// This is a shorthand for using the [`draw`] method with [`Paste`].
///
/// # Example
/// ```no_run
/// # use ril::prelude::*;
/// # fn main() -> ril::Result<()> {
/// let mut image = Image::new(256, 256, Rgb::white());
/// let overlay_image = Image::open("overlay.png")?;
///
/// let (w, h) = overlay_image.dimensions();
/// let mut mask = Image::new(w, h, BitPixel::off());
/// mask.draw(&Ellipse::from_bounding_box(0, 0, w, h).with_fill(BitPixel::on()));
///
/// image.paste_with_mask(64, 64, &overlay_image, &mask);
/// # Ok(())
/// # }
/// ```
pub fn paste_with_mask(&mut self, x: u32, y: u32, image: &Self, mask: &Image<BitPixel>) {
self.draw(&crate::Paste::new(image).with_position(x, y).with_mask(mask));
}
/// Masks the alpha values of this image with the luminance values of the given single-channel
/// [`L`] image.
///
/// If you want to mask using the alpha values of the image instead of providing an [`L`] image,
/// you can split the bands of the image and extract the alpha band.
///
/// This masking image must have the same dimensions as this image. If it doesn't, you will
/// receive a panic.
///
/// # Panics
/// * The masking image has different dimensions from this image.
pub fn mask_alpha(&mut self, mask: &Image<L>)
where
P: Alpha,
{
assert_eq!(
self.dimensions(),
mask.dimensions(),
"Masking image with dimensions {:?} must have the \
same dimensions as this image with dimensions {:?}",
mask.dimensions(),
self.dimensions()
);
self.data
.iter_mut()
.zip(mask.data.iter())
.for_each(|(pixel, mask)| {
*pixel = pixel.with_alpha(mask.value());
});
}
/// Returns the palette associated with this image as a slice.
/// If there is no palette, this returns `None`.
#[must_use]
pub fn palette(&self) -> Option<&[P::Color]> {
self.palette.as_deref()
}
/// Returns the palette associated with this image as a mutable slice.
/// If there is no palette, this returns `None`.
#[must_use]
pub fn palette_mut(&mut self) -> Option<&mut [P::Color]> {
self.palette.as_deref_mut()
}
/// Returns the palette associated with this image as a slice. You must uphold the guarantee
/// that the image is paletted, otherwise this will result in undefined behaviour.
///
/// # Safety
/// * The image must always be paletted.
///
/// # See Also
/// * [`Self::palette`] - A safe, checked alternative to this method.
#[must_use]
pub unsafe fn palette_unchecked(&self) -> &[P::Color] {
self.palette.as_ref().unwrap_unchecked()
}
/// Returns the palette associated with this image as a mutable slice. You must uphold the
/// guarantee that the image is paletted, otherwise this will result in undefined behaviour.
///
/// # Safety
/// * The image must always be paletted.
///
/// # See Also
/// * [`Self::palette_mut`] - A safe, checked alternative to this method.
#[must_use]
pub unsafe fn palette_mut_unchecked(&mut self) -> &mut [P::Color] {
self.palette.as_mut().unwrap_unchecked()
}
/// Maps the palette of this image using the given function. If this image has no palette,
/// this will do nothing.
///
/// # Panics
/// * Safe conversion of palette references failed.
pub fn map_palette<'a, U, F, C: TrueColor>(self, mut f: F) -> Image<U>
where
Self: 'a,
P: Paletted<'a>,
U: Paletted<'a> + Pixel<Subpixel = P::Subpixel, Color = C>,
F: FnMut(P::Color) -> C,
{
let palette = self.palette.map(|palette| {
palette
.iter()
.map(|p| f(*p))
.collect::<Vec<_>>()
.into_boxed_slice()
});
Image {
width: self.width,
height: self.height,
data: self
.data
.into_iter()
.map(|p| {
U::from_raw_parts_paletted(
U::COLOR_TYPE,
U::BIT_DEPTH,
&[p.palette_index().into() as u8],
palette.as_deref(),
)
.expect("could not perform safe conversion of palette references")
})
.collect(),
format: self.format,
overlay: self.overlay,
palette,
}
}
/// Takes this image and flattens this paletted image into an unpaletted image. This is similar
/// to [`Self::convert`] but the output type is automatically resolved.
#[must_use = "the image is consumed by this method and returns a new image"]
pub fn flatten_palette<'a>(self) -> Image<P::Color>
where
Self: 'a,
P: Paletted<'a>,
{
self.map_pixels(|pixel| pixel.color())
}
/// Quantizes this image using its colors and turns it into its paletted counterpart.
/// This currently only works with 8-bit palettes.
///
/// This is similar to [`Self::convert`] but the output type is automatically resolved.
/// This is also the inverse conversion of [`Self::flatten_palette`].
///
/// # Errors
/// * The palette could not be created.
///
/// # See Also
/// * [`Quantizer`] - Implementation of the core quantizer. Use this for more fine-grained
/// control over the quantization process, such as adjusting the quantization speed.
#[must_use = "the image is consumed by this method and returns a new image"]
pub fn quantize<'p, T>(self, palette_size: u8) -> Image<T>
where
Self: 'p,
P: TrueColor,
T: Pixel<Color = P> + Paletted<'p, Subpixel = u8>,
{
let width = self.width();
let (palette, pixels) = crate::quantize::Quantizer::new()
.with_palette_size(palette_size as usize)
.quantize(self.data)
.expect("unable to quantize image");
Image::from_paletted_pixels(width, palette, pixels)
}
}
impl<'a> From<Image<PalettedRgb<'a>>> for Image<PalettedRgba<'a>> {
fn from(image: Image<PalettedRgb<'a>>) -> Self {
image.map_palette(Into::into)
}
}
impl<'a> From<Image<PalettedRgba<'a>>> for Image<PalettedRgb<'a>> {
fn from(image: Image<PalettedRgba<'a>>) -> Self {
image.map_palette(Into::into)
}
}
macro_rules! impl_cast_quantize {
($t:ty: $p:ty) => {
impl From<Image<$t>> for Image<$p> {
fn from(image: Image<$t>) -> Self {
let width = image.width();
let (palette, pixels) = crate::quantize::Quantizer::new()
.with_palette_size(image.data.len())
.quantize(image.data)
.expect("unable to quantize image");
Image::from_paletted_pixels(width, palette, pixels)
}
}
};
}
impl_cast_quantize!(Rgb: PalettedRgb<'_>);
impl_cast_quantize!(Rgba: PalettedRgba<'_>);
macro_rules! impl_cast {
($t:ty: $($f:ty)+) => {
$(
impl From<Image<$f>> for Image<$t> {
fn from(f: Image<$f>) -> Self {
f.map_pixels(<$t>::from)
}
}
)+
};
}
impl_cast!(BitPixel: L Rgb Rgba Dynamic PalettedRgb<'_> PalettedRgba<'_>);
impl_cast!(L: BitPixel Rgb Rgba Dynamic PalettedRgb<'_> PalettedRgba<'_>);
impl_cast!(Rgb: BitPixel L Rgba Dynamic PalettedRgb<'_> PalettedRgba<'_>);
impl_cast!(Rgba: BitPixel L Rgb Dynamic PalettedRgb<'_> PalettedRgba<'_>);
impl_cast!(Dynamic: BitPixel L Rgb Rgba PalettedRgb<'_> PalettedRgba<'_>);
/// Represents an image with multiple channels, called bands.
///
/// Each band should be represented as a separate [`Image`] with [`L`] or [`BitPixel`] pixels.
pub trait Banded<T> {
/// Takes this image and returns its bands.
fn bands(&self) -> T;
/// Creates a new image from the given bands.
fn from_bands(bands: T) -> Self;
}
type Band = Image<L>;
macro_rules! map_idx {
($image:expr, $idx:expr) => {{
use $crate::{Image, L};
Image {
width: $image.width,
height: $image.height,
data: $image.data.iter().map(|p| L(p.as_bytes()[$idx])).collect(),
format: $image.format,
overlay: $image.overlay,
palette: None,
}
}};
}
macro_rules! extract_bands {
($image:expr; $($idx:literal)+) => {{
($(map_idx!($image, $idx)),+)
}};
}
macro_rules! validate_dimensions {
($target:ident, $($others:ident),+) => {{
$(
assert_eq!(
$target.dimensions(),
$others.dimensions(),
"bands have different dimensions: {} has dimensions of {:?}, which is different \
from {} which has dimenesions of {:?}",
stringify!($target),
$target.dimensions(),
stringify!($others),
$others.dimensions()
);
)+
}};
}
impl Banded<(Band, Band, Band)> for Image<Rgb> {
fn bands(&self) -> (Band, Band, Band) {
extract_bands!(self; 0 1 2)
}
fn from_bands((r, g, b): (Band, Band, Band)) -> Self {
validate_dimensions!(r, g, b);
r.map_data(|data| {
data.into_iter()
.zip(g.data.into_iter())
.zip(b.data.into_iter())
.map(|((L(r), L(g)), L(b))| Rgb::new(r, g, b))
.collect()
})
}
}
impl Banded<(Band, Band, Band, Band)> for Image<Rgba> {
fn bands(&self) -> (Band, Band, Band, Band) {
extract_bands!(self; 0 1 2 3)
}
fn from_bands((r, g, b, a): (Band, Band, Band, Band)) -> Self {
validate_dimensions!(r, g, b, a);
r.map_data(|data| {
data.into_iter()
.zip(g.data.into_iter())
.zip(b.data.into_iter())
.zip(a.data.into_iter())
.map(|(((L(r), L(g)), L(b)), L(a))| Rgba::new(r, g, b, a))
.collect()
})
}
}
/// Represents the underlying encoding format of an image.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum ImageFormat {
/// No known encoding is known for the image.
///
/// This is usually because the image was created manually. See [`Image::set_format`]
/// to manually set the encoding format.
Unknown,
/// The image is encoded in the PNG format.
Png,
/// The image is encoded in the JPEG format.
Jpeg,
/// The image is encoded in the GIF format.
Gif,
/// The image is encoded in the BMP format.
Bmp,
/// The image is encoded in the TIFF format.
Tiff,
/// The image is encoded in the WebP format.
WebP,
}
impl Default for ImageFormat {
fn default() -> Self {
Self::Unknown
}
}
impl ImageFormat {
/// Returns whether the extension is unknown.
#[inline]
#[must_use]
pub fn is_unknown(&self) -> bool {
self == &Self::Unknown
}
/// Parses the given extension and returns the corresponding image format.
///
/// If the extension is an unknown extension, Ok([`ImageFormat::unknown`]) is returned.
///
/// If the extension is completely invalid and fails to be converted into a `&str`,
/// the [`Error::InvalidExtension`] error is returned.
///
/// # Errors
/// * The extension is completely invalid and failed to be converted into a `&str`.
pub fn from_extension(ext: impl AsRef<OsStr>) -> Result<Self> {
let extension = ext.as_ref().to_str();
Ok(
match extension
.ok_or_else(|| InvalidExtension(ext.as_ref().to_os_string()))?
.to_ascii_lowercase()
.as_str()
{
"png" | "apng" => Self::Png,
"jpg" | "jpeg" => Self::Jpeg,
"gif" => Self::Gif,
"bmp" => Self::Bmp,
"tiff" => Self::Tiff,
"webp" => Self::WebP,
_ => Self::Unknown,
},
)
}
/// Returns the format specified by the given path.
///
/// This uses [`ImageFormat::from_extension`] to parse the extension.
///
/// This resolves via the extension of the path. See [`ImageFormat::infer_encoding`] for an
/// implementation that can resolve the format from the data.
///
/// # Errors
/// * No extension can be resolved from the path.
pub fn from_path(path: impl AsRef<Path>) -> Result<Self> {
path.as_ref()
.extension()
.ok_or_else(|| InvalidExtension(path.as_ref().into()))
.and_then(Self::from_extension)
}
/// Returns the format specified by the given MIME type.
pub fn from_mime_type(mime: impl AsRef<str>) -> Self {
let mime = mime.as_ref();
match mime {
"image/png" => Self::Png,
"image/jpeg" => Self::Jpeg,
"image/gif" => Self::Gif,
"image/bmp" => Self::Bmp,
"image/tiff" => Self::Tiff,
"image/webp" => Self::WebP,
_ => Self::Unknown,
}
}
/// Infers the encoding format from the given data via a byte stream.
#[must_use]
pub fn infer_encoding(sample: &[u8]) -> Self {
if sample.starts_with(b"\x89PNG\x0D\x0A\x1A\x0A") {
Self::Png
} else if sample.starts_with(b"\xFF\xD8\xFF") {
Self::Jpeg
} else if sample.starts_with(b"GIF") {
Self::Gif
} else if sample.starts_with(b"BM") {
Self::Bmp
} else if sample.len() > 11 && &sample[8..12] == b"WEBP" {
Self::WebP
} else if (sample.starts_with(b"\x49\x49\x2A\0") || sample.starts_with(b"\x4D\x4D\0\x2A"))
&& sample[8] != 0x43
&& sample[9] != 0x52
{
Self::Tiff
} else {
Self::Unknown
}
}
/// Encodes the `Image` into raw bytes.
///
/// # Errors
/// * An error occured while encoding.
///
/// # Panics
/// * No encoder implementation is found for this image encoding.
#[cfg_attr(
not(any(feature = "png", feature = "gif", feature = "jpeg", feature = "webp")),
allow(unused_variables, unreachable_code)
)]
pub fn run_encoder<P: Pixel>(&self, image: &Image<P>, dest: &mut impl Write) -> Result<()> {
match self {
#[cfg(feature = "png")]
Self::Png => png::PngEncoder::new().encode(image, dest),
#[cfg(feature = "jpeg")]
Self::Jpeg => jpeg::JpegEncoder::new().encode(image, dest),
#[cfg(feature = "gif")]
Self::Gif => gif::GifEncoder::new().encode(image, dest),
#[cfg(feature = "webp")]
Self::WebP => webp::WebPEncoder::default().encode(image, dest),
_ => panic!(
"No encoder implementation is found for this image format. \
Did you forget to enable the feature?"
),
}
}
/// Encodes the `ImageSequence` into raw bytes. If the encoding does not supported image
/// sequences (or multi-frame images), it will only encode the first frame.
///
/// # Errors
/// * An error occured while encoding.
///
/// # Panics
/// * No encoder implementation is found for this image encoding.
#[cfg_attr(
not(any(feature = "png", feature = "gif", feature = "jpeg", feature = "webp")),
allow(unused_variables, unreachable_code)
)]
pub fn run_sequence_encoder<P: Pixel>(
&self,
seq: &crate::ImageSequence<P>,
dest: &mut impl Write,
) -> Result<()> {
match self {
#[cfg(feature = "png")]
Self::Png => png::PngEncoder::new().encode_sequence(seq, dest),
#[cfg(feature = "jpeg")]
Self::Jpeg => jpeg::JpegEncoder::new().encode_sequence(seq, dest),
#[cfg(feature = "gif")]
Self::Gif => gif::GifEncoder::new().encode_sequence(seq, dest),
#[cfg(feature = "webp")]
Self::WebP => webp::WebPEncoder::default().encode_sequence(seq, dest),
_ => panic!(
"No encoder implementation is found for this image format. \
Did you forget to enable the feature?"
),
}
}
/// Decodes the image data from into an image.
///
/// # Errors
/// * An error occured while decoding.
///
/// # Panics
/// * No decoder implementation is found for this image encoding.
#[cfg_attr(
not(any(feature = "png", feature = "gif", feature = "jpeg", feature = "webp")),
allow(unused_variables, unreachable_code)
)]
#[allow(clippy::needless_pass_by_value)] // would require a major refactor
pub fn run_decoder<P: Pixel>(&self, stream: impl Read) -> Result<Image<P>> {
match self {
#[cfg(feature = "png")]
Self::Png => png::PngDecoder::new().decode(stream),
#[cfg(feature = "jpeg")]
Self::Jpeg => jpeg::JpegDecoder::new().decode(stream),
#[cfg(feature = "gif")]
Self::Gif => gif::GifDecoder::new().decode(stream),
#[cfg(feature = "webp")]
Self::WebP => webp::WebPDecoder::default().decode(stream),
_ => panic!(
"No encoder implementation is found for this image format. \
Did you forget to enable the feature?"
),
}
}
/// Decodes the image sequence data into an image sequence.
///
/// # Errors
/// * An error occured while decoding.
///
/// # Panics
/// * No decoder implementation is found for this image encoding.
#[cfg_attr(
not(any(feature = "png", feature = "gif", feature = "jpeg", feature = "webp")),
allow(unused_variables, unreachable_code)
)]
#[allow(clippy::needless_pass_by_value)] // would require a major refactor
pub fn run_sequence_decoder<P: Pixel, R: Read>(
&self,
stream: R,
) -> Result<DynamicFrameIterator<P, R>> {
Ok(match self {
#[cfg(feature = "png")]
Self::Png => DynamicFrameIterator::Png(png::PngDecoder::new().decode_sequence(stream)?),
#[cfg(feature = "jpeg")]
Self::Jpeg => jpeg::JpegDecoder::new().decode_sequence(stream)?,
#[cfg(feature = "gif")]
Self::Gif => DynamicFrameIterator::Gif(gif::GifDecoder::new().decode_sequence(stream)?),
#[cfg(feature = "webp")]
Self::WebP => {
DynamicFrameIterator::WebP(webp::WebPDecoder::default().decode_sequence(stream)?)
}
_ => panic!(
"No encoder implementation is found for this image format. \
Did you forget to enable the feature?"
),
})
}
}
impl Display for ImageFormat {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"{}",
match self {
Self::Png => "png",
Self::Jpeg => "jpeg",
Self::Gif => "gif",
Self::Bmp => "bmp",
Self::Tiff => "tiff",
Self::WebP => "webp",
Self::Unknown => "",
}
)
}
}