2023-09-05 20:31:53 -05:00
|
|
|
//! # fimg
|
|
|
|
//!
|
|
|
|
//! Provides fast image operations, such as rotation, flipping, and overlaying.
|
2023-09-04 18:45:23 -05:00
|
|
|
#![feature(
|
|
|
|
slice_swap_unchecked,
|
2023-09-21 18:52:40 -05:00
|
|
|
stmt_expr_attributes,
|
2023-09-06 06:36:44 -05:00
|
|
|
generic_const_exprs,
|
2023-09-25 01:48:58 -05:00
|
|
|
vec_into_raw_parts,
|
2023-09-04 19:27:10 -05:00
|
|
|
slice_as_chunks,
|
2023-09-04 18:45:23 -05:00
|
|
|
unchecked_math,
|
|
|
|
portable_simd,
|
2023-09-06 06:36:44 -05:00
|
|
|
const_option,
|
2023-09-04 18:45:23 -05:00
|
|
|
array_chunks,
|
|
|
|
test
|
|
|
|
)]
|
|
|
|
#![warn(
|
2023-09-05 20:31:53 -05:00
|
|
|
clippy::missing_docs_in_private_items,
|
2023-09-04 18:45:23 -05:00
|
|
|
clippy::multiple_unsafe_ops_per_block,
|
2023-09-05 21:24:41 -05:00
|
|
|
clippy::undocumented_unsafe_blocks,
|
2023-09-04 18:45:23 -05:00
|
|
|
clippy::missing_const_for_fn,
|
|
|
|
clippy::missing_safety_doc,
|
|
|
|
unsafe_op_in_unsafe_fn,
|
|
|
|
clippy::dbg_macro,
|
2023-09-05 20:31:53 -05:00
|
|
|
missing_docs
|
2023-09-04 18:45:23 -05:00
|
|
|
)]
|
2023-09-06 06:36:44 -05:00
|
|
|
#![allow(clippy::zero_prefixed_literal, incomplete_features)]
|
2023-09-04 18:45:23 -05:00
|
|
|
|
|
|
|
use std::{num::NonZeroU32, slice::SliceIndex};
|
|
|
|
|
|
|
|
mod affine;
|
2023-09-05 23:21:32 -05:00
|
|
|
pub mod builder;
|
2023-09-25 01:48:58 -05:00
|
|
|
pub mod cloner;
|
2023-09-10 17:16:59 -05:00
|
|
|
mod drawing;
|
2023-09-04 18:45:23 -05:00
|
|
|
mod overlay;
|
2023-09-20 22:35:58 -05:00
|
|
|
pub mod scale;
|
2023-09-25 01:48:58 -05:00
|
|
|
use cloner::ImageCloner;
|
2023-09-25 04:49:49 -05:00
|
|
|
pub use overlay::{ClonerOverlay, ClonerOverlayAt, Overlay, OverlayAt};
|
2023-09-04 18:45:23 -05:00
|
|
|
|
2023-09-05 20:31:53 -05:00
|
|
|
/// like assert!(), but causes undefined behaviour at runtime when the condition is not met.
|
|
|
|
///
|
|
|
|
/// # Safety
|
2023-09-09 05:58:13 -05:00
|
|
|
///
|
2023-09-05 20:31:53 -05:00
|
|
|
/// UB if condition is false.
|
2023-09-04 18:45:23 -05:00
|
|
|
macro_rules! assert_unchecked {
|
|
|
|
($cond:expr) => {{
|
|
|
|
if !$cond {
|
|
|
|
#[cfg(debug_assertions)]
|
|
|
|
let _ = ::core::ptr::NonNull::<()>::dangling().as_ref(); // force unsafe wrapping block
|
|
|
|
#[cfg(debug_assertions)]
|
|
|
|
panic!("assertion failed: {} returned false", stringify!($cond));
|
|
|
|
#[cfg(not(debug_assertions))]
|
|
|
|
std::hint::unreachable_unchecked()
|
|
|
|
}
|
|
|
|
}};
|
|
|
|
}
|
|
|
|
use assert_unchecked;
|
|
|
|
|
2023-09-04 21:59:33 -05:00
|
|
|
impl Image<&[u8], 3> {
|
|
|
|
/// Repeat self till it fills a new image of size x, y
|
|
|
|
/// # Safety
|
|
|
|
///
|
|
|
|
/// UB if self's width is not a multiple of x, or self's height is not a multiple of y
|
|
|
|
pub unsafe fn repeated(&self, x: u32, y: u32) -> Image<Vec<u8>, 3> {
|
2023-09-04 18:45:23 -05:00
|
|
|
let mut img = Image::alloc(x, y); // could probably optimize this a ton but eh
|
|
|
|
for x in 0..(x / self.width()) {
|
|
|
|
for y in 0..(y / self.height()) {
|
|
|
|
let a: &mut Image<&mut [u8], 3> = &mut img.as_mut();
|
|
|
|
// SAFETY: caller upholds
|
|
|
|
unsafe { a.overlay_at(self, x * self.width(), y * self.height()) };
|
|
|
|
}
|
|
|
|
}
|
|
|
|
img
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-09-05 20:31:53 -05:00
|
|
|
/// calculates a column major index, with unchecked math
|
2023-09-04 18:45:23 -05:00
|
|
|
#[inline]
|
|
|
|
unsafe fn really_unsafe_index(x: u32, y: u32, w: u32) -> usize {
|
|
|
|
// y * w + x
|
2023-09-05 21:24:41 -05:00
|
|
|
// SAFETY: FIXME make safe math
|
2023-09-04 18:45:23 -05:00
|
|
|
let tmp = unsafe { (y as usize).unchecked_mul(w as usize) };
|
2023-09-05 21:24:41 -05:00
|
|
|
// SAFETY: FIXME make safe math
|
2023-09-04 18:45:23 -05:00
|
|
|
unsafe { tmp.unchecked_add(x as usize) }
|
|
|
|
}
|
|
|
|
|
2023-09-04 21:59:33 -05:00
|
|
|
/// A image with a variable number of channels, and a nonzero size.
|
2023-09-25 01:48:58 -05:00
|
|
|
#[derive(Debug, PartialEq, Eq)]
|
2023-09-04 18:45:23 -05:00
|
|
|
pub struct Image<T, const CHANNELS: usize> {
|
2023-09-04 21:59:33 -05:00
|
|
|
/// column order 2d slice/vec
|
2023-09-05 23:21:32 -05:00
|
|
|
buffer: T,
|
2023-09-04 21:59:33 -05:00
|
|
|
/// image horizontal size
|
2023-09-05 23:21:32 -05:00
|
|
|
width: NonZeroU32,
|
2023-09-04 21:59:33 -05:00
|
|
|
/// image vertical size
|
2023-09-05 23:21:32 -05:00
|
|
|
height: NonZeroU32,
|
2023-09-04 18:45:23 -05:00
|
|
|
}
|
|
|
|
|
2023-09-25 01:48:58 -05:00
|
|
|
impl<T: Clone, const CHANNELS: usize> Clone for Image<T, CHANNELS> {
|
|
|
|
/// Returns a duplicate of this image.
|
|
|
|
/// ```
|
|
|
|
/// # use fimg::Image;
|
|
|
|
/// # let i = Image::<Vec<_>, 1>::alloc(5,5);
|
|
|
|
/// let new_i = i.clone();
|
|
|
|
/// ```
|
|
|
|
/// If you find yourself in the pattern of
|
|
|
|
/// ```
|
|
|
|
/// # use fimg::Image;
|
|
|
|
/// # let i = Image::<Vec<_>, 1>::alloc(5,5);
|
|
|
|
/// let mut i = i.clone();
|
|
|
|
/// unsafe { i.rot_90() };
|
|
|
|
/// ```
|
|
|
|
/// STOP!
|
|
|
|
///
|
|
|
|
/// Instead use
|
|
|
|
/// ```
|
|
|
|
/// # use fimg::Image;
|
|
|
|
/// # let i = Image::<Vec<_>, 1>::alloc(5,5);
|
|
|
|
/// let i = unsafe { i.cloner().rot_90() };
|
|
|
|
/// ```
|
|
|
|
fn clone(&self) -> Self {
|
|
|
|
Self {
|
|
|
|
buffer: self.buffer.clone(),
|
|
|
|
width: self.width,
|
|
|
|
height: self.height,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-09-04 18:45:23 -05:00
|
|
|
impl<T, const CHANNELS: usize> Image<T, CHANNELS> {
|
|
|
|
#[inline]
|
2023-09-04 21:59:33 -05:00
|
|
|
/// get the height as a [`u32`]
|
2023-09-04 18:45:23 -05:00
|
|
|
pub fn height(&self) -> u32 {
|
|
|
|
self.height.into()
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
2023-09-04 21:59:33 -05:00
|
|
|
/// get the width as a [`u32`]
|
2023-09-04 18:45:23 -05:00
|
|
|
pub fn width(&self) -> u32 {
|
|
|
|
self.width.into()
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
2023-09-04 21:59:33 -05:00
|
|
|
/// create a new image
|
2023-09-05 23:21:32 -05:00
|
|
|
///
|
|
|
|
/// # Safety
|
|
|
|
///
|
|
|
|
/// does not check that buffer.len() == w * h * C
|
|
|
|
///
|
|
|
|
/// using this with invalid values may result in future UB
|
|
|
|
pub const unsafe fn new(width: NonZeroU32, height: NonZeroU32, buffer: T) -> Self {
|
2023-09-05 20:31:53 -05:00
|
|
|
Self {
|
2023-09-04 18:45:23 -05:00
|
|
|
buffer,
|
|
|
|
width,
|
|
|
|
height,
|
|
|
|
}
|
|
|
|
}
|
2023-09-05 23:21:32 -05:00
|
|
|
|
2023-09-06 06:49:51 -05:00
|
|
|
/// consumes the image, returning the image buffer
|
|
|
|
pub fn take_buffer(self) -> T {
|
|
|
|
self.buffer
|
|
|
|
}
|
|
|
|
|
2023-09-05 23:21:32 -05:00
|
|
|
/// returns a immutable reference to the backing buffer
|
|
|
|
pub const fn buffer(&self) -> &T {
|
|
|
|
&self.buffer
|
|
|
|
}
|
|
|
|
|
|
|
|
/// returns a mutable(!) reference to the backing buffer
|
|
|
|
///
|
|
|
|
/// # Safety
|
|
|
|
///
|
|
|
|
/// please do not change buffer size.
|
|
|
|
pub unsafe fn buffer_mut(&mut self) -> &mut T {
|
|
|
|
&mut self.buffer
|
|
|
|
}
|
2023-09-04 18:45:23 -05:00
|
|
|
}
|
|
|
|
|
2023-09-06 07:28:53 -05:00
|
|
|
impl<const CHANNELS: usize, T: Clone> Image<&[T], CHANNELS> {
|
|
|
|
/// Allocate a new `Image<Vec<T>>` from this imageref.
|
|
|
|
pub fn to_owned(&self) -> Image<Vec<T>, CHANNELS> {
|
|
|
|
// SAFETY: we have been constructed already, so must be valid
|
|
|
|
unsafe { Image::new(self.width, self.height, self.buffer.to_vec()) }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<const CHANNELS: usize, T: Clone> Image<&mut [T], CHANNELS> {
|
|
|
|
/// Allocate a new `Image<Vec<T>>` from this mutable imageref.
|
|
|
|
pub fn to_owned(&self) -> Image<Vec<T>, CHANNELS> {
|
|
|
|
// SAFETY: we have been constructed already, so must be valid
|
|
|
|
unsafe { Image::new(self.width, self.height, self.buffer.to_vec()) }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-09-25 01:48:58 -05:00
|
|
|
impl<const CHANNELS: usize> Copy for Image<&[u8], CHANNELS> {}
|
|
|
|
|
2023-09-04 18:45:23 -05:00
|
|
|
impl<const CHANNELS: usize> Image<&[u8], CHANNELS> {
|
|
|
|
#[inline]
|
|
|
|
#[must_use]
|
2023-09-04 21:59:33 -05:00
|
|
|
/// Copy this ref image
|
2023-09-04 18:45:23 -05:00
|
|
|
pub const fn copy(&self) -> Self {
|
|
|
|
Self {
|
|
|
|
width: self.width,
|
|
|
|
height: self.height,
|
|
|
|
buffer: self.buffer,
|
|
|
|
}
|
|
|
|
}
|
2023-09-06 06:36:44 -05:00
|
|
|
|
|
|
|
/// Create a new immutable image of width x, y.
|
|
|
|
///
|
|
|
|
/// # Panics
|
|
|
|
///
|
|
|
|
/// if width || height == 0
|
|
|
|
///
|
|
|
|
/// ```
|
|
|
|
/// # use fimg::Image;
|
|
|
|
/// let img = Image::make::<5, 5>();
|
|
|
|
/// # let img: Image<_, 4> = img;
|
|
|
|
/// ```
|
|
|
|
pub const fn make<'a, const WIDTH: u32, const HEIGHT: u32>() -> Image<&'a [u8], CHANNELS>
|
|
|
|
where
|
|
|
|
[(); CHANNELS * WIDTH as usize * HEIGHT as usize]: Sized,
|
|
|
|
{
|
|
|
|
Image {
|
|
|
|
width: NonZeroU32::new(WIDTH).expect("passed zero width to builder"),
|
|
|
|
height: NonZeroU32::new(HEIGHT).expect("passed zero height to builder"),
|
|
|
|
buffer: &[0; CHANNELS * WIDTH as usize * HEIGHT as usize],
|
|
|
|
}
|
|
|
|
}
|
2023-09-04 18:45:23 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
impl<T: std::ops::Deref<Target = [u8]>, const CHANNELS: usize> Image<T, CHANNELS> {
|
|
|
|
/// # Safety
|
|
|
|
///
|
|
|
|
/// - UB if x, y is out of bounds
|
|
|
|
/// - UB if buffer is too small
|
|
|
|
#[inline]
|
|
|
|
unsafe fn slice(&self, x: u32, y: u32) -> impl SliceIndex<[u8], Output = [u8]> {
|
|
|
|
debug_assert!(x < self.width(), "x out of bounds");
|
|
|
|
debug_assert!(y < self.height(), "y out of bounds");
|
2023-09-05 21:24:41 -05:00
|
|
|
// SAFETY: me when uncheck math: 😧
|
2023-09-04 18:45:23 -05:00
|
|
|
let index = unsafe { really_unsafe_index(x, y, self.width()) };
|
2023-09-05 21:24:41 -05:00
|
|
|
// SAFETY: 🧐 is unsound? 😖
|
2023-09-04 18:45:23 -05:00
|
|
|
let index = unsafe { index.unchecked_mul(CHANNELS) };
|
|
|
|
debug_assert!(self.buffer.len() > index);
|
2023-09-05 21:24:41 -05:00
|
|
|
// SAFETY: as long as the buffer isnt wrong, this is 😄
|
2023-09-04 18:45:23 -05:00
|
|
|
index..unsafe { index.unchecked_add(CHANNELS) }
|
|
|
|
}
|
|
|
|
|
2023-09-25 01:48:58 -05:00
|
|
|
/// Procure a [`ImageCloner`].
|
|
|
|
pub fn cloner(&self) -> ImageCloner<'_, CHANNELS> {
|
|
|
|
ImageCloner::from(self.as_ref())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Reference this image.
|
|
|
|
pub fn as_ref(&self) -> Image<&[u8], CHANNELS> {
|
|
|
|
// SAFETY: we got constructed okay, parameters must be valid
|
|
|
|
unsafe { Image::new(self.width, self.height, &*self.buffer) }
|
|
|
|
}
|
|
|
|
|
2023-09-04 18:45:23 -05:00
|
|
|
#[inline]
|
|
|
|
/// Returns a iterator over every pixel
|
2023-09-25 01:48:58 -05:00
|
|
|
pub fn chunked(&self) -> impl DoubleEndedIterator<Item = &[u8; CHANNELS]> {
|
2023-09-04 18:45:23 -05:00
|
|
|
// SAFETY: 0 sized images illegal
|
|
|
|
unsafe { assert_unchecked!(self.buffer.len() > CHANNELS) };
|
|
|
|
// SAFETY: no half pixels!
|
|
|
|
unsafe { assert_unchecked!(self.buffer.len() % CHANNELS == 0) };
|
|
|
|
self.buffer.array_chunks::<CHANNELS>()
|
|
|
|
}
|
|
|
|
|
2023-09-21 18:52:40 -05:00
|
|
|
#[inline]
|
|
|
|
/// Flatten the chunks of this image into a slice of slices.
|
|
|
|
pub fn flatten(&mut self) -> &[[u8; CHANNELS]] {
|
|
|
|
// SAFETY: buffer cannot have half pixels
|
|
|
|
unsafe { self.buffer.as_chunks_unchecked::<CHANNELS>() }
|
|
|
|
}
|
|
|
|
|
2023-09-04 18:45:23 -05:00
|
|
|
/// Return a pixel at (x, y).
|
|
|
|
/// # Safety
|
|
|
|
///
|
|
|
|
/// - UB if x, y is out of bounds
|
|
|
|
/// - UB if buffer is too small
|
|
|
|
#[inline]
|
|
|
|
pub unsafe fn pixel(&self, x: u32, y: u32) -> [u8; CHANNELS] {
|
2023-09-05 21:24:41 -05:00
|
|
|
// SAFETY: we have been told x, y is in bounds
|
2023-09-04 18:45:23 -05:00
|
|
|
let idx = unsafe { self.slice(x, y) };
|
2023-09-05 21:24:41 -05:00
|
|
|
// SAFETY: slice always returns a valid index
|
2023-09-04 18:45:23 -05:00
|
|
|
let ptr = unsafe { self.buffer.get_unchecked(idx).as_ptr().cast() };
|
2023-09-05 21:24:41 -05:00
|
|
|
// SAFETY: slice always returns a length of `CHANNELS`, so we `cast()` it for convenience.
|
2023-09-04 18:45:23 -05:00
|
|
|
unsafe { *ptr }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<T: std::ops::DerefMut<Target = [u8]>, const CHANNELS: usize> Image<T, CHANNELS> {
|
|
|
|
/// Return a mutable reference to a pixel at (x, y).
|
|
|
|
/// # Safety
|
|
|
|
///
|
|
|
|
/// - UB if x, y is out of bounds
|
|
|
|
/// - UB if buffer is too small
|
|
|
|
#[inline]
|
|
|
|
pub unsafe fn pixel_mut(&mut self, x: u32, y: u32) -> &mut [u8] {
|
2023-09-05 21:24:41 -05:00
|
|
|
// SAFETY: we have been told x, y is in bounds.
|
2023-09-04 18:45:23 -05:00
|
|
|
let idx = unsafe { self.slice(x, y) };
|
2023-09-05 21:24:41 -05:00
|
|
|
// SAFETY: slice should always return a valid index
|
2023-09-04 18:45:23 -05:00
|
|
|
unsafe { self.buffer.get_unchecked_mut(idx) }
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
/// Returns a iterator over every pixel, mutably
|
|
|
|
pub fn chunked_mut(&mut self) -> impl Iterator<Item = &mut [u8; CHANNELS]> {
|
|
|
|
// SAFETY: 0 sized images are not allowed
|
|
|
|
unsafe { assert_unchecked!(self.buffer.len() > CHANNELS) };
|
|
|
|
// SAFETY: buffer cannot have half pixels
|
|
|
|
unsafe { assert_unchecked!(self.buffer.len() % CHANNELS == 0) };
|
|
|
|
self.buffer.array_chunks_mut::<CHANNELS>()
|
|
|
|
}
|
|
|
|
|
2023-09-25 01:48:58 -05:00
|
|
|
/// Create a mutref to this image
|
|
|
|
pub fn as_mut(&mut self) -> Image<&mut [u8], CHANNELS> {
|
|
|
|
// SAFETY: construction went okay
|
|
|
|
unsafe { Image::new(self.width, self.height, &mut self.buffer) }
|
|
|
|
}
|
|
|
|
|
2023-09-21 18:52:40 -05:00
|
|
|
#[inline]
|
|
|
|
/// Flatten the chunks of this image into a mutable slice of slices.
|
|
|
|
pub fn flatten_mut(&mut self) -> &mut [[u8; CHANNELS]] {
|
|
|
|
// SAFETY: buffer cannot have half pixels
|
|
|
|
unsafe { self.buffer.as_chunks_unchecked_mut::<CHANNELS>() }
|
|
|
|
}
|
|
|
|
|
2023-09-04 18:45:23 -05:00
|
|
|
/// Set the pixel at x, y
|
|
|
|
///
|
|
|
|
/// # Safety
|
|
|
|
///
|
|
|
|
/// UB if x, y is out of bounds.
|
|
|
|
#[inline]
|
|
|
|
pub unsafe fn set_pixel(&mut self, x: u32, y: u32, px: [u8; CHANNELS]) {
|
|
|
|
// SAFETY: Caller says that x, y is in bounds
|
|
|
|
let out = unsafe { self.pixel_mut(x, y) };
|
|
|
|
// SAFETY: px must be CHANNELS long
|
|
|
|
unsafe { std::ptr::copy_nonoverlapping(px.as_ptr(), out.as_mut_ptr(), CHANNELS) };
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-09-04 21:59:33 -05:00
|
|
|
impl<const CHANNELS: usize> Image<&mut [u8], CHANNELS> {
|
|
|
|
/// Copy this ref image
|
|
|
|
pub fn copy(&mut self) -> Image<&mut [u8], CHANNELS> {
|
2023-09-05 23:21:32 -05:00
|
|
|
#[allow(clippy::undocumented_unsafe_blocks)]
|
|
|
|
unsafe {
|
|
|
|
Image::new(self.width, self.height, self.buffer)
|
|
|
|
}
|
2023-09-04 18:45:23 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<const CHANNELS: usize> Image<Vec<u8>, CHANNELS> {
|
|
|
|
/// Allocates a new image
|
|
|
|
///
|
|
|
|
/// # Panics
|
|
|
|
///
|
|
|
|
/// if width || height == 0
|
|
|
|
#[must_use]
|
|
|
|
pub fn alloc(width: u32, height: u32) -> Self {
|
2023-09-05 20:31:53 -05:00
|
|
|
Self {
|
2023-09-04 18:45:23 -05:00
|
|
|
width: width.try_into().unwrap(),
|
|
|
|
height: height.try_into().unwrap(),
|
|
|
|
buffer: vec![0; CHANNELS * width as usize * height as usize],
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-09-05 20:31:53 -05:00
|
|
|
|
|
|
|
/// helper macro for defining the save() method.
|
2023-09-04 18:45:23 -05:00
|
|
|
macro_rules! save {
|
|
|
|
($channels:literal == $clr:ident ($clrhuman:literal)) => {
|
2023-09-04 21:58:07 -05:00
|
|
|
impl Image<Vec<u8>, $channels> {
|
2023-09-05 00:37:04 -05:00
|
|
|
#[cfg(feature = "save")]
|
2023-09-04 21:58:07 -05:00
|
|
|
#[doc = "Save this "]
|
|
|
|
#[doc = $clrhuman]
|
|
|
|
#[doc = " image."]
|
|
|
|
pub fn save(&self, f: impl AsRef<std::path::Path>) {
|
|
|
|
self.as_ref().save(f)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-09-04 18:45:23 -05:00
|
|
|
impl Image<&[u8], $channels> {
|
|
|
|
#[cfg(feature = "save")]
|
|
|
|
#[doc = "Save this "]
|
|
|
|
#[doc = $clrhuman]
|
|
|
|
#[doc = " image."]
|
|
|
|
pub fn save(&self, f: impl AsRef<std::path::Path>) {
|
|
|
|
let p = std::fs::File::create(f).unwrap();
|
|
|
|
let w = &mut std::io::BufWriter::new(p);
|
|
|
|
let mut enc = png::Encoder::new(w, self.width(), self.height());
|
|
|
|
enc.set_color(png::ColorType::$clr);
|
|
|
|
enc.set_depth(png::BitDepth::Eight);
|
|
|
|
enc.set_source_gamma(png::ScaledFloat::new(1.0 / 2.2));
|
|
|
|
enc.set_source_chromaticities(png::SourceChromaticities::new(
|
|
|
|
(0.31270, 0.32900),
|
|
|
|
(0.64000, 0.33000),
|
|
|
|
(0.30000, 0.60000),
|
|
|
|
(0.15000, 0.06000),
|
|
|
|
));
|
|
|
|
let mut writer = enc.write_header().unwrap();
|
|
|
|
writer.write_image_data(self.buffer).unwrap();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2023-09-20 22:35:58 -05:00
|
|
|
impl<const CHANNELS: usize> Image<Vec<u8>, CHANNELS> {
|
|
|
|
#[cfg(feature = "save")]
|
|
|
|
/// Open a PNG image
|
|
|
|
pub fn open(f: impl AsRef<std::path::Path>) -> Self {
|
|
|
|
let p = std::fs::File::open(f).unwrap();
|
|
|
|
let r = std::io::BufReader::new(p);
|
|
|
|
let dec = png::Decoder::new(r);
|
|
|
|
let mut reader = dec.read_info().unwrap();
|
|
|
|
let mut buf = vec![0; reader.output_buffer_size()];
|
|
|
|
let info = reader.next_frame(&mut buf).unwrap();
|
|
|
|
use png::ColorType::*;
|
|
|
|
match info.color_type {
|
|
|
|
Indexed | Grayscale => {
|
|
|
|
assert_eq!(CHANNELS, 1, "indexed | grayscale requires one channel")
|
|
|
|
}
|
|
|
|
Rgb => assert_eq!(CHANNELS, 3, "rgb requires three channels"),
|
|
|
|
Rgba => assert_eq!(CHANNELS, 4, "rgba requires four channels"),
|
|
|
|
GrayscaleAlpha => assert_eq!(CHANNELS, 2, "ya requires two channels"),
|
|
|
|
}
|
|
|
|
Self::build(info.width, info.height).buf(buf)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-09-04 18:45:23 -05:00
|
|
|
save!(3 == Rgb("RGB"));
|
|
|
|
save!(4 == Rgba("RGBA"));
|
|
|
|
save!(2 == GrayscaleAlpha("YA"));
|
|
|
|
save!(1 == Grayscale("Y"));
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
macro_rules! img {
|
2023-09-05 23:21:32 -05:00
|
|
|
[[$($v:literal),+] [$($v2:literal),+]] => {
|
|
|
|
Image::<Vec<u8>, 1>::build(2,2).buf(vec![$($v,)+ $($v2,)+])
|
|
|
|
}
|
2023-09-04 18:45:23 -05:00
|
|
|
}
|
|
|
|
#[cfg(test)]
|
|
|
|
use img;
|
2023-09-09 05:54:54 -05:00
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
|
|
|
#[test]
|
|
|
|
fn repeat() {
|
|
|
|
let x: Image<&[u8], 3> = Image::build(8, 8).buf(include_bytes!("../benches/3_8x8.imgbuf"));
|
|
|
|
unsafe { x.repeated(128, 128) }; // repeat 16 times
|
|
|
|
}
|
|
|
|
}
|