mirror of
https://github.com/bend-n/fimg.git
synced 2024-12-22 10:28:21 -06:00
more image scaling filters
This commit is contained in:
parent
831f05de5b
commit
2c88066426
13
Cargo.toml
13
Cargo.toml
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "fimg"
|
name = "fimg"
|
||||||
version = "0.4.19"
|
version = "0.4.20"
|
||||||
authors = ["bend-n <bend.n@outlook.com>"]
|
authors = ["bend-n <bend.n@outlook.com>"]
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
@ -14,9 +14,10 @@ png = { version = "0.17", features = ["unstable"], optional = true }
|
||||||
fontdue = { version = "0.7.3", optional = true }
|
fontdue = { version = "0.7.3", optional = true }
|
||||||
vecto = "0.1.0"
|
vecto = "0.1.0"
|
||||||
umath = "0.0.7"
|
umath = "0.0.7"
|
||||||
|
fr = { version = "0.1.1", package = "fer", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
iai = { path = "../iai" }
|
iai = { git = "https://github.com/bend-n/iai.git" }
|
||||||
|
|
||||||
[[bench]]
|
[[bench]]
|
||||||
name = "overlays"
|
name = "overlays"
|
||||||
|
@ -33,15 +34,21 @@ name = "affine_transformations"
|
||||||
path = "benches/affine_transformations.rs"
|
path = "benches/affine_transformations.rs"
|
||||||
harness = false
|
harness = false
|
||||||
|
|
||||||
|
[[bench]]
|
||||||
|
name = "scaling"
|
||||||
|
path = "benches/scaling.rs"
|
||||||
|
harness = false
|
||||||
|
|
||||||
[[bench]]
|
[[bench]]
|
||||||
name = "tile"
|
name = "tile"
|
||||||
path = "benches/tile.rs"
|
path = "benches/tile.rs"
|
||||||
harness = false
|
harness = false
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
scale = ["fr"]
|
||||||
save = ["png"]
|
save = ["png"]
|
||||||
text = ["fontdue"]
|
text = ["fontdue"]
|
||||||
default = ["save"]
|
default = ["save", "scale"]
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
debug = 2
|
debug = 2
|
||||||
|
|
|
@ -8,7 +8,7 @@ quick simple image operations
|
||||||
- [x] rotation
|
- [x] rotation
|
||||||
- [x] flipping
|
- [x] flipping
|
||||||
- [x] image tiling
|
- [x] image tiling
|
||||||
- [x] nearest neighbor scaling
|
- [x] image scaling
|
||||||
- [x] triangle drawing
|
- [x] triangle drawing
|
||||||
- [x] simple line drawing
|
- [x] simple line drawing
|
||||||
- [x] box drawing
|
- [x] box drawing
|
||||||
|
|
21
benches/scaling.rs
Normal file
21
benches/scaling.rs
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
use fimg::{scale::*, Image};
|
||||||
|
|
||||||
|
macro_rules! bench {
|
||||||
|
($([$a: ident, $alg:ident]),+ $(,)?) => {
|
||||||
|
$(fn $a() {
|
||||||
|
let img: Image<_, 3> = Image::open("tdata/cat.png");
|
||||||
|
iai::black_box(img.scale::<$alg>(267, 178));
|
||||||
|
})+
|
||||||
|
|
||||||
|
iai::main!($($a,)+);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
bench![
|
||||||
|
[nearest, Nearest],
|
||||||
|
[bilinear, Bilinear],
|
||||||
|
[boxs, Box],
|
||||||
|
[lanczos3, Lanczos3],
|
||||||
|
[catmull, CatmullRom],
|
||||||
|
[mitchell, Mitchell],
|
||||||
|
[hamming, Hamming],
|
||||||
|
];
|
|
@ -14,8 +14,6 @@
|
||||||
array_chunks
|
array_chunks
|
||||||
)]
|
)]
|
||||||
#![warn(
|
#![warn(
|
||||||
clippy::missing_docs_in_private_items,
|
|
||||||
clippy::multiple_unsafe_ops_per_block,
|
|
||||||
clippy::undocumented_unsafe_blocks,
|
clippy::undocumented_unsafe_blocks,
|
||||||
clippy::missing_const_for_fn,
|
clippy::missing_const_for_fn,
|
||||||
clippy::missing_safety_doc,
|
clippy::missing_safety_doc,
|
||||||
|
@ -34,6 +32,7 @@ pub mod cloner;
|
||||||
mod drawing;
|
mod drawing;
|
||||||
pub(crate) mod math;
|
pub(crate) mod math;
|
||||||
mod overlay;
|
mod overlay;
|
||||||
|
#[cfg(feature = "scale")]
|
||||||
pub mod scale;
|
pub mod scale;
|
||||||
use cloner::ImageCloner;
|
use cloner::ImageCloner;
|
||||||
pub use overlay::{ClonerOverlay, ClonerOverlayAt, Overlay, OverlayAt};
|
pub use overlay::{ClonerOverlay, ClonerOverlayAt, Overlay, OverlayAt};
|
||||||
|
|
42
src/scale.rs
42
src/scale.rs
|
@ -1,42 +0,0 @@
|
||||||
//! holds scaling operations, at current only the Nearest Neighbor
|
|
||||||
use crate::Image;
|
|
||||||
|
|
||||||
/// [Nearest Neighbor](https://en.wikipedia.org/wiki/Nearest-neighbor_interpolation) image scaling algorithm implementation.
|
|
||||||
/// Use [`Nearest::scale`].
|
|
||||||
pub struct Nearest;
|
|
||||||
impl Nearest {
|
|
||||||
/// Resize a image.
|
|
||||||
/// # Safety
|
|
||||||
///
|
|
||||||
/// `image` must be as big or bigger than `width`, `height.
|
|
||||||
#[must_use = "function does not modify the original image"]
|
|
||||||
pub unsafe fn scale<const N: usize>(
|
|
||||||
image: Image<&[u8], N>,
|
|
||||||
width: u32,
|
|
||||||
height: u32,
|
|
||||||
) -> Image<Vec<u8>, N> {
|
|
||||||
let x_scale = image.width() as f32 / width as f32;
|
|
||||||
let y_scale = image.height() as f32 / height as f32;
|
|
||||||
let mut out = Image::alloc(width, height);
|
|
||||||
for y in 0..height {
|
|
||||||
for x in 0..width {
|
|
||||||
let x1 = ((x as f32 + 0.5) * x_scale).floor() as u32;
|
|
||||||
let y1 = ((y as f32 + 0.5) * y_scale).floor() as u32;
|
|
||||||
// SAFETY: i asked the caller to make sure its ok
|
|
||||||
let px = unsafe { image.pixel(x1, y1) };
|
|
||||||
// SAFETY: were looping over the width and height of out. its ok.
|
|
||||||
unsafe { out.set_pixel(x, y, px) };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
out
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_nearest() {
|
|
||||||
let i = Image::<_, 3>::open("tdata/cat.png");
|
|
||||||
assert_eq!(
|
|
||||||
unsafe { Nearest::scale(i.as_ref(), 268, 178) }.buffer,
|
|
||||||
Image::<_, 3>::open("tdata/small_cat.png").buffer
|
|
||||||
);
|
|
||||||
}
|
|
140
src/scale/algorithms.rs
Normal file
140
src/scale/algorithms.rs
Normal file
|
@ -0,0 +1,140 @@
|
||||||
|
use super::{traits::*, *};
|
||||||
|
use std::num::NonZeroU32;
|
||||||
|
|
||||||
|
/// [Nearest Neighbor](https://en.wikipedia.org/wiki/Nearest-neighbor_interpolation) image scaling algorithm.
|
||||||
|
pub struct Nearest;
|
||||||
|
|
||||||
|
impl ScalingAlgorithm for Nearest {
|
||||||
|
/// Can be used on non opaque too! (Nearest is special like that).
|
||||||
|
fn scale_opaque<const N: usize>(
|
||||||
|
i: Image<&[u8], N>,
|
||||||
|
w: NonZeroU32,
|
||||||
|
h: NonZeroU32,
|
||||||
|
) -> Image<std::boxed::Box<[u8]>, N>
|
||||||
|
where
|
||||||
|
ChannelCount<N>: ToImageView<N>,
|
||||||
|
{
|
||||||
|
let mut dst = fr::Image::new(w, h);
|
||||||
|
// SAFETY: swear, the pixel types are the same
|
||||||
|
unsafe {
|
||||||
|
fr::Resizer::new(fr::ResizeAlg::Nearest)
|
||||||
|
.resize(&ChannelCount::<N>::wrap(i), &mut dst.view_mut())
|
||||||
|
};
|
||||||
|
|
||||||
|
// SAFETY: ctor
|
||||||
|
unsafe { Image::new(dst.width(), dst.height(), dst.into_vec().into()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn scale_transparent<const N: usize>(
|
||||||
|
i: Image<&mut [u8], N>,
|
||||||
|
w: NonZeroU32,
|
||||||
|
h: NonZeroU32,
|
||||||
|
) -> Image<std::boxed::Box<[u8]>, N>
|
||||||
|
where
|
||||||
|
ChannelCount<N>: AlphaDiv<N>,
|
||||||
|
{
|
||||||
|
Self::scale_opaque(i.as_ref(), w, h)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! alg {
|
||||||
|
($for:ident) => {
|
||||||
|
impl ScalingAlgorithm for $for {
|
||||||
|
fn scale_opaque<const N: usize>(
|
||||||
|
i: Image<&[u8], N>,
|
||||||
|
w: NonZeroU32,
|
||||||
|
h: NonZeroU32,
|
||||||
|
) -> Image<std::boxed::Box<[u8]>, N>
|
||||||
|
where
|
||||||
|
ChannelCount<N>: ToImageView<N>,
|
||||||
|
{
|
||||||
|
let mut dst = fr::Image::new(w, h);
|
||||||
|
// SAFETY: swear, the pixel types are the same
|
||||||
|
unsafe {
|
||||||
|
fr::Resizer::new(fr::ResizeAlg::Convolution(fr::FilterType::$for))
|
||||||
|
.resize(&ChannelCount::<N>::wrap(i), &mut dst.view_mut())
|
||||||
|
};
|
||||||
|
|
||||||
|
// SAFETY: ctor
|
||||||
|
unsafe { Image::new(dst.width(), dst.height(), dst.into_vec().into()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn scale_transparent<const N: usize>(
|
||||||
|
i: Image<&mut [u8], N>,
|
||||||
|
w: NonZeroU32,
|
||||||
|
h: NonZeroU32,
|
||||||
|
) -> Image<std::boxed::Box<[u8]>, N>
|
||||||
|
where
|
||||||
|
ChannelCount<N>: AlphaDiv<N>,
|
||||||
|
{
|
||||||
|
let mut dst = fr::Image::new(w, h);
|
||||||
|
// SAFETY: yes
|
||||||
|
unsafe {
|
||||||
|
fr::Resizer::new(fr::ResizeAlg::Convolution(fr::FilterType::$for))
|
||||||
|
.resize(&ChannelCount::<N>::handle(i).view(), &mut dst.view_mut())
|
||||||
|
}
|
||||||
|
|
||||||
|
// SAFETY: ctor
|
||||||
|
unsafe { Image::new(dst.width(), dst.height(), dst.into_vec().into()) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// [Lanczos](https://en.wikipedia.org/wiki/Lanczos_resampling) scaling with a filter size (*a*) of 3.
|
||||||
|
pub struct Lanczos3 {}
|
||||||
|
alg!(Lanczos3);
|
||||||
|
|
||||||
|
/// [Catmull-Rom](https://en.wikipedia.org/wiki/Centripetal_Catmull%E2%80%93Rom_spline) bicubic filtering.
|
||||||
|
pub struct CatmullRom {}
|
||||||
|
alg!(CatmullRom);
|
||||||
|
|
||||||
|
/// Linear interpolation.
|
||||||
|
pub struct Bilinear {}
|
||||||
|
alg!(Bilinear);
|
||||||
|
|
||||||
|
/// The opposite of [`Nearest`].
|
||||||
|
pub struct Box {}
|
||||||
|
alg!(Box);
|
||||||
|
|
||||||
|
/// Hamming filtering has the same performance as a [`Bilinear`] filter, while
|
||||||
|
/// providing image (downscaling) quality comparable to bicubic filters like
|
||||||
|
/// [`CatmullRom`] or [`Mitchell`]. Creates a sharper image than [`Bilinear`] filtering,
|
||||||
|
/// and doesn't have dislocations on local level like [`Box`] suffers from.
|
||||||
|
/// Not recommended for upscaling.
|
||||||
|
pub struct Hamming {}
|
||||||
|
alg!(Hamming);
|
||||||
|
|
||||||
|
/// [Mitchell–Netravali](https://en.wikipedia.org/wiki/Mitchell%E2%80%93Netravali_filters) bicubic filtering.
|
||||||
|
pub struct Mitchell {}
|
||||||
|
alg!(Mitchell);
|
||||||
|
|
||||||
|
impl Nearest {
|
||||||
|
/// Resize a image.
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// `image` must be as big or bigger than `width`, `height.
|
||||||
|
#[must_use = "function does not modify the original image"]
|
||||||
|
#[deprecated = "use Image::scale instead (note that Image::scale does not support any N. if there is a N you would like to see supported, please open a issue)"]
|
||||||
|
pub unsafe fn scale<const N: usize>(
|
||||||
|
image: Image<&[u8], N>,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
) -> Image<Vec<u8>, N> {
|
||||||
|
let x_scale = image.width() as f32 / width as f32;
|
||||||
|
let y_scale = image.height() as f32 / height as f32;
|
||||||
|
let mut out = Image::alloc(width, height);
|
||||||
|
for y in 0..height {
|
||||||
|
for x in 0..width {
|
||||||
|
let x1 = ((x as f32 + 0.5) * x_scale).floor() as u32;
|
||||||
|
let y1 = ((y as f32 + 0.5) * y_scale).floor() as u32;
|
||||||
|
// SAFETY: i asked the caller to make sure its ok
|
||||||
|
let px = unsafe { image.pixel(x1, y1) };
|
||||||
|
// SAFETY: were looping over the width and height of out. its ok.
|
||||||
|
unsafe { out.set_pixel(x, y, px) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out
|
||||||
|
}
|
||||||
|
}
|
79
src/scale/mod.rs
Normal file
79
src/scale/mod.rs
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
//! holds scaling operations.
|
||||||
|
//!
|
||||||
|
//! choose from the wide expanse of options (ordered fastest to slowest):
|
||||||
|
//!
|
||||||
|
//! - [`Nearest`]: quickest, dumbest, jaggedest, scaling algorithm
|
||||||
|
//! - [`Box`]: you want slightly less pixels than nearest? here you go! kinda blurry though.
|
||||||
|
//! - [`Bilinear`]: _smooth_ scaling algorithm. rather fuzzy.
|
||||||
|
//! - [`Hamming`]: solves the [`Box`] problems. clearer image.
|
||||||
|
//! - [`CatmullRom`]: about the same as [`Hamming`], just a little slower.
|
||||||
|
//! - [`Mitchell`]: honestly, cant see the difference from [`CatmullRom`].
|
||||||
|
//! - [`Lanczos3`]: prettiest scaling algorithm. highly recommend.
|
||||||
|
//!
|
||||||
|
//! usage:
|
||||||
|
//! ```
|
||||||
|
//! # use fimg::{Image, scale::Lanczos3};
|
||||||
|
//! let i = Image::<_, 3>::open("tdata/small_cat.png");
|
||||||
|
//! let scaled = i.scale::<Lanczos3>(2144, 1424);
|
||||||
|
//! ```
|
||||||
|
use crate::Image;
|
||||||
|
|
||||||
|
mod algorithms;
|
||||||
|
pub mod traits;
|
||||||
|
pub use algorithms::*;
|
||||||
|
|
||||||
|
macro_rules! transparent {
|
||||||
|
($n: literal, $name: ident) => {
|
||||||
|
impl<T: AsMut<[u8]> + AsRef<[u8]>> Image<T, $n> {
|
||||||
|
/// Scale a
|
||||||
|
#[doc = stringify!($name)]
|
||||||
|
/// image with a given scaling algorithm.
|
||||||
|
pub fn scale<A: traits::ScalingAlgorithm>(
|
||||||
|
&mut self,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
) -> Image<std::boxed::Box<[u8]>, $n> {
|
||||||
|
A::scale_transparent(
|
||||||
|
self.as_mut(),
|
||||||
|
width.try_into().unwrap(),
|
||||||
|
height.try_into().unwrap(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! opaque {
|
||||||
|
($n: literal, $name: ident) => {
|
||||||
|
impl<T: AsMut<[u8]> + AsRef<[u8]>> Image<T, $n> {
|
||||||
|
/// Scale a
|
||||||
|
#[doc = stringify!($name)]
|
||||||
|
/// image with a given scaling algorithm.
|
||||||
|
pub fn scale<A: traits::ScalingAlgorithm>(
|
||||||
|
&self,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
) -> Image<std::boxed::Box<[u8]>, $n> {
|
||||||
|
A::scale_opaque(
|
||||||
|
self.as_ref(),
|
||||||
|
width.try_into().unwrap(),
|
||||||
|
height.try_into().unwrap(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
opaque!(1, Y);
|
||||||
|
transparent!(2, YA);
|
||||||
|
opaque!(3, RGB);
|
||||||
|
transparent!(4, RGBA);
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_nearest() {
|
||||||
|
let i = Image::<_, 3>::open("tdata/cat.png");
|
||||||
|
assert_eq!(
|
||||||
|
&*i.scale::<Nearest>(268, 178).buffer,
|
||||||
|
&*Image::<_, 3>::open("tdata/small_cat.png").buffer
|
||||||
|
);
|
||||||
|
}
|
91
src/scale/traits.rs
Normal file
91
src/scale/traits.rs
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
//! implementation detail for scaling. look into if you want to add a algorithm
|
||||||
|
use std::num::NonZeroU32;
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
mod seal {
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub trait Sealed {}
|
||||||
|
}
|
||||||
|
|
||||||
|
use seal::Sealed;
|
||||||
|
|
||||||
|
use crate::Image;
|
||||||
|
impl Sealed for ChannelCount<1> {}
|
||||||
|
impl Sealed for ChannelCount<2> {}
|
||||||
|
impl Sealed for ChannelCount<3> {}
|
||||||
|
impl Sealed for ChannelCount<4> {}
|
||||||
|
|
||||||
|
/// How to scale a image
|
||||||
|
pub trait ScalingAlgorithm {
|
||||||
|
/// Y/Rgb scale
|
||||||
|
fn scale_opaque<const N: usize>(
|
||||||
|
i: Image<&[u8], N>,
|
||||||
|
w: NonZeroU32,
|
||||||
|
h: NonZeroU32,
|
||||||
|
) -> Image<Box<[u8]>, N>
|
||||||
|
where
|
||||||
|
ChannelCount<N>: ToImageView<N>;
|
||||||
|
/// Ya/Rgba scale
|
||||||
|
fn scale_transparent<const N: usize>(
|
||||||
|
i: Image<&mut [u8], N>,
|
||||||
|
w: NonZeroU32,
|
||||||
|
h: NonZeroU32,
|
||||||
|
) -> Image<Box<[u8]>, N>
|
||||||
|
where
|
||||||
|
ChannelCount<N>: AlphaDiv<N>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// helper
|
||||||
|
pub trait ToImageView<const N: usize>: Sealed {
|
||||||
|
#[doc(hidden)]
|
||||||
|
type P: fr::PixelExt + fr::Convolution;
|
||||||
|
#[doc(hidden)]
|
||||||
|
fn wrap(i: Image<&[u8], N>) -> fr::ImageView<Self::P>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// helper
|
||||||
|
pub trait AlphaDiv<const N: usize>: Sealed + ToImageView<N> {
|
||||||
|
#[doc(hidden)]
|
||||||
|
type P: fr::PixelExt + fr::Convolution + fr::AlphaMulDiv;
|
||||||
|
#[doc(hidden)]
|
||||||
|
fn handle(i: Image<&mut [u8], N>) -> fr::Image<'_, <Self as AlphaDiv<N>>::P>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generic helper for [`Image`] and [`fr::Image`] transfers.
|
||||||
|
pub struct ChannelCount<const N: usize> {}
|
||||||
|
|
||||||
|
macro_rules! tiv {
|
||||||
|
($n:literal, $which:ident) => {
|
||||||
|
impl ToImageView<$n> for ChannelCount<$n> {
|
||||||
|
type P = fr::$which;
|
||||||
|
fn wrap(i: Image<&[u8], $n>) -> fr::ImageView<Self::P> {
|
||||||
|
// SAFETY: same conds
|
||||||
|
unsafe { fr::ImageView::new(i.width, i.height, i.buffer()) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
tiv!(1, U8);
|
||||||
|
tiv!(2, U8x2);
|
||||||
|
tiv!(3, U8x3);
|
||||||
|
tiv!(4, U8x4);
|
||||||
|
|
||||||
|
macro_rules! adiv {
|
||||||
|
($n:literal, $which:ident) => {
|
||||||
|
impl AlphaDiv<$n> for ChannelCount<$n> {
|
||||||
|
type P = fr::$which;
|
||||||
|
fn handle(i: Image<&mut [u8], $n>) -> fr::Image<<Self as AlphaDiv<$n>>::P> {
|
||||||
|
// SAFETY: we kinda have the same conditions
|
||||||
|
let mut i = unsafe { fr::Image::from_slice_u8(i.width, i.height, i.take_buffer()) };
|
||||||
|
// SAFETY: mhm
|
||||||
|
unsafe { fr::MulDiv::default().multiply_alpha_inplace(&mut i.view_mut()) };
|
||||||
|
|
||||||
|
i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
adiv!(2, U8x2);
|
||||||
|
adiv!(4, U8x4);
|
Loading…
Reference in a new issue