- Added method SrcImageView.set_crop_box_to_fit_dst_size().

- Fixed out-of-bounds error during resize with cropping.
- Refactored `ImageData`.
This commit is contained in:
Kirill Kuzminykh
2021-08-28 17:12:53 +03:00
parent 638064223e
commit c5d527704c
12 changed files with 424 additions and 194 deletions
+6
View File
@@ -1,5 +1,11 @@
## [Unreleased] - ReleaseDate
- Added method `SrcImageView.set_crop_box_to_fit_dst_size()`.
- Fixed out-of-bounds error during resize with cropping.
- Refactored `ImageData`.
- Added methods: `from_vec_u32()`, `from_vec_u8()`, `from_slice_u32()`,
`from_slice_u8()`.
- Removed methods: `from_buffer()`, `from_pixels()`.
## [0.2.0] - 2021-08-02
Generated
+70 -41
View File
@@ -21,10 +21,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "739f4a8db6605981345c5654f3a85b056ce52f37a39d34da03f25bf2151ea16e"
[[package]]
name = "anyhow"
version = "1.0.42"
name = "ahash"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "595d3cfa7a60d4555cb5067b99f07142a08ea778de5cf993f7b75c7d8fabc486"
checksum = "43bb833f0bf979d8475d38fbf09ed3b8a55e1885fe93ad3f93239fc6a4f17b98"
dependencies = [
"getrandom",
"once_cell",
"version_check",
]
[[package]]
name = "anyhow"
version = "1.0.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28ae2b3dec75a406790005a200b1bd89785afc02517a00ca99ecfe093ee9e6cf"
[[package]]
name = "argh"
@@ -69,9 +80,9 @@ checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
[[package]]
name = "bitflags"
version = "1.2.1"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bstr"
@@ -177,9 +188,9 @@ dependencies = [
[[package]]
name = "crossbeam-deque"
version = "0.8.0"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94af6efb46fef72616855b036a624cf27ba656ffc9be1b9a3c931cfc7749a9a9"
checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e"
dependencies = [
"cfg-if",
"crossbeam-epoch",
@@ -337,11 +348,11 @@ checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
[[package]]
name = "fallible_collections"
version = "0.4.2"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ad9169582543d2cfe9961be1e9eaf4fc42f9aa3483f7c485717b8dde36466ea"
checksum = "eaefd4190151d458f16f0793d3452d7f13aeb3701566a4cefc4c37598876cc00"
dependencies = [
"hashbrown",
"hashbrown 0.11.2",
]
[[package]]
@@ -389,9 +400,9 @@ dependencies = [
[[package]]
name = "git2"
version = "0.13.20"
version = "0.13.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9831e983241f8c5591ed53f17d874833e2fa82cac2625f3888c50cbfe136cba"
checksum = "659cd14835e75b64d9dba5b660463506763cf0aa6cb640aeeb0e98d841093490"
dependencies = [
"bitflags",
"libc",
@@ -429,7 +440,16 @@ version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04"
dependencies = [
"ahash",
"ahash 0.4.7",
]
[[package]]
name = "hashbrown"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
dependencies = [
"ahash 0.7.4",
]
[[package]]
@@ -438,7 +458,7 @@ version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d99cf782f0dc4372d26846bec3de7804ceb5df083c2d4462c0b8d2330e894fa8"
dependencies = [
"hashbrown",
"hashbrown 0.9.1",
]
[[package]]
@@ -500,15 +520,15 @@ dependencies = [
[[package]]
name = "itoa"
version = "0.4.7"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736"
checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4"
[[package]]
name = "jobserver"
version = "0.1.22"
version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "972f5ae5d1cb9c6ae417789196c803205313edde988685da5e3aae0827b9e7fd"
checksum = "af25a77299a7f711a01975c35a6a424eb6862092cc2d6c72c4ed6cbc56dfc1fa"
dependencies = [
"libc",
]
@@ -530,15 +550,15 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.98"
version = "0.2.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "320cfe77175da3a483efed4bc0adc1968ca050b098ce4f2f1c13a56626128790"
checksum = "3cb00336871be5ed2c8ed44b60ae9959dc5b9f08539422ed43f09e34ecaeba21"
[[package]]
name = "libgit2-sys"
version = "0.12.21+1.1.0"
version = "0.12.22+1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86271bacd72b2b9e854c3dcfb82efd538f15f870e4c11af66900effb462f6825"
checksum = "89c53ac117c44f7042ad8d8f5681378dfbc6010e49ec2c0d1f11dfedc7a4a1c3"
dependencies = [
"cc",
"libc",
@@ -589,15 +609,15 @@ dependencies = [
[[package]]
name = "matches"
version = "0.1.8"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08"
checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
[[package]]
name = "memchr"
version = "2.4.0"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc"
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
[[package]]
name = "memoffset"
@@ -718,6 +738,12 @@ dependencies = [
"libc",
]
[[package]]
name = "once_cell"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56"
[[package]]
name = "open"
version = "1.7.1"
@@ -874,9 +900,9 @@ dependencies = [
[[package]]
name = "redox_syscall"
version = "0.2.9"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ab49abadf3f9e1c4bc499e8845e152ad87d2ad2d30371841171169e9d75feee"
checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff"
dependencies = [
"bitflags",
]
@@ -960,18 +986,18 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "serde"
version = "1.0.126"
version = "1.0.129"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03"
checksum = "d1f72836d2aa753853178eda473a3b9d8e4eefdaf20523b919677e6de489f8f1"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.126"
version = "1.0.129"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "963a7dbc9895aeac7ac90e74f34a5d5261828f79df35cbed41e10189d3804d43"
checksum = "e57ae87ad533d9a56427558b516d0adac283614e347abf85b0dc0cbbf0a249f3"
dependencies = [
"proc-macro2",
"quote",
@@ -980,9 +1006,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.65"
version = "1.0.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28c5e91e4240b46c4c19219d6cc84784444326131a4210f496f948d5cc827a29"
checksum = "336b10da19a12ad094b59d870ebde26a45402e5b470add4b5fd03c5048a32127"
dependencies = [
"itoa",
"ryu",
@@ -1023,9 +1049,9 @@ checksum = "3bdb25a4593d6656239319426f4025f7a658157e25e89f0e0319d7516d46042d"
[[package]]
name = "syn"
version = "1.0.74"
version = "1.0.75"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1873d832550d4588c3dbc20f01361ab00bfe741048f71e3fecf145a7cc18b29c"
checksum = "b7f58f7e8eaa0009c5fec437aabf511bd9933e4b2d7407bd05273c01a8906ea7"
dependencies = [
"proc-macro2",
"quote",
@@ -1118,12 +1144,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
[[package]]
name = "unicode-bidi"
version = "0.3.5"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eeb8be209bb1c96b7c177c7420d26e04eccacb0eeae6b980e35fcb74678107e0"
dependencies = [
"matches",
]
checksum = "246f4c42e67e7a4e3c6106ff716a5d067d4132a642840b242e357e468a2a0085"
[[package]]
name = "unicode-normalization"
@@ -1170,6 +1193,12 @@ version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "version_check"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
[[package]]
name = "wasi"
version = "0.10.2+wasi-snapshot-preview1"
+58 -44
View File
@@ -21,7 +21,7 @@ Environment:
- RAM: DDR4 3000 MHz
- Ubuntu 20.04 (linux 5.8)
- Rust 1.54
- fast_image_resize = "0.1"
- fast_image_resize = "0.3"
- glassbench = "0.3.0"
Other Rust libraries used to compare of resizing speed:
@@ -45,21 +45,21 @@ Pipeline:
| | Nearest | Bilinear | CatmullRom | Lanczos3 |
|------------|:-------:|:--------:|:----------:|:--------:|
| image | 105.194 | 198.359 | 289.810 | 381.861 |
| resize | 16.186 | 71.964 | 132.501 | 192.672 |
| fir rust | 0.476 | 55.284 | 84.732 | 117.519 |
| fir sse4.1 | - | 11.848 | 17.864 | 25.978 |
| fir avx2 | - | 8.753 | 12.569 | 18.112 |
| image | 106.303 | 199.011 | 291.423 | 382.793 |
| resize | 16.099 | 71.947 | 132.481 | 205.961 |
| fir rust | 0.478 | 55.376 | 84.567 | 117.269 |
| fir sse4.1 | - | 11.847 | 17.823 | 25.459 |
| fir avx2 | - | 8.598 | 11.864 | 17.549 |
Compiled with `rustflags = ["-C", "target-cpu=native"]`
| | Nearest | Bilinear | CatmullRom | Lanczos3 |
|------------|:-------:|:--------:|:----------:|:--------:|
| image | 91.138 | 182.251 | 279.838 | 380.956 |
| resize | 15.967 | 62.125 | 114.870 | 168.013 |
| fir rust | 0.467 | 56.376 | 61.565 | 85.433 |
| fir sse4.1 | - | 11.229 | 16.503 | 23.465 |
| fir avx2 | - | 8.464 | 11.602 | 17.076 |
| image | 91.787 | 182.502 | 282.821 | 385.492 |
| resize | 15.908 | 62.011 | 114.741 | 167.815 |
| fir rust | 0.471 | 56.330 | 61.589 | 85.443 |
| fir sse4.1 | - | 11.204 | 16.510 | 23.442 |
| fir avx2 | - | 8.402 | 11.550 | 16.987 |
### Resize RGBA image 4928x3279 => 852x567
@@ -72,23 +72,25 @@ Pipeline:
| | Nearest | Bilinear | CatmullRom | Lanczos3 |
|------------|:-------:|:--------:|:----------:|:--------:|
| image | 101.499 | 216.029 | 335.151 | 455.582 |
| resize | 19.671 | 85.199 | 155.205 | 224.660 |
| fir rust | 13.583 | 67.031 | 94.816 | 124.344 |
| fir sse4.1 | 12.012 | 23.370 | 29.345 | 36.925 |
| fir avx2 | 6.893 | 15.162 | 18.959 | 24.518 |
| image | 102.564 | 205.981 | 309.851 | 423.252 |
| resize | 19.003 | 92.075 | 169.876 | 247.993 |
| fir rust | 13.619 | 67.097 | 97.157 | 125.865 |
| fir sse4.1 | 12.182 | 23.555 | 29.506 | 37.118 |
| fir avx2 | 6.931 | 15.052 | 18.391 | 23.812 |
Compiled with `rustflags = ["-C", "target-cpu=native"]`
| | Nearest | Bilinear | CatmullRom | Lanczos3 |
|------------|:-------:|:--------:|:----------:|:--------:|
| image | 90.750 | 186.545 | 284.383 | 392.403 |
| resize | 19.918 | 69.129 | 130.433 | 191.987 |
| fir rust | 9.957 | 68.817 | 76.454 | 103.521 |
| fir sse4.1 | 7.865 | 18.407 | 23.586 | 30.649 |
| fir avx2 | 6.882 | 14.847 | 18.026 | 23.450 |
| image | 90.421 | 193.379 | 306.635 | 422.674 |
| resize | 19.572 | 69.091 | 130.327 | 192.235 |
| fir rust | 9.987 | 69.005 | 76.551 | 103.646 |
| fir sse4.1 | 7.860 | 18.375 | 23.601 | 30.603 |
| fir avx2 | 6.897 | 14.907 | 18.034 | 23.677 |
## Example
## Examples
### Resize image
```rust
use std::io::BufWriter;
@@ -109,38 +111,36 @@ fn resize_image_example() {
.unwrap();
let width = NonZeroU32::new(img.width()).unwrap();
let height = NonZeroU32::new(img.height()).unwrap();
let src_buffer = img.to_rgb8();
let mut src_image = fr::ImageData::from_vec_u8(
width,
height,
img.to_rgba8().into_raw(),
fr::PixelType::U8x4,
)
.unwrap();
// Create immutable view of source image data
let src_view =
fr::SrcImageView::from_buffer(
width,
height,
src_buffer.as_raw(),
fr::PixelType::U8x4
)
// Create MulDiv instance
let alpha_mul_div: fr::MulDiv = Default::default();
// Multiple RGB channels of source image by alpha channel
alpha_mul_div
.multiply_alpha_inplace(&mut src_image.dst_view())
.unwrap();
// Create wrapper that own data of destination image
let dst_width = NonZeroU32::new(1024).unwrap();
let dst_height = NonZeroU32::new(768).unwrap();
let mut dst_image = fr::ImageData::new(
dst_width,
dst_height,
src_view.pixel_type()
);
let mut dst_image = fr::ImageData::new(dst_width, dst_height, src_image.pixel_type());
// Get mutable view of destination image data
let mut dst_view = dst_image.dst_view();
// Create Resizer instance and resize source image
// 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);
let mut resizer = fr::Resizer::new(fr::ResizeAlg::Convolution(fr::FilterType::Lanczos3));
resizer.resize(&src_image.src_view(), &mut dst_view);
// 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());
@@ -150,8 +150,22 @@ fn resize_image_example() {
dst_image.get_buffer(),
dst_width.get(),
dst_height.get(),
ColorType::Rgb8,
ColorType::Rgba8,
)
.unwrap();
}
```
### Change CPU extensions used by resizer
```rust
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);
}
// ...
}
```
+2 -2
View File
@@ -10,10 +10,10 @@ const fn p(r: u8, g: u8, b: u8, a: u8) -> u32 {
// Multiplies by alpha
fn get_src_image(width: NonZeroU32, height: NonZeroU32, pixel: u32) -> ImageData<Vec<u32>> {
fn get_src_image(width: NonZeroU32, height: NonZeroU32, pixel: u32) -> ImageData {
let buf_size = (width.get() * height.get()) as usize;
let buffer = vec![pixel; buf_size];
ImageData::from_pixels(width, height, buffer, PixelType::U8x4).unwrap()
ImageData::from_vec_u32(width, height, buffer, PixelType::U8x4).unwrap()
}
fn multiplies_alpha_avx2(bench: &mut Bench) {
+2 -7
View File
@@ -76,15 +76,10 @@ pub fn bench_downscale_rgb(bench: &mut Bench) {
};
for alg_name in alg_names {
let src_rgba_image = utils::get_big_rgba_image();
let buf: Vec<u32> = src_rgba_image
.as_raw()
.chunks_exact(4)
.map(|p| u32::from_le_bytes([p[0], p[1], p[2], p[3]]))
.collect();
let src_image_data = ImageData::from_pixels(
let src_image_data = ImageData::from_vec_u8(
NonZeroU32::new(src_image.width()).unwrap(),
NonZeroU32::new(src_image.height()).unwrap(),
buf,
src_rgba_image.into_raw(),
PixelType::U8x4,
)
.unwrap();
+2 -7
View File
@@ -83,15 +83,10 @@ pub fn bench_downscale_rgba(bench: &mut Bench) {
"Lanczos3" => ResizeAlg::Convolution(FilterType::Lanczos3),
_ => return,
};
let buf: Vec<u32> = src_image
.as_raw()
.chunks_exact(4)
.map(|p| u32::from_le_bytes([p[0], p[1], p[2], p[3]]))
.collect();
let src_image_data = ImageData::from_pixels(
let src_image_data = ImageData::from_vec_u8(
NonZeroU32::new(src_image.width()).unwrap(),
NonZeroU32::new(src_image.height()).unwrap(),
buf,
src_image.into_raw(),
PixelType::U8x4,
)
.unwrap();
+6 -16
View File
@@ -13,37 +13,27 @@ const NEW_HEIGHT: u32 = 567;
const NEW_BIG_WIDTH: u32 = 4928;
const NEW_BIG_HEIGHT: u32 = 3279;
fn get_big_source_image() -> ImageData<Vec<u32>> {
fn get_big_source_image() -> ImageData {
let img = utils::get_big_rgba_image();
let width = img.width();
let height = img.height();
let buf = img
.as_raw()
.chunks_exact(4)
.map(|p| u32::from_le_bytes([p[0], p[1], p[2], p[3]]))
.collect();
ImageData::from_pixels(
ImageData::from_vec_u8(
NonZeroU32::new(width).unwrap(),
NonZeroU32::new(height).unwrap(),
buf,
img.into_raw(),
PixelType::U8x4,
)
.unwrap()
}
fn get_small_source_image() -> ImageData<Vec<u32>> {
fn get_small_source_image() -> ImageData {
let img = utils::get_small_rgba_image();
let width = img.width();
let height = img.height();
let buf = img
.as_raw()
.chunks_exact(4)
.map(|p| u32::from_le_bytes([p[0], p[1], p[2], p[3]]))
.collect();
ImageData::from_pixels(
ImageData::from_vec_u8(
NonZeroU32::new(width).unwrap(),
NonZeroU32::new(height).unwrap(),
buf,
img.into_raw(),
PixelType::U8x4,
)
.unwrap()
+105 -52
View File
@@ -1,30 +1,110 @@
use std::num::NonZeroU32;
use crate::{DstImageView, InvalidBufferSizeError, PixelType, SrcImageView};
use crate::{DstImageView, ImageBufferError, InvalidBufferSizeError, PixelType, SrcImageView};
#[derive(Debug, Clone)]
pub struct ImageData<T: AsRef<[u32]>> {
#[derive(Debug)]
enum PixelsContainer<'a> {
Mut(&'a mut [u32]),
VecU32(Vec<u32>),
VecU8(Vec<u8>),
}
#[derive(Debug)]
pub struct ImageData<'a> {
width: NonZeroU32,
height: NonZeroU32,
pixels: T,
pixels: PixelsContainer<'a>,
pixel_type: PixelType,
}
impl<T: AsRef<[u32]>> ImageData<T> {
pub fn from_pixels(
impl<'a> ImageData<'a> {
pub fn new(width: NonZeroU32, height: NonZeroU32, pixel_type: PixelType) -> Self {
let size = (width.get() * height.get()) as usize;
let pixels = vec![0; size];
Self {
width,
height,
pixels: PixelsContainer::VecU32(pixels),
pixel_type,
}
}
pub fn from_vec_u32(
width: NonZeroU32,
height: NonZeroU32,
pixels: T,
pixels: Vec<u32>,
pixel_type: PixelType,
) -> Result<Self, InvalidBufferSizeError> {
let size = (width.get() * height.get()) as usize;
if pixels.as_ref().len() != size {
if pixels.len() != size {
return Err(InvalidBufferSizeError);
}
Ok(Self {
width,
height,
pixels,
pixels: PixelsContainer::VecU32(pixels),
pixel_type,
})
}
pub fn from_vec_u8(
width: NonZeroU32,
height: NonZeroU32,
mut pixels: Vec<u8>,
pixel_type: PixelType,
) -> Result<Self, ImageBufferError> {
let size = (width.get() * height.get()) as usize * 4;
if pixels.len() != size {
return Err(ImageBufferError::InvalidBufferSize);
}
let (head, _, _) = unsafe { &pixels.align_to_mut::<u32>() };
if !head.is_empty() {
return Err(ImageBufferError::InvalidBufferAlignment);
}
Ok(Self {
width,
height,
pixels: PixelsContainer::VecU8(pixels),
pixel_type,
})
}
pub fn from_slice_u32(
width: NonZeroU32,
height: NonZeroU32,
pixels: &'a mut [u32],
pixel_type: PixelType,
) -> Result<Self, InvalidBufferSizeError> {
let size = (width.get() * height.get()) as usize;
if pixels.len() != size {
return Err(InvalidBufferSizeError);
}
Ok(Self {
width,
height,
pixels: PixelsContainer::Mut(pixels),
pixel_type,
})
}
pub fn from_slice_u8(
width: NonZeroU32,
height: NonZeroU32,
buffer: &'a mut [u8],
pixel_type: PixelType,
) -> Result<Self, ImageBufferError> {
let size = (width.get() * height.get()) as usize * 4;
if buffer.len() != size {
return Err(ImageBufferError::InvalidBufferSize);
}
let (head, pixels, _) = unsafe { buffer.align_to_mut::<u32>() };
if !head.is_empty() {
return Err(ImageBufferError::InvalidBufferAlignment);
}
Ok(Self {
width,
height,
pixels: PixelsContainer::Mut(pixels),
pixel_type,
})
}
@@ -46,64 +126,37 @@ impl<T: AsRef<[u32]>> ImageData<T> {
#[inline(always)]
pub fn get_pixels(&self) -> &[u32] {
self.pixels.as_ref()
match &self.pixels {
PixelsContainer::Mut(p) => p,
PixelsContainer::VecU32(v) => v,
PixelsContainer::VecU8(v) => unsafe { v.align_to::<u32>().1 },
}
}
#[inline(always)]
pub fn get_buffer(&self) -> &[u8] {
let (_, buffer, _) = unsafe { self.pixels.as_ref().align_to::<u8>() };
let pixels = self.get_pixels();
let (_, buffer, _) = unsafe { pixels.align_to::<u8>() };
buffer
}
#[inline(always)]
pub fn src_view(&self) -> SrcImageView {
let pixels = self.pixels.as_ref();
let pixels = self.get_pixels();
let rows = pixels.chunks(self.width.get() as usize).collect();
SrcImageView::from_rows(self.width, self.height, rows, self.pixel_type).unwrap()
}
}
impl<T: AsRef<[u32]> + AsMut<[u32]>> ImageData<T> {
#[inline(always)]
pub fn dst_view(&mut self) -> DstImageView {
let pixels = self.pixels.as_mut();
let rows = pixels.chunks_mut(self.width.get() as usize).collect();
let rows = match &mut self.pixels {
PixelsContainer::Mut(p) => p.chunks_mut(self.width.get() as usize).collect(),
PixelsContainer::VecU32(v) => v.chunks_mut(self.width.get() as usize).collect(),
PixelsContainer::VecU8(v) => {
let p = unsafe { v.align_to_mut::<u32>().1 };
p.chunks_mut(self.width.get() as usize).collect()
}
};
DstImageView::from_rows(self.width, self.height, rows, self.pixel_type).unwrap()
}
}
impl ImageData<Vec<u32>> {
pub fn new(width: NonZeroU32, height: NonZeroU32, pixel_type: PixelType) -> Self {
let size = (width.get() * height.get()) as usize;
let pixels = vec![0; size];
Self {
width,
height,
pixels,
pixel_type,
}
}
/// Copy content of given `buffer` into internal `Vec<u32>`
pub fn from_buffer(
width: NonZeroU32,
height: NonZeroU32,
buffer: &[u8],
pixel_type: PixelType,
) -> Result<Self, InvalidBufferSizeError> {
let size = (width.get() * height.get()) as usize * 4;
if buffer.len() != size {
return Err(InvalidBufferSizeError);
}
let pixels = buffer
.chunks_exact(4)
.map(|p| u32::from_le_bytes([p[0], p[1], p[2], p[3]]))
.collect();
Ok(Self {
width,
height,
pixels,
pixel_type,
})
}
}
+62
View File
@@ -142,6 +142,68 @@ impl<'a> SrcImageView<'a> {
Ok(())
}
/// Set a crop box to resize the source image into the
/// aspect ratio of destination image without distortions.
///
/// `centering` used to control the cropping position. Use (0.5, 0.5) for
/// center cropping (e.g. if cropping the width, take 50% off
/// of the left side, and therefore 50% off the right side).
/// (0.0, 0.0) will crop from the top left corner (i.e. if
/// cropping the width, take all of the crop off of the right
/// side, and if cropping the height, take all of it off the
/// bottom). (1.0, 0.0) will crop from the bottom left
/// corner, etc. (i.e. if cropping the width, take all of the
/// crop off the left side, and if cropping the height take
/// none from the top, and therefore all off the bottom).
pub fn set_crop_box_to_fit_dst_size(
&mut self,
dst_width: NonZeroU32,
dst_height: NonZeroU32,
centering: Option<(f32, f32)>,
) {
// This function based on code of ImageOps.fit() from Pillow package.
// https://github.com/python-pillow/Pillow/blob/master/src/PIL/ImageOps.py
let centering = if let Some((x, y)) = centering {
(x.clamp(0.0, 1.0), y.clamp(0.0, 1.0))
} else {
(0.5, 0.5)
};
// calculate aspect ratios
let width = self.width.get() as f32;
let height = self.height.get() as f32;
let image_ratio = width / height;
let required_ration = dst_width.get() as f32 / dst_height.get() as f32;
let crop_width;
let crop_height;
// figure out if the sides or top/bottom will be cropped off
if (image_ratio - required_ration).abs() < f32::EPSILON {
// The image is already the needed ratio
crop_width = width;
crop_height = height;
} else if image_ratio >= required_ration {
// The image is wider than what's needed, crop the sides
crop_width = required_ration * height;
crop_height = height;
} else {
// The image is taller than what's needed, crop the top and bottom
crop_width = width;
crop_height = width / required_ration;
}
let crop_left = (width - crop_width) * centering.0;
let crop_top = (height - crop_height) * centering.1;
self.set_crop_box(CropBox {
left: crop_left.round() as u32,
top: crop_top.round() as u32,
width: NonZeroU32::new(crop_width.round() as u32).unwrap(),
height: NonZeroU32::new(crop_height.round() as u32).unwrap(),
})
.unwrap();
}
#[inline(always)]
pub fn get_buffer(&self) -> Vec<u8> {
let row_size = self.width.get() as usize;
+8 -8
View File
@@ -147,13 +147,13 @@ fn get_temp_image_from_buffer(
width: NonZeroU32,
height: NonZeroU32,
pixel_type: PixelType,
) -> ImageData<&mut [u32]> {
) -> ImageData {
let buf_size = (width.get() * height.get()) as usize;
if buffer.len() < buf_size {
buffer.resize(buf_size, 0);
}
let pixels = &mut buffer[0..buf_size];
ImageData::from_pixels(width, height, pixels, pixel_type).unwrap()
ImageData::from_slice_u32(width, height, pixels, pixel_type).unwrap()
}
fn resample_nearest(src_image: &SrcImageView, dst_image: &mut DstImageView) {
@@ -221,12 +221,6 @@ fn resample_convolution(
let y_first = vert_coeffs.bounds[0].start;
if need_vertical {
// Shift bounds for vertical pass
vert_coeffs
.bounds
.iter_mut()
.for_each(|b| b.start -= y_first);
// Last used row in the source image
let last_y_bound = vert_coeffs.bounds.last().unwrap();
let y_last = last_y_bound.start + last_y_bound.size;
@@ -244,6 +238,12 @@ fn resample_convolution(
y_first,
horiz_coeffs,
);
// Shift bounds for vertical pass
vert_coeffs
.bounds
.iter_mut()
.for_each(|b| b.start -= y_first);
resampler.vert_convolution(&temp_image.src_view(), dst_image, vert_coeffs);
} else {
resampler.horiz_convolution(src_image, dst_image, y_first, horiz_coeffs);
+69
View File
@@ -0,0 +1,69 @@
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::ImageData::from_vec_u8(
width,
height,
img.to_rgba8().into_raw(),
fr::PixelType::U8x4,
)
.unwrap();
// Create MulDiv instance
let alpha_mul_div: fr::MulDiv = Default::default();
// Multiple RGB channels of source image by alpha channel
alpha_mul_div
.multiply_alpha_inplace(&mut src_image.dst_view())
.unwrap();
// Create wrapper that own data of destination image
let dst_width = NonZeroU32::new(1024).unwrap();
let dst_height = NonZeroU32::new(768).unwrap();
let mut dst_image = fr::ImageData::new(dst_width, dst_height, src_image.pixel_type());
// Get mutable view of destination image data
let mut dst_view = dst_image.dst_view();
// 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.src_view(), &mut dst_view);
// 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());
let encoder = PngEncoder::new(&mut result_buf);
encoder
.encode(
dst_image.get_buffer(),
dst_width.get(),
dst_height.get(),
ColorType::Rgba8,
)
.unwrap();
}
fn main() {
let mut resizer = fr::Resizer::new(fr::ResizeAlg::Convolution(fr::FilterType::Lanczos3));
unsafe {
resizer.set_cpu_extensions(fr::CpuExtensions::Sse4_1);
}
// ...
}
+34 -17
View File
@@ -5,38 +5,37 @@ use image::codecs::png::PngEncoder;
use image::io::Reader as ImageReader;
use image::{ColorType, GenericImageView};
use fast_image_resize::ImageData;
use fast_image_resize::{CpuExtensions, FilterType, PixelType, ResizeAlg, Resizer, SrcImageView};
use fast_image_resize::{
CpuExtensions, FilterType, ImageData, PixelType, ResizeAlg, Resizer, SrcImageView,
};
fn get_source_image() -> ImageData<Vec<u32>> {
let img = ImageReader::open("./data/nasa-4928x3279.png")
fn get_source_image() -> ImageData<'static> {
let img = ImageReader::open("/home/cykooz/t.png")
.unwrap()
.decode()
.unwrap();
let width = img.width();
let height = img.height();
let rgba = img.to_rgba8();
ImageData::from_buffer(
ImageData::from_vec_u8(
NonZeroU32::new(width).unwrap(),
NonZeroU32::new(height).unwrap(),
rgba.as_raw(),
img.to_rgba8().into_raw(),
PixelType::U8x4,
)
.unwrap()
}
fn get_small_source_image() -> ImageData<Vec<u32>> {
fn get_small_source_image() -> ImageData<'static> {
let img = ImageReader::open("./data/nasa-852x567.png")
.unwrap()
.decode()
.unwrap();
let width = img.width();
let height = img.height();
let rgba = img.to_rgba8();
ImageData::from_buffer(
ImageData::from_vec_u8(
NonZeroU32::new(width).unwrap(),
NonZeroU32::new(height).unwrap(),
rgba.as_raw(),
img.to_rgba8().into_raw(),
PixelType::U8x4,
)
.unwrap()
@@ -65,7 +64,7 @@ fn save_result(image: &SrcImageView, name: &str) {
}
#[test]
fn resample_wo_simd_lanczos3_test() {
fn resize_wo_simd_lanczos3_test() {
let image = get_source_image();
let mut resizer = Resizer::new(ResizeAlg::Convolution(FilterType::Lanczos3));
unsafe {
@@ -82,7 +81,7 @@ fn resample_wo_simd_lanczos3_test() {
}
#[test]
fn resample_sse4_lanczos3_test() {
fn resize_sse4_lanczos3_test() {
let image = get_source_image();
let mut resizer = Resizer::new(ResizeAlg::Convolution(FilterType::Lanczos3));
unsafe {
@@ -99,7 +98,7 @@ fn resample_sse4_lanczos3_test() {
}
#[test]
fn resample_avx2_lanczos3_test() {
fn resize_avx2_lanczos3_test() {
let image = get_source_image();
let mut resizer = Resizer::new(ResizeAlg::Convolution(FilterType::Lanczos3));
unsafe {
@@ -116,7 +115,7 @@ fn resample_avx2_lanczos3_test() {
}
#[test]
fn resample_avx2_lanczos3_upscale_test() {
fn resize_avx2_lanczos3_upscale_test() {
let image = get_small_source_image();
let mut resizer = Resizer::new(ResizeAlg::Convolution(FilterType::Lanczos3));
unsafe {
@@ -133,7 +132,7 @@ fn resample_avx2_lanczos3_upscale_test() {
}
#[test]
fn resample_nearest_test() {
fn resize_nearest_test() {
let image = get_source_image();
let mut resizer = Resizer::new(ResizeAlg::Nearest);
unsafe {
@@ -150,7 +149,7 @@ fn resample_nearest_test() {
}
#[test]
fn resample_super_sampling_test() {
fn resize_super_sampling_test() {
let image = get_source_image();
let mut resizer = Resizer::new(ResizeAlg::SuperSampling(FilterType::Lanczos3, 2));
unsafe {
@@ -165,3 +164,21 @@ fn resample_super_sampling_test() {
resizer.resize(&image.src_view(), &mut result.dst_view());
save_result(&result.src_view(), "super_sampling_avx2");
}
#[test]
fn resize_with_cropping() {
let src_image = get_source_image();
let mut resizer = Resizer::new(ResizeAlg::Convolution(FilterType::Lanczos3));
unsafe {
resizer.set_cpu_extensions(CpuExtensions::None);
}
let mut dst_image = ImageData::new(
NonZeroU32::new(256).unwrap(),
NonZeroU32::new(256).unwrap(),
src_image.pixel_type(),
);
let mut src_view = src_image.src_view();
src_view.set_crop_box_to_fit_dst_size(dst_image.width(), dst_image.height(), None);
resizer.resize(&src_view, &mut dst_image.dst_view());
save_result(&dst_image.src_view(), "cropping_lanczos3_avx2");
}