mirror of
https://github.com/bend-n/fimg.git
synced 2024-12-22 02:28:19 -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]
|
||||
name = "fimg"
|
||||
version = "0.4.19"
|
||||
version = "0.4.20"
|
||||
authors = ["bend-n <bend.n@outlook.com>"]
|
||||
license = "MIT"
|
||||
edition = "2021"
|
||||
|
@ -14,9 +14,10 @@ png = { version = "0.17", features = ["unstable"], optional = true }
|
|||
fontdue = { version = "0.7.3", optional = true }
|
||||
vecto = "0.1.0"
|
||||
umath = "0.0.7"
|
||||
fr = { version = "0.1.1", package = "fer", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
iai = { path = "../iai" }
|
||||
iai = { git = "https://github.com/bend-n/iai.git" }
|
||||
|
||||
[[bench]]
|
||||
name = "overlays"
|
||||
|
@ -33,15 +34,21 @@ name = "affine_transformations"
|
|||
path = "benches/affine_transformations.rs"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "scaling"
|
||||
path = "benches/scaling.rs"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "tile"
|
||||
path = "benches/tile.rs"
|
||||
harness = false
|
||||
|
||||
[features]
|
||||
scale = ["fr"]
|
||||
save = ["png"]
|
||||
text = ["fontdue"]
|
||||
default = ["save"]
|
||||
default = ["save", "scale"]
|
||||
|
||||
[profile.release]
|
||||
debug = 2
|
||||
|
|
|
@ -8,7 +8,7 @@ quick simple image operations
|
|||
- [x] rotation
|
||||
- [x] flipping
|
||||
- [x] image tiling
|
||||
- [x] nearest neighbor scaling
|
||||
- [x] image scaling
|
||||
- [x] triangle drawing
|
||||
- [x] simple line 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
|
||||
)]
|
||||
#![warn(
|
||||
clippy::missing_docs_in_private_items,
|
||||
clippy::multiple_unsafe_ops_per_block,
|
||||
clippy::undocumented_unsafe_blocks,
|
||||
clippy::missing_const_for_fn,
|
||||
clippy::missing_safety_doc,
|
||||
|
@ -34,6 +32,7 @@ pub mod cloner;
|
|||
mod drawing;
|
||||
pub(crate) mod math;
|
||||
mod overlay;
|
||||
#[cfg(feature = "scale")]
|
||||
pub mod scale;
|
||||
use cloner::ImageCloner;
|
||||
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