#image #resize

fast_image_resize

Library for fast image resizing with using of SIMD instructions

16 releases (8 breaking)

Uses new Rust 2021

new 0.9.2 May 19, 2022
0.8.0 Mar 23, 2022
0.5.3 Dec 13, 2021
0.5.2 Nov 26, 2021
0.1.0 Jul 31, 2021

#67 in Hardware support

Download history 591/week @ 2022-01-29 681/week @ 2022-02-05 718/week @ 2022-02-12 608/week @ 2022-02-19 787/week @ 2022-02-26 497/week @ 2022-03-05 590/week @ 2022-03-12 739/week @ 2022-03-19 771/week @ 2022-03-26 789/week @ 2022-04-02 675/week @ 2022-04-09 653/week @ 2022-04-16 767/week @ 2022-04-23 538/week @ 2022-04-30 527/week @ 2022-05-07 734/week @ 2022-05-14

2,725 downloads per month
Used in 3 crates

MIT/Apache

285KB
6K SLoC

fast_image_resize

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.

CHANGELOG

Supported pixel formats and available optimisations:

  • U8 - one u8 component per pixel:
    • native Rust-code without forced SIMD
    • SSE4.1 (partial)
    • AVX2
  • U8x2 - two u8 components per pixel (e.g. LA):
    • native Rust-code without forced SIMD
    • SSE4.1
    • AVX2
  • U8x3 - three u8 components per pixel (e.g. RGB):
    • native Rust-code without forced SIMD
    • SSE4.1 (partial)
    • AVX2
  • U8x4 - four u8 components per pixel (RGBA, RGBx, CMYK and other):
    • native Rust-code without forced SIMD
    • SSE4.1
    • AVX2
  • U16x3 - three u16 components per pixel (e.g. RGB):
    • native Rust-code without forced SIMD
    • SSE4.1
    • AVX2
  • I32 - one i32 component per pixel:
    • native Rust-code without forced SIMD
  • F32 - one f32 component per pixel:
    • native Rust-code without forced SIMD

Benchmarks

Environment:

  • CPU: AMD Ryzen 9 5950X
  • RAM: DDR4 3800 MHz
  • Ubuntu 22.04 (linux 5.15.0)
  • Rust 1.61.0
  • fast_image_resize = "0.9.2"
  • glassbench = "0.3.1"
  • rustflags = ["-C", "llvm-args=-x86-branches-within-32B-boundaries"]

Other Rust libraries used to compare of resizing speed:

Resize algorithms:

  • Nearest
  • Convolution with Bilinear filter
  • Convolution with CatmullRom filter
  • Convolution with Lanczos3 filter

Resize RGB8 image (U8x3) 4928x3279 => 852x567

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 18.25 79.66 138.48 189.00
resize - 50.41 97.00 142.89
fir rust 0.26 37.79 64.21 93.78
fir sse4.1 0.26 26.07 39.62 53.22
fir avx2 0.26 6.95 8.87 12.68

Resize RGBA8 image (U8x4) 4928x3279 => 852x567

Pipeline:

src_image => multiply by alpha => resize => divide by alpha => dst_image

Nearest Bilinear CatmullRom Lanczos3
image 18.32 75.46 128.47 181.34
resize - 49.26 94.38 138.48
fir rust 0.17 33.67 47.63 67.37
fir sse4.1 0.17 12.21 15.89 20.61
fir avx2 0.17 9.18 11.46 15.26

Resize grayscale image (U8) 4928x3279 => 852x567

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 15.05 44.65 70.26 95.61
resize - 16.88 32.98 56.31
fir rust 0.14 13.21 14.98 21.86
fir sse4.1 0.14 11.22 11.23 16.46
fir avx2 0.14 6.03 4.41 7.37

Resize grayscale image with alpha channel (U8x2) 4928x3279 => 852x567

Pipeline:

src_image => multiply by alpha => resize => divide by alpha => dst_image

  • Source image nasa-4928x3279-rgba.png has converted into grayscale image with alpha channel (two bytes per pixel).
  • Numbers in table is mean duration of image resizing in milliseconds.
  • The resize crate does not support this pixel format.
Nearest Bilinear CatmullRom Lanczos3
image 16.41 61.23 111.31 149.96
fir rust 0.15 23.67 27.94 38.91
fir sse4.1 0.15 11.66 13.33 16.44
fir avx2 0.15 10.33 11.43 14.12

Resize RGB16 image (U16x3) 4928x3279 => 852x567

Pipeline:

src_image => resize => dst_image

  • Source image nasa-4928x3279.png has converted into RGB16 image.
  • Numbers in table is mean duration of image resizing in milliseconds.
Nearest Bilinear CatmullRom Lanczos3
image 17.54 72.24 126.80 176.03
resize - 52.51 100.40 147.31
fir rust 0.31 39.56 65.65 92.90
fir sse4.1 0.31 22.14 35.69 50.40
fir avx2 0.31 19.12 28.25 33.86

Examples

Resize RGBA8 image

use std::io::BufWriter;
use std::num::NonZeroU32;

use image::codecs::png::PngEncoder;
use image::io::Reader as ImageReader;
use image::{ColorType, ImageEncoder};

use fast_image_resize as fr;

fn main() {
    // 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 
    // (not required for the Nearest algorithm) 
    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)
        .write_image(
            dst_image.buffer(),
            dst_width.get(),
            dst_height.get(),
            ColorType::Rgba8,
        )
        .unwrap();
}

Resize with cropping

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;

fn resize_image_with_cropping(
    mut src_view: fr::ImageView,
    dst_width: NonZeroU32,
    dst_height: NonZeroU32
) -> fr::Image {
    // Set cropping parameters
    src_view.set_crop_box_to_fit_dst_size(dst_width, dst_height, None);

    // Create container for data of destination image
    let mut dst_image = fr::Image::new(
        dst_width,
        dst_height,
        src_view.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_view, &mut dst_view).unwrap();

    dst_image
}

fn main() {
    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 src_image = fr::Image::from_vec_u8(
        width,
        height,
        img.to_rgb8().into_raw(),
        fr::PixelType::U8x3,
    ).unwrap();
    resize_image_with_cropping(
        src_image.view(),
        NonZeroU32::new(1024).unwrap(),
        NonZeroU32::new(768).unwrap(),
    );
}

Change CPU extensions used by resizer

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);
    }
}

Dependencies

~0.4–0.8MB
~19K SLoC