Rust library for fast image resizing with using of SIMD instructions.
Note: This library does not support converting image color spaces. If it is important for you to resize images with a non-linear color space (e.g. sRGB) correctly, then you need to convert it to a linear color space before resizing. Read more about resizing with respect to color space.
Supported pixel formats and available optimisations:
U8
- oneu8
component per pixel:- native Rust-code without forced SIMD
- AVX2
U8x3
- threeu8
components per pixel (e.g. RGB):- native Rust-code without forced SIMD
- SSE4.1 (auto-vectorization)
- AVX2
U8x4
- fouru8
components per pixel (RGBA, RGBx, CMYK and other):- native Rust-code without forced SIMD
- SSE4.1
- AVX2
U16x3
- threeu16
components per pixel (e.g. RGB):- native Rust-code without forced SIMD
I32
- onei32
component per pixel:- native Rust-code without forced SIMD
F32
- onef32
component per pixel:- native Rust-code without forced SIMD
Environment:
- CPU: Intel(R) Core(TM) i7-6700K CPU @ 4.00GHz
- RAM: DDR4 3000 MHz
- Ubuntu 20.04 (linux 5.11)
- Rust 1.57.1
- fast_image_resize = "0.6.0"
- glassbench = "0.3.1"
rustflags = ["-C", "llvm-args=-x86-branches-within-32B-boundaries"]
Other Rust libraries used to compare of resizing speed:
- image = "0.23.14" (https://crates.io/crates/image)
- resize = "0.7.2" (https://crates.io/crates/resize)
Resize algorithms:
- Nearest
- Convolution with Bilinear filter
- Convolution with CatmullRom filter
- Convolution with Lanczos3 filter
Pipeline:
src_image => resize => dst_image
- Source image nasa-4928x3279.png
- Numbers in table is mean duration of image resizing in milliseconds.
Nearest | Bilinear | CatmullRom | Lanczos3 | |
---|---|---|---|---|
image | 96.466 | 186.243 | 268.888 | 358.235 |
resize | 15.812 | 68.793 | 125.291 | 181.471 |
fir rust | 0.495 | 56.372 | 93.182 | 127.870 |
fir sse4.1 | - | 44.775 | 56.014 | 78.759 |
fir avx2 | - | 11.290 | 14.731 | 20.678 |
Pipeline:
src_image => multiply by alpha => resize => divide by alpha => dst_image
- Source image nasa-4928x3279-rgba.png
- Numbers in table is mean duration of image resizing in milliseconds.
Nearest | Bilinear | CatmullRom | Lanczos3 | |
---|---|---|---|---|
image | 102.809 | 185.787 | 266.163 | 356.038 |
resize | 18.723 | 83.072 | 156.063 | 229.400 |
fir rust | 12.518 | 65.821 | 92.371 | 122.981 |
fir sse4.1 | 9.529 | 21.008 | 27.444 | 35.534 |
fir avx2 | 7.622 | 15.755 | 19.369 | 25.184 |
Pipeline:
src_image => resize => dst_image
- Source image nasa-4928x3279.png has converted into grayscale image with one byte per pixel.
- Numbers in table is mean duration of image resizing in milliseconds.
Nearest | Bilinear | CatmullRom | Lanczos3 | |
---|---|---|---|---|
image | 80.937 | 132.497 | 174.721 | 219.534 |
resize | 10.107 | 25.514 | 49.871 | 84.692 |
fir rust | 0.208 | 23.255 | 26.471 | 37.642 |
fir avx2 | - | 9.927 | 8.054 | 12.298 |
Pipeline:
src_image => resize => dst_image
- Source image nasa-4928x3279.png
- Numbers in table is mean duration of image resizing in milliseconds.
Nearest | Bilinear | CatmullRom | Lanczos3 | |
---|---|---|---|---|
image | 98.962 | 180.516 | 255.045 | 335.265 |
resize | 16.504 | 66.861 | 120.973 | 174.806 |
fir rust | 0.800 | 53.874 | 89.453 | 123.963 |
use std::io::BufWriter;
use std::num::NonZeroU32;
use image::codecs::png::PngEncoder;
use image::io::Reader as ImageReader;
use image::{ColorType, GenericImageView};
use fast_image_resize as fr;
#[test]
fn resize_image_example() {
// Read source image from file
let img = ImageReader::open("./data/nasa-4928x3279.png")
.unwrap()
.decode()
.unwrap();
let width = NonZeroU32::new(img.width()).unwrap();
let height = NonZeroU32::new(img.height()).unwrap();
let mut src_image = fr::Image::from_vec_u8(
width,
height,
img.to_rgba8().into_raw(),
fr::PixelType::U8x4,
)
.unwrap();
// Create MulDiv instance
let alpha_mul_div = fr::MulDiv::default();
// Multiple RGB channels of source image by alpha channel
alpha_mul_div
.multiply_alpha_inplace(&mut src_image.view_mut())
.unwrap();
// Create container for data of destination image
let dst_width = NonZeroU32::new(1024).unwrap();
let dst_height = NonZeroU32::new(768).unwrap();
let mut dst_image = fr::Image::new(
dst_width,
dst_height,
src_image.pixel_type(),
);
// Get mutable view of destination image data
let mut dst_view = dst_image.view_mut();
// Create Resizer instance and resize source image
// into buffer of destination image
let mut resizer = fr::Resizer::new(
fr::ResizeAlg::Convolution(fr::FilterType::Lanczos3)
);
resizer.resize(&src_image.view(), &mut dst_view).unwrap();
// Divide RGB channels of destination image by alpha
alpha_mul_div.divide_alpha_inplace(&mut dst_view).unwrap();
// Write destination image as PNG-file
let mut result_buf = BufWriter::new(Vec::new());
PngEncoder::new(&mut result_buf)
.encode(
dst_image.buffer(),
dst_width.get(),
dst_height.get(),
ColorType::Rgba8,
)
.unwrap();
}
use fast_image_resize as fr;
fn main() {
let mut resizer = fr::Resizer::new(
fr::ResizeAlg::Convolution(fr::FilterType::Lanczos3),
);
unsafe {
resizer.set_cpu_extensions(fr::CpuExtensions::Sse4_1);
}
}