fast image operations
bendn 2023-10-31
parent bb33b6c · commit 3e01a15
-rw-r--r--Cargo.toml2
-rw-r--r--src/convert.rs64
-rw-r--r--src/dyn/affine.rs38
-rw-r--r--src/dyn/convert.rs110
-rw-r--r--src/dyn/mod.rs121
-rw-r--r--src/dyn/scale.rs10
-rw-r--r--src/lib.rs72
-rw-r--r--src/pixels/convert.rs97
-rw-r--r--src/pixels/mod.rs3
-rw-r--r--src/pixels/utility.rs19
-rw-r--r--src/pixels/wam.rs2
11 files changed, 518 insertions, 20 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 14421ed..f84495a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "fimg"
-version = "0.4.21"
+version = "0.4.22"
authors = ["bend-n <[email protected]>"]
license = "MIT"
edition = "2021"
diff --git a/src/convert.rs b/src/convert.rs
new file mode 100644
index 0000000..5704f26
--- /dev/null
+++ b/src/convert.rs
@@ -0,0 +1,64 @@
+//! define From's for images.
+//! these conversions are defined by [`PFrom`].
+use crate::{pixels::convert::PFrom, Image};
+
+fn map<const A: usize, const B: usize>(image: Image<&[u8], A>) -> Image<Box<[u8]>, B>
+where
+ [u8; B]: PFrom<A>,
+{
+ let buffer = image
+ .chunked()
+ .copied()
+ .flat_map(<[u8; B] as PFrom<A>>::pfrom)
+ .collect::<Vec<_>>()
+ .into();
+ // SAFETY: ctor
+ unsafe { Image::new(image.width, image.height, buffer) }
+}
+
+macro_rules! convert {
+ ($a:literal => $b:literal) => {
+ impl From<Image<&[u8], $b>> for Image<Box<[u8]>, $a> {
+ fn from(value: Image<&[u8], $b>) -> Self {
+ map(value)
+ }
+ }
+ };
+}
+
+macro_rules! cv {
+ [$($n:literal),+] => {
+ $(convert!($n => 1);
+ convert!($n => 2);
+ convert!($n => 3);
+ convert!($n => 4);)+
+ };
+}
+
+cv![1, 2, 3, 4];
+
+macro_rules! boxconv {
+ ($a:literal => $b: literal) => {
+ impl From<Image<Box<[u8]>, $b>> for Image<Box<[u8]>, $a> {
+ fn from(value: Image<Box<[u8]>, $b>) -> Self {
+ value.as_ref().into()
+ }
+ }
+ };
+}
+
+boxconv!(1 => 2);
+boxconv!(1 => 3);
+boxconv!(1 => 4);
+
+boxconv!(2 => 1);
+boxconv!(2 => 3);
+boxconv!(2 => 4);
+
+boxconv!(3 => 1);
+boxconv!(3 => 2);
+boxconv!(3 => 4);
+
+boxconv!(4 => 1);
+boxconv!(4 => 2);
+boxconv!(4 => 3);
diff --git a/src/dyn/affine.rs b/src/dyn/affine.rs
new file mode 100644
index 0000000..2c425c0
--- /dev/null
+++ b/src/dyn/affine.rs
@@ -0,0 +1,38 @@
+use super::{e, DynImage};
+
+impl<T: AsMut<[u8]> + AsRef<[u8]>> DynImage<T> {
+ /// Rotate this image 90 degrees clockwise.
+ ///
+ /// # Safety
+ ///
+ /// UB if this image is not square
+ pub unsafe fn rot_90(&mut self) {
+ // SAFETY: caller guarantees
+ unsafe { e!(self, |i| i.rot_90()) }
+ }
+
+ /// Rotate this image 180 degrees clockwise.
+ pub fn rot_180(&mut self) {
+ e!(self, |i| i.rot_180())
+ }
+
+ /// Rotate this image 270 degrees clockwise.
+ ///
+ /// # Safety
+ ///
+ /// UB if this image is not square
+ pub unsafe fn rot_270(&mut self) {
+ // SAFETY: caller guarantees
+ unsafe { e!(self, |i| i.rot_270()) }
+ }
+
+ /// Flip this image horizontally.
+ pub fn flip_h(&mut self) {
+ e!(self, |i| i.flip_h())
+ }
+
+ /// Flip this image vertically.
+ pub fn flip_v(&mut self) {
+ e!(self, |i| i.flip_v())
+ }
+}
diff --git a/src/dyn/convert.rs b/src/dyn/convert.rs
new file mode 100644
index 0000000..146c73e
--- /dev/null
+++ b/src/dyn/convert.rs
@@ -0,0 +1,110 @@
+#![allow(clippy::useless_conversion)]
+use super::{e, DynImage, Image};
+
+impl From<DynImage<Box<[u8]>>> for Image<Box<[u8]>, 1> {
+ fn from(value: DynImage<Box<[u8]>>) -> Self {
+ e!(value, |i| i.into())
+ }
+}
+
+impl From<DynImage<Box<[u8]>>> for Image<Box<[u8]>, 2> {
+ fn from(value: DynImage<Box<[u8]>>) -> Self {
+ e!(value, |i| i.into())
+ }
+}
+impl From<DynImage<Box<[u8]>>> for Image<Box<[u8]>, 3> {
+ fn from(value: DynImage<Box<[u8]>>) -> Self {
+ e!(value, |i| i.into())
+ }
+}
+impl From<DynImage<Box<[u8]>>> for Image<Box<[u8]>, 4> {
+ fn from(value: DynImage<Box<[u8]>>) -> Self {
+ e!(value, |i| i.into())
+ }
+}
+
+impl<T> DynImage<T> {
+ /// Gets out the Y image, if its there, else returning self.
+ ///
+ /// If you want to convert, see [`DynImage::to_y`].
+ pub fn get_y(self) -> Result<Image<T, 1>, Self> {
+ match self {
+ Self::Y(i) => Ok(i),
+ _ => Err(self),
+ }
+ }
+
+ /// Gets out the YA image, if its there, else returning self.
+ ///
+ /// If you want to convert, see [`DynImage::to_ya`].
+ pub fn get_ya(self) -> Result<Image<T, 2>, Self> {
+ match self {
+ Self::Ya(i) => Ok(i),
+ _ => Err(self),
+ }
+ }
+
+ /// Gets out the RGB image, if its there, else returning self.
+ ///
+ /// If you want to convert, see [`DynImage::to_rgb`].
+ pub fn get_rgb(self) -> Result<Image<T, 3>, Self> {
+ match self {
+ Self::Rgb(i) => Ok(i),
+ _ => Err(self),
+ }
+ }
+
+ /// Gets out the RGBA image, if its there, else returning self.
+ ///
+ /// If you want to convert, see [`DynImage::to_rgba`].
+ pub fn get_rgba(self) -> Result<Image<T, 4>, Self> {
+ match self {
+ Self::Rgba(i) => Ok(i),
+ _ => Err(self),
+ }
+ }
+}
+
+impl DynImage<Box<[u8]>> {
+ /// Convert this dyn image into a Y image.
+ pub fn to_y(self) -> Image<Box<[u8]>, 1> {
+ self.into()
+ }
+
+ /// Convert this dyn image into a YA image.
+ pub fn to_ya(self) -> Image<Box<[u8]>, 2> {
+ self.into()
+ }
+
+ /// Convert this dyn image into a RGB image.
+ pub fn to_rgb(self) -> Image<Box<[u8]>, 3> {
+ self.into()
+ }
+
+ /// Convert this dyn image into a RGBA image.
+ pub fn to_rgba(self) -> Image<Box<[u8]>, 4> {
+ self.into()
+ }
+}
+
+impl<T: AsRef<[u8]>> DynImage<T> {
+ /// Produce a Y image from this dyn image.
+ pub fn y(&self) -> Image<Box<[u8]>, 1> {
+ e!(self, |i| i.as_ref().into())
+ }
+
+ /// Produce a YA image from this dyn image.
+ pub fn ya(&self) -> Image<Box<[u8]>, 2> {
+ e!(self, |i| i.as_ref().into())
+ }
+
+ /// Produce a RGB image from this dyn image.
+ pub fn rgb(&self) -> Image<Box<[u8]>, 3> {
+ e!(self, |i| i.as_ref().into())
+ }
+
+ /// Produce a RGBA image from this dyn image.
+ pub fn rgba(&self) -> Image<Box<[u8]>, 4> {
+ e!(self, |i| i.as_ref().into())
+ }
+}
diff --git a/src/dyn/mod.rs b/src/dyn/mod.rs
new file mode 100644
index 0000000..b86742e
--- /dev/null
+++ b/src/dyn/mod.rs
@@ -0,0 +1,121 @@
+use crate::Image;
+mod affine;
+mod convert;
+#[cfg(feature = "scale")]
+mod scale;
+
+#[derive(Clone, Debug, PartialEq)]
+/// Dynamic image.
+/// Can be any of {`Y8`, `YA8`, `RGB8`, `RGB16`}.
+pub enum DynImage<T> {
+ /// Y image
+ Y(Image<T, 1>),
+ /// YA image
+ Ya(Image<T, 2>),
+ /// RGB image
+ Rgb(Image<T, 3>),
+ /// RGBA image
+ Rgba(Image<T, 4>),
+}
+
+impl Copy for DynImage<&[u8]> {}
+
+macro_rules! e {
+ ($dyn:expr => |$image: pat_param| $do:expr) => {
+ match $dyn {
+ DynImage::Y($image) => DynImage::Y($do),
+ DynImage::Ya($image) => DynImage::Ya($do),
+ DynImage::Rgb($image) => DynImage::Rgb($do),
+ DynImage::Rgba($image) => DynImage::Rgba($do),
+ }
+ };
+ ($dyn:expr, |$image: pat_param| $do:expr) => {
+ match $dyn {
+ DynImage::Y($image) => $do,
+ DynImage::Ya($image) => $do,
+ DynImage::Rgb($image) => $do,
+ DynImage::Rgba($image) => $do,
+ }
+ };
+}
+use e;
+
+impl<T> DynImage<T> {
+ /// Get the width of this image.
+ pub const fn width(&self) -> u32 {
+ e!(self, |i| i.width())
+ }
+
+ /// Get the height of this image.
+ pub const fn height(&self) -> u32 {
+ e!(self, |i| i.height())
+ }
+
+ /// Get the image buffer.
+ pub const fn buffer(&self) -> &T {
+ e!(self, |i| i.buffer())
+ }
+
+ /// Take the image buffer.
+ pub fn take_buffer(self) -> T {
+ e!(self, |i| i.take_buffer())
+ }
+}
+
+impl<T: AsRef<[u8]>> DynImage<T> {
+ /// Reference this image.
+ pub fn as_ref(&self) -> DynImage<&[u8]> {
+ e!(self => |i| i.as_ref())
+ }
+
+ /// Bytes of this image.
+ pub fn bytes(&self) -> &[u8] {
+ e!(self, |i| i.bytes())
+ }
+}
+
+impl DynImage<Box<[u8]>> {
+ #[cfg(feature = "save")]
+ /// Open a PNG image
+ pub fn open(f: impl AsRef<std::path::Path>) -> Self {
+ use png::Transformations as T;
+ let p = std::fs::File::open(f).unwrap();
+ let r = std::io::BufReader::new(p);
+ let mut dec = png::Decoder::new(r);
+ dec.set_transformations(T::STRIP_16 | T::EXPAND);
+ let mut reader = dec.read_info().unwrap();
+ let mut buf = vec![0; reader.output_buffer_size()].into_boxed_slice();
+ let info = reader.next_frame(&mut buf).unwrap();
+ use png::ColorType::*;
+ match info.color_type {
+ Indexed | Grayscale => Self::Y(Image::build(info.width, info.height).buf(buf)),
+ Rgb => Self::Rgb(Image::build(info.width, info.height).buf(buf)),
+ Rgba => Self::Rgba(Image::build(info.width, info.height).buf(buf)),
+ GrayscaleAlpha => Self::Ya(Image::build(info.width, info.height).buf(buf)),
+ }
+ }
+
+ #[cfg(feature = "save")]
+ /// Save this image to a PNG.
+ 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_depth(png::BitDepth::Eight);
+ enc.set_color(match self {
+ Self::Y(_) => png::ColorType::Grayscale,
+ Self::Ya(_) => png::ColorType::GrayscaleAlpha,
+ Self::Rgb(_) => png::ColorType::Rgb,
+ Self::Rgba(_) => png::ColorType::Rgba,
+ });
+ 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.bytes()).unwrap();
+ }
+}
diff --git a/src/dyn/scale.rs b/src/dyn/scale.rs
new file mode 100644
index 0000000..f827a1f
--- /dev/null
+++ b/src/dyn/scale.rs
@@ -0,0 +1,10 @@
+use crate::scale::traits::ScalingAlgorithm;
+
+use super::{e, DynImage};
+
+impl<T: AsMut<[u8]> + AsRef<[u8]>> DynImage<T> {
+ /// Scale this image with a given scaling algorithm.
+ pub fn scale<A: ScalingAlgorithm>(&mut self, width: u32, height: u32) -> DynImage<Box<[u8]>> {
+ e!(self => |i| i.scale::<A>(width, height))
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
index 3f5d7a3..e325c6c 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,6 +1,39 @@
//! # fimg
//!
//! Provides fast image operations, such as rotation, flipping, and overlaying.
+//!
+//! ## Organization
+//!
+//! Image types:
+//!
+//! - [`Image`]: the main image type.
+//! - [`DynImage`]: This is the image type you use when, say, loading a png. You should immediately convert this into a
+//! - [`ImageCloner`]: this is... a [`Image`], but about to be cloned. It just allows some simple out-of-place optimizations, that `.clone().op()` dont allow. (produce with [`Image::cloner`])
+//!
+//! ### Operations
+//!
+//! Affine:
+//! - [`Image::rot_90`]
+//! - [`Image::rot_180`]
+//! - [`Image::rot_270`]
+//! - [`Image::flip_h`]
+//! - [`Image::flip_v`]
+//!
+//! Drawing:
+//! - [`Image::box`], [`Image::filled_box`], [`Image::stroked_box`]
+//! - [`Image::circle`], [`Image::border_circle`]
+//! - [`Image::line`], [`Image::thick_line`]
+//! - [`Image::points`]
+//! - [`Image::quad`]
+//! - [`Image::poly`], [`Image::border_poly`]
+//! - [`Image::tri`]
+//! - [`Image::text`]
+//!
+//! Scaling: [`Image::scale`]
+//!
+//! Misc image ops:
+//! - [`Image::repeated`]
+//! - [`Image::overlay`](Overlay), [`Image::overlay_at`](OverlayAt), [`Image::overlay_blended`](BlendingOverlay)
#![feature(
slice_swap_unchecked,
generic_const_exprs,
@@ -27,16 +60,21 @@
use std::{num::NonZeroU32, slice::SliceIndex};
mod affine;
+#[doc(hidden)]
pub mod builder;
+#[doc(hidden)]
pub mod cloner;
+mod convert;
mod drawing;
+mod r#dyn;
pub(crate) mod math;
mod overlay;
pub mod pixels;
#[cfg(feature = "scale")]
pub mod scale;
-use cloner::ImageCloner;
+pub use cloner::ImageCloner;
pub use overlay::{BlendingOverlay, ClonerOverlay, ClonerOverlayAt, Overlay, OverlayAt};
+pub use r#dyn::DynImage;
/// like assert!(), but causes undefined behaviour at runtime when the condition is not met.
///
@@ -145,14 +183,14 @@ impl<T: Clone, const CHANNELS: usize> Clone for Image<T, CHANNELS> {
impl<T, const CHANNELS: usize> Image<T, CHANNELS> {
#[inline]
/// get the height as a [`u32`]
- pub fn height(&self) -> u32 {
- self.height.into()
+ pub const fn height(&self) -> u32 {
+ self.height.get()
}
#[inline]
/// get the width as a [`u32`]
- pub fn width(&self) -> u32 {
- self.width.into()
+ pub const fn width(&self) -> u32 {
+ self.width.get()
}
#[inline]
@@ -299,7 +337,12 @@ impl<T: AsRef<[u8]>, const CHANNELS: usize> Image<T, CHANNELS> {
/// The size of the underlying buffer.
#[allow(clippy::len_without_is_empty)]
pub fn len(&self) -> usize {
- self.buffer.as_ref().len()
+ self.bytes().len()
+ }
+
+ /// Bytes of this image.
+ pub fn bytes(&self) -> &[u8] {
+ self.buffer.as_ref()
}
/// # Safety
@@ -342,7 +385,7 @@ impl<T: AsRef<[u8]>, const CHANNELS: usize> Image<T, CHANNELS> {
/// 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.as_ref()) }
+ unsafe { Image::new(self.width, self.height, self.bytes()) }
}
#[inline]
@@ -352,14 +395,14 @@ impl<T: AsRef<[u8]>, const CHANNELS: usize> Image<T, CHANNELS> {
unsafe { assert_unchecked!(self.len() > CHANNELS) };
// SAFETY: no half pixels!
unsafe { assert_unchecked!(self.len() % CHANNELS == 0) };
- self.buffer.as_ref().array_chunks::<CHANNELS>()
+ self.bytes().array_chunks::<CHANNELS>()
}
#[inline]
/// Flatten the chunks of this image into a slice of slices.
pub fn flatten(&self) -> &[[u8; CHANNELS]] {
// SAFETY: buffer cannot have half pixels
- unsafe { self.buffer.as_ref().as_chunks_unchecked::<CHANNELS>() }
+ unsafe { self.bytes().as_chunks_unchecked::<CHANNELS>() }
}
/// Return a pixel at (x, y).
@@ -427,7 +470,7 @@ impl<T: AsMut<[u8]> + AsRef<[u8]>, const CHANNELS: usize> Image<T, CHANNELS> {
unsafe fn copy_within(&mut self, src: std::ops::Range<usize>, dest: usize) {
let std::ops::Range { start, end } = src;
debug_assert!(
- dest <= self.buffer.as_ref().len() - end - start,
+ dest <= self.bytes().len() - end - start,
"dest is out of bounds"
);
#[allow(clippy::multiple_unsafe_ops_per_block)]
@@ -516,7 +559,7 @@ macro_rules! save {
(0.15000, 0.06000),
));
let mut writer = enc.write_header().unwrap();
- writer.write_image_data(self.buffer.as_ref()).unwrap();
+ writer.write_image_data(self.bytes()).unwrap();
}
}
};
@@ -531,13 +574,10 @@ impl<const CHANNELS: usize> Image<Vec<u8>, CHANNELS> {
let r = std::io::BufReader::new(p);
let mut dec = png::Decoder::new(r);
match CHANNELS {
- 1 => dec.set_transformations(T::STRIP_16 | T::EXPAND),
- 2 => dec.set_transformations(T::STRIP_16 | T::ALPHA),
- 3 => dec.set_transformations(T::STRIP_16 | T::EXPAND),
- 4 => dec.set_transformations(T::STRIP_16 | T::ALPHA),
+ 1 | 3 => dec.set_transformations(T::STRIP_16 | T::EXPAND),
+ 2 | 4 => dec.set_transformations(T::STRIP_16 | T::ALPHA),
_ => (),
}
- dec.set_transformations(png::Transformations::EXPAND);
let mut reader = dec.read_info().unwrap();
let mut buf = vec![0; reader.output_buffer_size()];
let info = reader.next_frame(&mut buf).unwrap();
diff --git a/src/pixels/convert.rs b/src/pixels/convert.rs
new file mode 100644
index 0000000..55c9338
--- /dev/null
+++ b/src/pixels/convert.rs
@@ -0,0 +1,97 @@
+//! provides From's for pixels.
+
+use super::{Push, Trunc};
+
+/// Converts a pixel to another pixel.
+pub trait PFrom<const N: usize> {
+ /// Convert a pixel to this pixel.
+ fn pfrom(f: [u8; N]) -> Self;
+}
+
+impl<const N: usize> PFrom<N> for [u8; N] {
+ fn pfrom(f: [u8; N]) -> Self {
+ f
+ }
+}
+
+/// Y pixel
+pub type Y = [u8; 1];
+impl PFrom<2> for Y {
+ fn pfrom(f: YA) -> Self {
+ f.trunc()
+ }
+}
+
+impl PFrom<3> for Y {
+ fn pfrom([r, g, b]: RGB) -> Self {
+ [((2126 * r as u16 + 7152 * g as u16 + 722 * b as u16) / 10000) as u8]
+ }
+}
+
+impl PFrom<4> for Y {
+ fn pfrom(f: RGBA) -> Self {
+ PFrom::pfrom(f.trunc())
+ }
+}
+
+/// YA pixel
+pub type YA = [u8; 2];
+impl PFrom<1> for YA {
+ fn pfrom(f: Y) -> Self {
+ f.and(255)
+ }
+}
+
+impl PFrom<3> for YA {
+ fn pfrom(f: RGB) -> Self {
+ Y::pfrom(f).and(255)
+ }
+}
+
+impl PFrom<4> for YA {
+ fn pfrom(f: RGBA) -> Self {
+ Y::pfrom(f.trunc()).and(255)
+ }
+}
+
+/// RGB pixel
+pub type RGB = [u8; 3];
+
+impl PFrom<1> for RGB {
+ fn pfrom([y]: Y) -> Self {
+ [y; 3]
+ }
+}
+
+impl PFrom<2> for RGB {
+ fn pfrom([y, _]: YA) -> Self {
+ [y; 3]
+ }
+}
+
+impl PFrom<4> for RGB {
+ fn pfrom(f: RGBA) -> Self {
+ f.trunc()
+ }
+}
+
+/// RGBA pixel
+pub type RGBA = [u8; 4];
+
+impl PFrom<1> for RGBA {
+ fn pfrom([y]: Y) -> Self {
+ [y; 3].and(255)
+ }
+}
+
+impl PFrom<2> for RGBA {
+ fn pfrom([y, a]: YA) -> Self {
+ [y; 3].and(a)
+ }
+}
+
+impl PFrom<3> for RGBA {
+ fn pfrom(f: [u8; 3]) -> Self {
+ f.and(255)
+ }
+}
diff --git a/src/pixels/mod.rs b/src/pixels/mod.rs
index 503115a..8607206 100644
--- a/src/pixels/mod.rs
+++ b/src/pixels/mod.rs
@@ -4,5 +4,6 @@ pub mod blending;
mod utility;
mod wam;
pub use blending::Blend;
-pub(crate) use utility::{float, unfloat, Floatify, PMap, Trunc, Unfloatify};
+pub(crate) use utility::{float, unfloat, Floatify, PMap, Push, Trunc, Unfloatify};
pub(crate) use wam::Wam;
+pub mod convert;
diff --git a/src/pixels/utility.rs b/src/pixels/utility.rs
index 5184f58..aabadb5 100644
--- a/src/pixels/utility.rs
+++ b/src/pixels/utility.rs
@@ -53,7 +53,7 @@ impl<const N: usize, T: Copy, R: Copy> PMap<T, R, N> for [T; N] {
}
pub trait Trunc<T, const N: usize> {
- /// it does `a[..a.len() - 1].try_into().unwrap()``.
+ /// it does `a[..a.len() - 1].try_into().unwrap()`.
fn trunc(&self) -> [T; N - 1];
}
@@ -63,6 +63,17 @@ impl<const N: usize, T: Copy> Trunc<T, N> for [T; N] {
}
}
+pub trait Push<T, const N: usize> {
+ fn and(self, and: T) -> [T; N + 1];
+}
+
+impl<const N: usize, T> Push<T, N> for [T; N] {
+ fn and(self, and: T) -> [T; N + 1] {
+ let mut iter = self.into_iter().chain(std::iter::once(and));
+ std::array::from_fn(|_| iter.next().unwrap())
+ }
+}
+
#[test]
fn trunc() {
let x = [1];
@@ -70,3 +81,9 @@ fn trunc() {
let x = [1, 2, 3, 4];
assert_eq!(x.trunc(), [1, 2, 3]);
}
+
+#[test]
+fn append() {
+ let x = [1];
+ assert_eq!(x.and(5), [1, 5]);
+}
diff --git a/src/pixels/wam.rs b/src/pixels/wam.rs
index 30016fd..cf0f461 100644
--- a/src/pixels/wam.rs
+++ b/src/pixels/wam.rs
@@ -7,7 +7,7 @@ pub trait Wam {
///
/// # Safety
///
- /// pls make l = 0..=f32::MAX/2, r = 0..=f32::MAX/2
+ /// pls make l = <code>0..=[f32::MAX]/2</code>, r = <code>0..=[f32::MAX]/2</code>
unsafe fn wam(self, b: Self, l: FF32, r: FF32) -> Self;
}