mirror of
https://github.com/Cykooz/fast_image_resize.git
synced 2026-10-07 17:01:09 +00:00
Write new benches for WASM32 to run them in headless browser.
This commit is contained in:
Generated
+194
@@ -111,6 +111,17 @@ version = "0.7.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f02882884d3e1bc524fb12c79f107f6ad0e1cfd498c536ffb494301740995dfe"
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.89"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.5.1"
|
||||
@@ -140,6 +151,22 @@ dependencies = [
|
||||
"arrayvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "benches_wasm"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"console_error_panic_hook",
|
||||
"fast_image_resize",
|
||||
"gloo-timers",
|
||||
"image",
|
||||
"js-sys",
|
||||
"resize",
|
||||
"rgb",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bit_field"
|
||||
version = "0.10.3"
|
||||
@@ -327,6 +354,16 @@ version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
|
||||
|
||||
[[package]]
|
||||
name = "console_error_panic_hook"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.3.0"
|
||||
@@ -530,9 +567,11 @@ dependencies = [
|
||||
"fast_image_resize",
|
||||
"image",
|
||||
"itertools 0.15.0",
|
||||
"js-sys",
|
||||
"libvips",
|
||||
"nix",
|
||||
"num-traits",
|
||||
"pastey",
|
||||
"png 0.18.1",
|
||||
"rayon",
|
||||
"resize",
|
||||
@@ -542,6 +581,9 @@ dependencies = [
|
||||
"tera",
|
||||
"thiserror 2.0.18",
|
||||
"walkdir",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-test",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -569,6 +611,39 @@ dependencies = [
|
||||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-channel"
|
||||
version = "0.3.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d"
|
||||
|
||||
[[package]]
|
||||
name = "futures-task"
|
||||
version = "0.3.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393"
|
||||
|
||||
[[package]]
|
||||
name = "futures-util"
|
||||
version = "0.3.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-task",
|
||||
"pin-project-lite",
|
||||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.17"
|
||||
@@ -615,6 +690,18 @@ dependencies = [
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gloo-timers"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "482ce8a491a501da4cd806bd190275363d674f2845005c6ddbd5d3e1dd54495d"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "half"
|
||||
version = "2.7.1"
|
||||
@@ -778,6 +865,17 @@ version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00810f1d8b74be64b13dbf3db89ac67740615d6c891f0e7b6179326533011a07"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.103"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"futures-util",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lebe"
|
||||
version = "0.5.3"
|
||||
@@ -853,6 +951,16 @@ version = "2.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4"
|
||||
|
||||
[[package]]
|
||||
name = "minicov"
|
||||
version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4869b6a491569605d66d3952bcdf03df789e5b536e5f0cf7758a7f08a55ae24d"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.8.9"
|
||||
@@ -896,6 +1004,15 @@ version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8"
|
||||
|
||||
[[package]]
|
||||
name = "nu-ansi-term"
|
||||
version = "0.50.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-bigint"
|
||||
version = "0.4.6"
|
||||
@@ -971,6 +1088,18 @@ version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
||||
|
||||
[[package]]
|
||||
name = "pastey"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2ee67f1008b1ba2321834326597b8e186293b049a023cdef258527550b9935b4"
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.33"
|
||||
@@ -1357,6 +1486,12 @@ dependencies = [
|
||||
"quote",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.15.2"
|
||||
@@ -1572,6 +1707,16 @@ dependencies = [
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-futures"
|
||||
version = "0.4.76"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c62df1340f32221cb9c54d6a27b030e3dba64361d4a95bed55f9aacb44da291d"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.126"
|
||||
@@ -1604,6 +1749,55 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-test"
|
||||
version = "0.3.76"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a0d555ca874445df8d314f94f5c948a4e74e5418f332c89f660a3d8310a96f4"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"cast",
|
||||
"js-sys",
|
||||
"libm",
|
||||
"minicov",
|
||||
"nu-ansi-term",
|
||||
"num-traits",
|
||||
"oorandom",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"wasm-bindgen-test-macro",
|
||||
"wasm-bindgen-test-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-test-macro"
|
||||
version = "0.3.76"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94eb68555b95bcea5e8cf4abe280b529049479fa995bfc23734af96a6aedc120"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-test-shared"
|
||||
version = "0.2.126"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c31d56021e873866c968588ed85ccdf56db5c426e44afdb4618c39895104b920"
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.103"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8622dcb61c0bcc9fffa6938bed81210af2da9a7e4a1a834b2e37a59b6dfb6141"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "weezl"
|
||||
version = "0.1.12"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
[workspace]
|
||||
members = [
|
||||
"resizer",
|
||||
"benches_wasm",
|
||||
]
|
||||
|
||||
|
||||
@@ -72,6 +73,14 @@ criterion = { version = "0.7.0", default-features = false, features = ["cargo_be
|
||||
tera = { version = "2.0.0-alpha.9", features = ["glob_fs"] }
|
||||
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
|
||||
wasm-bindgen-test = "0.3.76"
|
||||
js-sys = "0.3.103"
|
||||
wasm-bindgen = "0.2.126"
|
||||
web-sys = { version = "0.3.103", features = ["Document", "Element", "HtmlElement", "Node", "Window", "Request", "RequestInit", "RequestMode", "Response", "Performance"] }
|
||||
pastey = "0.2.3"
|
||||
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
|
||||
nix = { version = "0.31.3", default-features = false, features = ["sched"] }
|
||||
|
||||
|
||||
+11
-11
@@ -75,18 +75,18 @@ fn downscale_bench(
|
||||
|
||||
pub fn resize_in_one_dimension_bench(bench_group: &mut utils::BenchGroup) {
|
||||
let pixel_types = [
|
||||
PixelType::U8,
|
||||
PixelType::U8x2,
|
||||
PixelType::U8x3,
|
||||
PixelType::U8x4,
|
||||
PixelType::U16,
|
||||
PixelType::U16x2,
|
||||
// PixelType::U8,
|
||||
// PixelType::U8x2,
|
||||
// PixelType::U8x3,
|
||||
// PixelType::U8x4,
|
||||
// PixelType::U16,
|
||||
// PixelType::U16x2,
|
||||
PixelType::U16x3,
|
||||
PixelType::U16x4,
|
||||
PixelType::F32,
|
||||
PixelType::F32x2,
|
||||
PixelType::F32x3,
|
||||
PixelType::F32x4,
|
||||
// PixelType::U16x4,
|
||||
// PixelType::F32,
|
||||
// PixelType::F32x2,
|
||||
// PixelType::F32x3,
|
||||
// PixelType::F32x4,
|
||||
];
|
||||
let mut cpu_extensions = vec![CpuExtensions::None];
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
#![cfg(target_arch = "wasm32")]
|
||||
|
||||
use fast_image_resize::pixels::{
|
||||
F32x2, F32x3, F32x4, U16x2, U16x3, U16x4, U8x2, U8x3, U8x4, F32, U16, U8,
|
||||
};
|
||||
use pastey::paste;
|
||||
use resize::Pixel::{Gray16, Gray8, GrayF32, RGB16, RGB8, RGBA16P, RGBA8P, RGBF32};
|
||||
use rgb::FromSlice;
|
||||
use wasm_bindgen_test::{wasm_bindgen_bench, wasm_bindgen_test_configure, Criterion};
|
||||
|
||||
use crate::wasm_utils::load_images::{load_big_image, load_big_src_image};
|
||||
use crate::wasm_utils::resize_functions::*;
|
||||
mod wasm_utils;
|
||||
|
||||
// Run in a browser.
|
||||
wasm_bindgen_test_configure!(run_in_browser);
|
||||
|
||||
fn get_bencher() -> Criterion {
|
||||
Criterion::default()
|
||||
.with_location(file!(), module_path!())
|
||||
.sample_size(10)
|
||||
}
|
||||
|
||||
macro_rules! image_benches {
|
||||
{$(($pixel_type:ty, $suffix:ident)),+} => {
|
||||
$(
|
||||
paste! {
|
||||
#[wasm_bindgen_bench]
|
||||
async fn [<bench_image_ $suffix>](_: &mut Criterion) {
|
||||
let src_image = load_big_image::<$pixel_type>().await.unwrap();
|
||||
image_resize(&mut get_bencher(), &src_image);
|
||||
}
|
||||
}
|
||||
)+
|
||||
}
|
||||
}
|
||||
|
||||
image_benches! {
|
||||
(U8, l8),
|
||||
(U16, l16),
|
||||
(F32, l32f),
|
||||
(U8x3, rgb8),
|
||||
(U16x3, rgb16),
|
||||
(F32x3, rgb32f)
|
||||
}
|
||||
|
||||
macro_rules! resize_benches {
|
||||
{$(($pixel_type:ty, $resize_pixel_type:expr, $resize_as_method:ident, $suffix:ident)),+} => {
|
||||
$(
|
||||
paste! {
|
||||
#[wasm_bindgen_bench]
|
||||
async fn [<bench_resize_ $suffix>](_: &mut Criterion) {
|
||||
let src_image = load_big_image::<$pixel_type>().await.unwrap();
|
||||
resize_resize(
|
||||
&mut get_bencher(),
|
||||
$resize_pixel_type,
|
||||
src_image.as_raw().$resize_as_method(),
|
||||
src_image.width(),
|
||||
src_image.height(),
|
||||
);
|
||||
}
|
||||
}
|
||||
)+
|
||||
}
|
||||
}
|
||||
|
||||
resize_benches! {
|
||||
(U8, Gray8, as_gray, l),
|
||||
(U16, Gray16, as_gray, l16),
|
||||
(F32, GrayF32, as_gray, l32f),
|
||||
(U8x3, RGB8, as_rgb, rgb),
|
||||
(U16x3, RGB16, as_rgb, rgb16),
|
||||
(F32x3, RGBF32, as_rgb, rgb32f),
|
||||
(U8x4, RGBA8P, as_rgba, rgba),
|
||||
(U16x4, RGBA16P, as_rgba, rgba16)
|
||||
}
|
||||
|
||||
macro_rules! fir_benches {
|
||||
{$(($pixel_type:ty, $use_alpha:expr, $suffix:ident)),+} => {
|
||||
$(
|
||||
paste! {
|
||||
#[wasm_bindgen_bench]
|
||||
async fn [<bench_fir_ $suffix>](_: &mut Criterion) {
|
||||
let src_image = load_big_src_image::<$pixel_type>().await.unwrap();
|
||||
fir_resize::<$pixel_type>(&mut get_bencher(), src_image, $use_alpha);
|
||||
}
|
||||
}
|
||||
)+
|
||||
}
|
||||
}
|
||||
|
||||
fir_benches! {
|
||||
(U8, false, l),
|
||||
(U16, false, l16),
|
||||
(F32, false, l32f),
|
||||
(U8x2, true, la),
|
||||
(U16x2, true, la16),
|
||||
(F32x2, true, la32f),
|
||||
(U8x3, false, rgb),
|
||||
(U16x3, false, rgb16),
|
||||
(F32x3, false, rgb32f),
|
||||
(U8x4, true, rgba),
|
||||
(U16x4, true, rgba16),
|
||||
(F32x4, true, rgba32f)
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
use std::borrow::Cow;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use itertools::Itertools;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BenchResult {
|
||||
pub function_name: String,
|
||||
pub parameter: String,
|
||||
/// Estimate time in nanoseconds
|
||||
pub estimate: f64,
|
||||
}
|
||||
|
||||
static COL_ORDER: [&str; 5] = ["Nearest", "Box", "Bilinear", "Bicubic", "Lanczos3"];
|
||||
|
||||
pub fn build_md_table(bench_results: &[BenchResult]) -> String {
|
||||
let mut row_names: Vec<String> = Vec::new();
|
||||
let mut row_indexes: HashMap<String, usize> = HashMap::new();
|
||||
let mut col_names: Vec<String> = Vec::new();
|
||||
|
||||
for result in bench_results {
|
||||
let row_name = result.function_name.clone();
|
||||
if !row_names.contains(&row_name) {
|
||||
row_names.push(row_name.clone());
|
||||
row_indexes.insert(row_name.clone(), row_names.len() - 1);
|
||||
}
|
||||
let col_name = result.parameter.clone();
|
||||
if !col_names.contains(&col_name) {
|
||||
col_names.push(col_name.clone());
|
||||
}
|
||||
}
|
||||
|
||||
// Reorder columns
|
||||
let mut ordered_pos = 0;
|
||||
for name in COL_ORDER {
|
||||
if let Some((cur_pos, _)) = col_names.iter().find_position(|s| s.as_str() == name) {
|
||||
if cur_pos != ordered_pos {
|
||||
col_names.swap(cur_pos, ordered_pos);
|
||||
}
|
||||
ordered_pos += 1;
|
||||
}
|
||||
}
|
||||
let col_indexes: HashMap<String, usize> = col_names
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, v)| (v.clone(), i))
|
||||
.collect();
|
||||
|
||||
let cols_count = col_names.len();
|
||||
let mut values = vec![Cow::Borrowed("-"); row_names.len() * cols_count];
|
||||
|
||||
for result in bench_results {
|
||||
let row_index = row_indexes.get(&result.function_name).copied();
|
||||
let col_index = col_indexes.get(&result.parameter).copied();
|
||||
if let (Some(row_index), Some(col_index)) = (row_index, col_index) {
|
||||
let value = result.estimate / 1000000.;
|
||||
if value >= 0.01 {
|
||||
let value_index = row_index * cols_count + col_index;
|
||||
values[value_index] = Cow::Owned(format!("{:.2}", value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let first_column_width = row_names.iter().map(|s| s.len()).max().unwrap_or(0);
|
||||
let mut column_width: Vec<usize> = vec![first_column_width];
|
||||
|
||||
for (col_index, col_name) in col_names.iter().enumerate() {
|
||||
let width = (0..row_names.len())
|
||||
.map(|row_index| {
|
||||
let value_index = row_index * cols_count + col_index;
|
||||
values.get(value_index).map(|v| v.len()).unwrap_or(0)
|
||||
})
|
||||
.max()
|
||||
.unwrap_or(0);
|
||||
column_width.push(width.max(col_name.len()));
|
||||
}
|
||||
|
||||
let mut first_row: Vec<String> = vec!["".to_owned()];
|
||||
col_names.iter().for_each(|s| first_row.push(s.to_owned()));
|
||||
|
||||
let mut str_buffer: Vec<String> = vec![];
|
||||
table_row(&mut str_buffer, &column_width, &first_row);
|
||||
table_header_underline(&mut str_buffer, &column_width);
|
||||
|
||||
for row_name in row_names.iter() {
|
||||
let mut row = vec![row_name.clone()];
|
||||
for col_name in col_names.iter() {
|
||||
let row_index = row_indexes.get(row_name).copied();
|
||||
let col_index = col_indexes.get(col_name).copied();
|
||||
if let (Some(row_index), Some(col_index)) = (row_index, col_index) {
|
||||
let value_index = row_index * cols_count + col_index;
|
||||
let value = values
|
||||
.get(value_index)
|
||||
.map(|v| v.to_string())
|
||||
.unwrap_or_default();
|
||||
row.push(value);
|
||||
}
|
||||
}
|
||||
table_row(&mut str_buffer, &column_width, &row);
|
||||
}
|
||||
|
||||
str_buffer.join("")
|
||||
}
|
||||
|
||||
fn table_row(buffer: &mut Vec<String>, widths: &[usize], values: &[String]) {
|
||||
for (i, (&width, value)) in widths.iter().zip(values).enumerate() {
|
||||
match i {
|
||||
0 => buffer.push(format!("| {:width$} ", value, width = width)),
|
||||
_ => buffer.push(format!("| {:^width$} ", value, width = width)),
|
||||
}
|
||||
}
|
||||
buffer.push("|\n".to_string());
|
||||
}
|
||||
|
||||
fn table_header_underline(buffer: &mut Vec<String>, widths: &[usize]) {
|
||||
for (i, &width) in widths.iter().enumerate() {
|
||||
match i {
|
||||
0 => buffer.push(format!("|{:-<width$}", "", width = width + 2)),
|
||||
_ => buffer.push(format!("|:{:-<width$}:", "", width = width)),
|
||||
}
|
||||
}
|
||||
buffer.push("|\n".to_string());
|
||||
}
|
||||
@@ -3,11 +3,13 @@ use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
|
||||
pub use bencher::*;
|
||||
pub use md_table::*;
|
||||
pub use resize_functions::*;
|
||||
pub use results::*;
|
||||
use serde::Deserialize;
|
||||
|
||||
mod bencher;
|
||||
mod md_table;
|
||||
mod resize_functions;
|
||||
mod results;
|
||||
pub mod testing;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use std::hint::black_box;
|
||||
use std::ops::Deref;
|
||||
|
||||
use fast_image_resize::images::Image;
|
||||
@@ -84,6 +83,8 @@ pub fn libvips_resize<P: PixelTestingExt>(bench_group: &mut BenchGroup, has_alph
|
||||
|
||||
#[cfg(all(not(target_arch = "wasm32"), not(target_os = "windows")))]
|
||||
mod vips {
|
||||
use std::hint::black_box;
|
||||
|
||||
use libvips::ops::{self, BandFormat, Kernel, ReduceOptions};
|
||||
use libvips::{VipsApp, VipsImage};
|
||||
|
||||
|
||||
+23
-142
@@ -1,5 +1,3 @@
|
||||
use std::borrow::Cow;
|
||||
use std::collections::HashMap;
|
||||
use std::env;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::SystemTime;
|
||||
@@ -8,41 +6,8 @@ use itertools::Itertools;
|
||||
use serde::Deserialize;
|
||||
use walkdir::WalkDir;
|
||||
|
||||
use super::get_arch_id_and_name;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BenchResult {
|
||||
pub function_name: String,
|
||||
pub parameter: String,
|
||||
/// Estimate time in nanoseconds
|
||||
pub estimate: f64,
|
||||
}
|
||||
|
||||
impl BenchResult {
|
||||
pub fn new(function_name: String, parameter: Option<String>, path: &Path) -> Self {
|
||||
#[derive(Deserialize)]
|
||||
struct Mean {
|
||||
point_estimate: f64,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Estimates {
|
||||
mean: Mean,
|
||||
}
|
||||
|
||||
let data =
|
||||
std::fs::read_to_string(path).expect("Unable to read file with benchmark results");
|
||||
|
||||
let estimates: Estimates =
|
||||
serde_json::from_str(&data).expect("Unable to parse JSON data with benchmark results");
|
||||
|
||||
Self {
|
||||
function_name,
|
||||
parameter: parameter.unwrap_or_default(),
|
||||
estimate: estimates.mean.point_estimate,
|
||||
}
|
||||
}
|
||||
}
|
||||
use super::{get_arch_id_and_name, md_table};
|
||||
use crate::utils::md_table::BenchResult;
|
||||
|
||||
/// Find all "new/estimates.json" files inside of given directory.
|
||||
/// Get only files what were created after the given time.
|
||||
@@ -90,121 +55,37 @@ pub fn get_results(parent_dir: &PathBuf, modified_after: &SystemTime) -> Vec<Ben
|
||||
_ => panic!("Relative path to bench result is invalid"),
|
||||
};
|
||||
|
||||
result.push(BenchResult::new(function_name, parameter_name, &path));
|
||||
result.push(load_bench_result(function_name, parameter_name, &path));
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
static COL_ORDER: [&str; 5] = ["Nearest", "Box", "Bilinear", "Bicubic", "Lanczos3"];
|
||||
|
||||
pub fn build_md_table(bench_results: &[BenchResult]) -> String {
|
||||
let mut row_names: Vec<String> = Vec::new();
|
||||
let mut row_indexes: HashMap<String, usize> = HashMap::new();
|
||||
let mut col_names: Vec<String> = Vec::new();
|
||||
|
||||
for result in bench_results {
|
||||
let row_name = result.function_name.clone();
|
||||
if !row_names.contains(&row_name) {
|
||||
row_names.push(row_name.clone());
|
||||
row_indexes.insert(row_name.clone(), row_names.len() - 1);
|
||||
}
|
||||
let col_name = result.parameter.clone();
|
||||
if !col_names.contains(&col_name) {
|
||||
col_names.push(col_name.clone());
|
||||
}
|
||||
pub fn load_bench_result(
|
||||
function_name: String,
|
||||
parameter: Option<String>,
|
||||
path: &Path,
|
||||
) -> BenchResult {
|
||||
#[derive(Deserialize)]
|
||||
struct Mean {
|
||||
point_estimate: f64,
|
||||
}
|
||||
|
||||
// Reorder columns
|
||||
let mut ordered_pos = 0;
|
||||
for name in COL_ORDER {
|
||||
if let Some((cur_pos, _)) = col_names.iter().find_position(|s| s.as_str() == name) {
|
||||
if cur_pos != ordered_pos {
|
||||
col_names.swap(cur_pos, ordered_pos);
|
||||
}
|
||||
ordered_pos += 1;
|
||||
}
|
||||
}
|
||||
let col_indexes: HashMap<String, usize> = col_names
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, v)| (v.clone(), i))
|
||||
.collect();
|
||||
|
||||
let cols_count = col_names.len();
|
||||
let mut values = vec![Cow::Borrowed("-"); row_names.len() * cols_count];
|
||||
|
||||
for result in bench_results {
|
||||
let row_index = row_indexes.get(&result.function_name).copied();
|
||||
let col_index = col_indexes.get(&result.parameter).copied();
|
||||
if let (Some(row_index), Some(col_index)) = (row_index, col_index) {
|
||||
let value = result.estimate / 1000000.;
|
||||
if value >= 0.01 {
|
||||
let value_index = row_index * cols_count + col_index;
|
||||
values[value_index] = Cow::Owned(format!("{:.2}", value));
|
||||
}
|
||||
}
|
||||
#[derive(Deserialize)]
|
||||
struct Estimates {
|
||||
mean: Mean,
|
||||
}
|
||||
|
||||
let first_column_width = row_names.iter().map(|s| s.len()).max().unwrap_or(0);
|
||||
let mut column_width: Vec<usize> = vec![first_column_width];
|
||||
let data = std::fs::read_to_string(path).expect("Unable to read file with benchmark results");
|
||||
|
||||
for (col_index, col_name) in col_names.iter().enumerate() {
|
||||
let width = (0..row_names.len())
|
||||
.map(|row_index| {
|
||||
let value_index = row_index * cols_count + col_index;
|
||||
values.get(value_index).map(|v| v.len()).unwrap_or(0)
|
||||
})
|
||||
.max()
|
||||
.unwrap_or(0);
|
||||
column_width.push(width.max(col_name.len()));
|
||||
let estimates: Estimates =
|
||||
serde_json::from_str(&data).expect("Unable to parse JSON data with benchmark results");
|
||||
|
||||
BenchResult {
|
||||
function_name,
|
||||
parameter: parameter.unwrap_or_default(),
|
||||
estimate: estimates.mean.point_estimate,
|
||||
}
|
||||
|
||||
let mut first_row: Vec<String> = vec!["".to_owned()];
|
||||
col_names.iter().for_each(|s| first_row.push(s.to_owned()));
|
||||
|
||||
let mut str_buffer: Vec<String> = vec![];
|
||||
table_row(&mut str_buffer, &column_width, &first_row);
|
||||
table_header_underline(&mut str_buffer, &column_width);
|
||||
|
||||
for row_name in row_names.iter() {
|
||||
let mut row = vec![row_name.clone()];
|
||||
for col_name in col_names.iter() {
|
||||
let row_index = row_indexes.get(row_name).copied();
|
||||
let col_index = col_indexes.get(col_name).copied();
|
||||
if let (Some(row_index), Some(col_index)) = (row_index, col_index) {
|
||||
let value_index = row_index * cols_count + col_index;
|
||||
let value = values
|
||||
.get(value_index)
|
||||
.map(|v| v.to_string())
|
||||
.unwrap_or_default();
|
||||
row.push(value);
|
||||
}
|
||||
}
|
||||
table_row(&mut str_buffer, &column_width, &row);
|
||||
}
|
||||
|
||||
str_buffer.join("")
|
||||
}
|
||||
|
||||
fn table_row(buffer: &mut Vec<String>, widths: &[usize], values: &[String]) {
|
||||
for (i, (&width, value)) in widths.iter().zip(values).enumerate() {
|
||||
match i {
|
||||
0 => buffer.push(format!("| {:width$} ", value, width = width)),
|
||||
_ => buffer.push(format!("| {:^width$} ", value, width = width)),
|
||||
}
|
||||
}
|
||||
buffer.push("|\n".to_string());
|
||||
}
|
||||
|
||||
fn table_header_underline(buffer: &mut Vec<String>, widths: &[usize]) {
|
||||
for (i, &width) in widths.iter().enumerate() {
|
||||
match i {
|
||||
0 => buffer.push(format!("|{:-<width$}", "", width = width + 2)),
|
||||
_ => buffer.push(format!("|:{:-<width$}:", "", width = width)),
|
||||
}
|
||||
}
|
||||
buffer.push("|\n".to_string());
|
||||
}
|
||||
|
||||
fn insert_string_into_file(path: &Path, placeholder_name: &str, string: &str) {
|
||||
@@ -288,7 +169,7 @@ fn write_bench_results_into_file(md_table: &str) {
|
||||
|
||||
pub fn print_and_write_compare_result(bench_results: &[BenchResult]) {
|
||||
if !bench_results.is_empty() {
|
||||
let md_table = build_md_table(bench_results);
|
||||
let md_table = md_table::build_md_table(bench_results);
|
||||
println!("{}", md_table);
|
||||
if env::var("WRITE_COMPARE_RESULT").unwrap_or_else(|_| "".to_owned()) == "1" {
|
||||
write_bench_results_into_file(&md_table);
|
||||
|
||||
@@ -0,0 +1,350 @@
|
||||
use std::io::Cursor;
|
||||
use std::ops::Deref;
|
||||
|
||||
use fast_image_resize::images::Image;
|
||||
use fast_image_resize::pixels::*;
|
||||
use fast_image_resize::{CpuExtensions, PixelTrait, PixelType};
|
||||
use image::{ImageBuffer, ImageFormat, ImageReader};
|
||||
use wasm_bindgen::JsValue;
|
||||
|
||||
use super::load_file;
|
||||
|
||||
pub trait PixelTestingExt: PixelTrait {
|
||||
type ImagePixel: image::Pixel;
|
||||
type Container: Deref<Target = [<Self::ImagePixel as image::Pixel>::Subpixel]>;
|
||||
|
||||
fn pixel_type_str() -> &'static str {
|
||||
match Self::pixel_type() {
|
||||
PixelType::U8 => "u8",
|
||||
PixelType::U8x2 => "u8x2",
|
||||
PixelType::U8x3 => "u8x3",
|
||||
PixelType::U8x4 => "u8x4",
|
||||
PixelType::U16 => "u16",
|
||||
PixelType::U16x2 => "u16x2",
|
||||
PixelType::U16x3 => "u16x3",
|
||||
PixelType::U16x4 => "u16x4",
|
||||
PixelType::I32 => "i32",
|
||||
PixelType::F32 => "f32",
|
||||
PixelType::F32x2 => "f32x2",
|
||||
PixelType::F32x3 => "f32x3",
|
||||
PixelType::F32x4 => "f32x4",
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn cpu_extensions() -> Vec<CpuExtensions> {
|
||||
vec![CpuExtensions::None, CpuExtensions::Simd128]
|
||||
}
|
||||
|
||||
fn img_paths() -> (&'static str, &'static str, &'static str) {
|
||||
match Self::pixel_type() {
|
||||
PixelType::U8
|
||||
| PixelType::U8x3
|
||||
| PixelType::U16
|
||||
| PixelType::U16x3
|
||||
| PixelType::I32
|
||||
| PixelType::F32
|
||||
| PixelType::F32x3 => (
|
||||
"../data/nasa-4928x3279.png",
|
||||
"../data/nasa-4019x4019.png",
|
||||
"../data/nasa-852x567.png",
|
||||
),
|
||||
PixelType::U8x2
|
||||
| PixelType::U8x4
|
||||
| PixelType::U16x2
|
||||
| PixelType::U16x4
|
||||
| PixelType::F32x2
|
||||
| PixelType::F32x4 => (
|
||||
"../data/nasa-4928x3279-rgba.png",
|
||||
"../data/nasa-4019x4019-rgba.png",
|
||||
"../data/nasa-852x567-rgba.png",
|
||||
),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn load_image_buffer(
|
||||
img_reader: ImageReader<Cursor<Vec<u8>>>,
|
||||
) -> ImageBuffer<Self::ImagePixel, Self::Container>;
|
||||
|
||||
fn img_into_bytes(img: ImageBuffer<Self::ImagePixel, Self::Container>) -> Vec<u8>;
|
||||
}
|
||||
|
||||
pub async fn load_big_image<T: PixelTestingExt>(
|
||||
) -> Result<ImageBuffer<T::ImagePixel, T::Container>, JsValue> {
|
||||
let path = T::img_paths().0;
|
||||
let data = load_file(path).await?;
|
||||
let mut reader = ImageReader::new(Cursor::new(data));
|
||||
reader.set_format(ImageFormat::Png);
|
||||
Ok(T::load_image_buffer(reader))
|
||||
}
|
||||
|
||||
pub async fn load_big_square_image<T: PixelTestingExt>(
|
||||
) -> Result<ImageBuffer<T::ImagePixel, T::Container>, JsValue> {
|
||||
let path = T::img_paths().1;
|
||||
let data = load_file(path).await?;
|
||||
let mut reader = ImageReader::new(Cursor::new(data));
|
||||
reader.set_format(ImageFormat::Png);
|
||||
Ok(T::load_image_buffer(reader))
|
||||
}
|
||||
|
||||
pub async fn load_small_image<T: PixelTestingExt>(
|
||||
) -> Result<ImageBuffer<T::ImagePixel, T::Container>, JsValue> {
|
||||
let path = T::img_paths().2;
|
||||
let data = load_file(path).await?;
|
||||
let mut reader = ImageReader::new(Cursor::new(data));
|
||||
reader.set_format(ImageFormat::Png);
|
||||
Ok(T::load_image_buffer(reader))
|
||||
}
|
||||
|
||||
pub async fn load_big_src_image<T: PixelTestingExt>() -> Result<Image<'static>, JsValue> {
|
||||
let img = load_big_image::<T>().await?;
|
||||
Ok(Image::from_vec_u8(
|
||||
img.width(),
|
||||
img.height(),
|
||||
T::img_into_bytes(img),
|
||||
T::pixel_type(),
|
||||
)
|
||||
.unwrap())
|
||||
}
|
||||
|
||||
pub async fn load_big_square_src_image<T: PixelTestingExt>() -> Result<Image<'static>, JsValue> {
|
||||
let img = load_big_square_image::<T>().await?;
|
||||
Ok(Image::from_vec_u8(
|
||||
img.width(),
|
||||
img.height(),
|
||||
T::img_into_bytes(img),
|
||||
T::pixel_type(),
|
||||
)
|
||||
.unwrap())
|
||||
}
|
||||
|
||||
pub async fn load_small_src_image<T: PixelTestingExt>() -> Result<Image<'static>, JsValue> {
|
||||
let img = load_small_image::<T>().await?;
|
||||
Ok(Image::from_vec_u8(
|
||||
img.width(),
|
||||
img.height(),
|
||||
T::img_into_bytes(img),
|
||||
T::pixel_type(),
|
||||
)
|
||||
.unwrap())
|
||||
}
|
||||
|
||||
impl PixelTestingExt for U8 {
|
||||
type ImagePixel = image::Luma<u8>;
|
||||
type Container = Vec<u8>;
|
||||
|
||||
fn load_image_buffer(
|
||||
img_reader: ImageReader<Cursor<Vec<u8>>>,
|
||||
) -> ImageBuffer<Self::ImagePixel, Self::Container> {
|
||||
img_reader.decode().unwrap().to_luma8()
|
||||
}
|
||||
|
||||
fn img_into_bytes(img: ImageBuffer<Self::ImagePixel, Self::Container>) -> Vec<u8> {
|
||||
img.into_raw()
|
||||
}
|
||||
}
|
||||
|
||||
impl PixelTestingExt for U8x2 {
|
||||
type ImagePixel = image::LumaA<u8>;
|
||||
type Container = Vec<u8>;
|
||||
|
||||
fn load_image_buffer(
|
||||
img_reader: ImageReader<Cursor<Vec<u8>>>,
|
||||
) -> ImageBuffer<Self::ImagePixel, Self::Container> {
|
||||
img_reader.decode().unwrap().to_luma_alpha8()
|
||||
}
|
||||
|
||||
fn img_into_bytes(img: ImageBuffer<Self::ImagePixel, Self::Container>) -> Vec<u8> {
|
||||
img.into_raw()
|
||||
}
|
||||
}
|
||||
|
||||
impl PixelTestingExt for U8x3 {
|
||||
type ImagePixel = image::Rgb<u8>;
|
||||
type Container = Vec<u8>;
|
||||
|
||||
fn load_image_buffer(
|
||||
img_reader: ImageReader<Cursor<Vec<u8>>>,
|
||||
) -> ImageBuffer<Self::ImagePixel, Self::Container> {
|
||||
img_reader.decode().unwrap().to_rgb8()
|
||||
}
|
||||
|
||||
fn img_into_bytes(img: ImageBuffer<Self::ImagePixel, Self::Container>) -> Vec<u8> {
|
||||
img.into_raw()
|
||||
}
|
||||
}
|
||||
|
||||
impl PixelTestingExt for U16 {
|
||||
type ImagePixel = image::Luma<u16>;
|
||||
type Container = Vec<u16>;
|
||||
|
||||
fn load_image_buffer(
|
||||
img_reader: ImageReader<Cursor<Vec<u8>>>,
|
||||
) -> ImageBuffer<Self::ImagePixel, Self::Container> {
|
||||
img_reader.decode().unwrap().to_luma16()
|
||||
}
|
||||
|
||||
fn img_into_bytes(img: ImageBuffer<Self::ImagePixel, Self::Container>) -> Vec<u8> {
|
||||
img.as_raw().iter().flat_map(|&c| c.to_le_bytes()).collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl PixelTestingExt for U16x2 {
|
||||
type ImagePixel = image::LumaA<u16>;
|
||||
type Container = Vec<u16>;
|
||||
|
||||
fn load_image_buffer(
|
||||
img_reader: ImageReader<Cursor<Vec<u8>>>,
|
||||
) -> ImageBuffer<Self::ImagePixel, Self::Container> {
|
||||
img_reader.decode().unwrap().to_luma_alpha16()
|
||||
}
|
||||
|
||||
fn img_into_bytes(img: ImageBuffer<Self::ImagePixel, Self::Container>) -> Vec<u8> {
|
||||
img.as_raw().iter().flat_map(|&c| c.to_le_bytes()).collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl PixelTestingExt for U16x3 {
|
||||
type ImagePixel = image::Rgb<u16>;
|
||||
type Container = Vec<u16>;
|
||||
|
||||
fn load_image_buffer(
|
||||
img_reader: ImageReader<Cursor<Vec<u8>>>,
|
||||
) -> ImageBuffer<Self::ImagePixel, Self::Container> {
|
||||
img_reader.decode().unwrap().to_rgb16()
|
||||
}
|
||||
|
||||
fn img_into_bytes(img: ImageBuffer<Self::ImagePixel, Self::Container>) -> Vec<u8> {
|
||||
img.as_raw().iter().flat_map(|&c| c.to_le_bytes()).collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl PixelTestingExt for U16x4 {
|
||||
type ImagePixel = image::Rgba<u16>;
|
||||
type Container = Vec<u16>;
|
||||
|
||||
fn load_image_buffer(
|
||||
img_reader: ImageReader<Cursor<Vec<u8>>>,
|
||||
) -> ImageBuffer<Self::ImagePixel, Self::Container> {
|
||||
img_reader.decode().unwrap().to_rgba16()
|
||||
}
|
||||
|
||||
fn img_into_bytes(img: ImageBuffer<Self::ImagePixel, Self::Container>) -> Vec<u8> {
|
||||
img.as_raw().iter().flat_map(|&c| c.to_le_bytes()).collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl PixelTestingExt for I32 {
|
||||
type ImagePixel = image::Luma<i32>;
|
||||
type Container = Vec<i32>;
|
||||
|
||||
fn cpu_extensions() -> Vec<CpuExtensions> {
|
||||
vec![CpuExtensions::None]
|
||||
}
|
||||
|
||||
fn load_image_buffer(
|
||||
img_reader: ImageReader<Cursor<Vec<u8>>>,
|
||||
) -> ImageBuffer<Self::ImagePixel, Self::Container> {
|
||||
let image_u16 = img_reader.decode().unwrap().to_luma32f();
|
||||
ImageBuffer::from_fn(image_u16.width(), image_u16.height(), |x, y| {
|
||||
let pixel = image_u16.get_pixel(x, y);
|
||||
image::Luma::from([(pixel.0[0] * i32::MAX as f32).round() as i32])
|
||||
})
|
||||
}
|
||||
|
||||
fn img_into_bytes(img: ImageBuffer<Self::ImagePixel, Self::Container>) -> Vec<u8> {
|
||||
img.as_raw()
|
||||
.iter()
|
||||
.flat_map(|val| val.to_le_bytes())
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl PixelTestingExt for F32 {
|
||||
type ImagePixel = image::Luma<f32>;
|
||||
type Container = Vec<f32>;
|
||||
|
||||
fn load_image_buffer(
|
||||
img_reader: ImageReader<Cursor<Vec<u8>>>,
|
||||
) -> ImageBuffer<Self::ImagePixel, Self::Container> {
|
||||
img_reader.decode().unwrap().to_luma32f()
|
||||
}
|
||||
|
||||
fn img_into_bytes(img: ImageBuffer<Self::ImagePixel, Self::Container>) -> Vec<u8> {
|
||||
img.as_raw()
|
||||
.iter()
|
||||
.flat_map(|val| val.to_le_bytes())
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl PixelTestingExt for F32x2 {
|
||||
type ImagePixel = image::LumaA<f32>;
|
||||
type Container = Vec<f32>;
|
||||
|
||||
fn load_image_buffer(
|
||||
img_reader: ImageReader<Cursor<Vec<u8>>>,
|
||||
) -> ImageBuffer<Self::ImagePixel, Self::Container> {
|
||||
img_reader.decode().unwrap().to_luma_alpha32f()
|
||||
}
|
||||
|
||||
fn img_into_bytes(img: ImageBuffer<Self::ImagePixel, Self::Container>) -> Vec<u8> {
|
||||
img.as_raw()
|
||||
.iter()
|
||||
.flat_map(|val| val.to_le_bytes())
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl PixelTestingExt for F32x3 {
|
||||
type ImagePixel = image::Rgb<f32>;
|
||||
type Container = Vec<f32>;
|
||||
|
||||
fn load_image_buffer(
|
||||
img_reader: ImageReader<Cursor<Vec<u8>>>,
|
||||
) -> ImageBuffer<Self::ImagePixel, Self::Container> {
|
||||
img_reader.decode().unwrap().to_rgb32f()
|
||||
}
|
||||
|
||||
fn img_into_bytes(img: ImageBuffer<Self::ImagePixel, Self::Container>) -> Vec<u8> {
|
||||
img.as_raw().iter().flat_map(|&c| c.to_le_bytes()).collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl PixelTestingExt for F32x4 {
|
||||
type ImagePixel = image::Rgba<f32>;
|
||||
type Container = Vec<f32>;
|
||||
|
||||
fn load_image_buffer(
|
||||
img_reader: ImageReader<Cursor<Vec<u8>>>,
|
||||
) -> ImageBuffer<Self::ImagePixel, Self::Container> {
|
||||
img_reader.decode().unwrap().to_rgba32f()
|
||||
}
|
||||
|
||||
fn img_into_bytes(img: ImageBuffer<Self::ImagePixel, Self::Container>) -> Vec<u8> {
|
||||
img.as_raw().iter().flat_map(|&c| c.to_le_bytes()).collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl PixelTestingExt for U8x4 {
|
||||
type ImagePixel = image::Rgba<u8>;
|
||||
type Container = Vec<u8>;
|
||||
|
||||
fn load_image_buffer(
|
||||
img_reader: ImageReader<Cursor<Vec<u8>>>,
|
||||
) -> ImageBuffer<Self::ImagePixel, Self::Container> {
|
||||
img_reader.decode().unwrap().to_rgba8()
|
||||
}
|
||||
|
||||
fn img_into_bytes(img: ImageBuffer<Self::ImagePixel, Self::Container>) -> Vec<u8> {
|
||||
img.into_raw()
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn cpu_ext_into_str(cpu_extensions: CpuExtensions) -> &'static str {
|
||||
match cpu_extensions {
|
||||
CpuExtensions::None => "rust",
|
||||
CpuExtensions::Simd128 => "simd128",
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#![cfg(target_arch = "wasm32")]
|
||||
|
||||
use js_sys::Uint8Array;
|
||||
use wasm_bindgen::prelude::*;
|
||||
use web_sys::{Request, RequestInit, RequestMode, Response};
|
||||
pub mod load_images;
|
||||
pub mod resize_functions;
|
||||
|
||||
pub async fn load_file(path: &str) -> Result<Vec<u8>, JsValue> {
|
||||
let opts = RequestInit::new();
|
||||
opts.set_method("GET");
|
||||
opts.set_mode(RequestMode::Cors);
|
||||
|
||||
let request = Request::new_with_str_and_init(path, &opts)?;
|
||||
|
||||
let window = web_sys::window().unwrap();
|
||||
let resp = window.fetch_with_request(&request).await?;
|
||||
|
||||
let resp: Response = resp.dyn_into()?;
|
||||
|
||||
let buffer = resp.array_buffer()?.await?;
|
||||
let bytes = Uint8Array::new(&buffer).to_vec();
|
||||
|
||||
Ok(bytes)
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
use std::ops::Deref;
|
||||
|
||||
use fast_image_resize::images::Image;
|
||||
use fast_image_resize::{FilterType, ResizeAlg, ResizeOptions, Resizer};
|
||||
use image::{imageops, ImageBuffer};
|
||||
use wasm_bindgen_test::Criterion;
|
||||
|
||||
use super::load_images::{cpu_ext_into_str, PixelTestingExt};
|
||||
|
||||
pub const ALG_NAMES: [&str; 5] = ["Nearest", "Box", "Bilinear", "Bicubic", "Lanczos3"];
|
||||
const NEW_WIDTH: u32 = 852;
|
||||
const NEW_HEIGHT: u32 = 567;
|
||||
|
||||
/// Resize the image with the help of "image" crate (https://crates.io/crates/image)
|
||||
pub fn image_resize<P, C>(bencher: &mut Criterion, src_image: &ImageBuffer<P, C>)
|
||||
where
|
||||
P: image::Pixel + 'static,
|
||||
C: Deref<Target = [P::Subpixel]>,
|
||||
{
|
||||
for alg_name in ALG_NAMES {
|
||||
let filter = match alg_name {
|
||||
"Nearest" => imageops::Nearest,
|
||||
"Bilinear" => imageops::Triangle,
|
||||
"Bicubic" => imageops::CatmullRom,
|
||||
"Lanczos3" => imageops::Lanczos3,
|
||||
_ => return,
|
||||
};
|
||||
bencher.bench_function(&format!("image {alg_name}"), |b| {
|
||||
b.iter(|| imageops::resize(src_image, NEW_WIDTH, NEW_HEIGHT, filter))
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Resize the image with the help of "resize" crate (https://crates.io/crates/resize)
|
||||
pub fn resize_resize<Format, Out>(
|
||||
bencher: &mut Criterion,
|
||||
pixel_format: Format,
|
||||
src_image: &[Format::InputPixel],
|
||||
src_width: u32,
|
||||
src_height: u32,
|
||||
) where
|
||||
Out: Clone,
|
||||
Format: resize::PixelFormat<OutputPixel = Out> + Copy,
|
||||
{
|
||||
let mut dst = vec![pixel_format.into_pixel(Format::new()); (NEW_WIDTH * NEW_HEIGHT) as usize];
|
||||
|
||||
for alg_name in ALG_NAMES {
|
||||
let filter = match alg_name {
|
||||
"Nearest" => resize::Type::Point,
|
||||
"Box" => resize::Type::Custom(resize::Filter::box_filter(0.5)),
|
||||
"Bilinear" => resize::Type::Triangle,
|
||||
"Bicubic" => resize::Type::Catrom,
|
||||
"Lanczos3" => resize::Type::Lanczos3,
|
||||
_ => continue,
|
||||
};
|
||||
let mut resizer = resize::new(
|
||||
src_width as usize,
|
||||
src_height as usize,
|
||||
NEW_WIDTH as usize,
|
||||
NEW_HEIGHT as usize,
|
||||
pixel_format,
|
||||
filter,
|
||||
)
|
||||
.unwrap();
|
||||
bencher.bench_function(&format!("resize {alg_name}"), |b| {
|
||||
b.iter(|| resizer.resize(src_image, &mut dst).unwrap())
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Resize image with the help of "fast_imager_resize" crate
|
||||
pub fn fir_resize<P: PixelTestingExt>(
|
||||
bencher: &mut Criterion,
|
||||
src_image_data: Image<'static>,
|
||||
use_alpha: bool,
|
||||
) {
|
||||
let mut dst_image = Image::new(NEW_WIDTH, NEW_HEIGHT, src_image_data.pixel_type());
|
||||
let cpu_extensions = P::cpu_extensions();
|
||||
for cpu_ext in cpu_extensions {
|
||||
let lib_name = format!("fir {}", cpu_ext_into_str(cpu_ext));
|
||||
for alg_name in ALG_NAMES {
|
||||
let resize_alg = match alg_name {
|
||||
"Nearest" => ResizeAlg::Nearest,
|
||||
"Box" => ResizeAlg::Convolution(FilterType::Box),
|
||||
"Bilinear" => ResizeAlg::Convolution(FilterType::Bilinear),
|
||||
"Bicubic" => ResizeAlg::Convolution(FilterType::CatmullRom),
|
||||
"Lanczos3" => ResizeAlg::Convolution(FilterType::Lanczos3),
|
||||
_ => continue,
|
||||
};
|
||||
let mut fast_resizer = Resizer::new();
|
||||
unsafe {
|
||||
fast_resizer.set_cpu_extensions(cpu_ext);
|
||||
}
|
||||
let resize_options = ResizeOptions::new()
|
||||
.resize_alg(resize_alg)
|
||||
.use_alpha(use_alpha);
|
||||
|
||||
bencher.bench_function(&format!("{lib_name} {alg_name}"), |b| {
|
||||
b.iter(|| {
|
||||
fast_resizer
|
||||
.resize(&src_image_data, &mut dst_image, &resize_options)
|
||||
.unwrap()
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
[package]
|
||||
name = "benches_wasm"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
|
||||
[dependencies]
|
||||
fast_image_resize = { path = "..", features = ["image"] }
|
||||
wasm-bindgen = "0.2.126"
|
||||
wasm-bindgen-futures = "0.4.76"
|
||||
js-sys = "0.3.103"
|
||||
image = { version = "0.25.6", default-features = false, features = ["png"] }
|
||||
resize = { version = "0.8.9", default-features = false, features = ["std"] }
|
||||
rgb = "0.8.53"
|
||||
console_error_panic_hook = "0.1.7"
|
||||
gloo-timers = { version = "0.4.0", features = ["futures"] }
|
||||
|
||||
|
||||
[dependencies.web-sys]
|
||||
version = "0.3.103"
|
||||
features = ["Document", "Element", "HtmlElement", "Node", "Window", "Request", "RequestInit", "RequestMode", "Response", "Performance"]
|
||||
|
||||
|
||||
[package.metadata.wasm-pack.profile.release]
|
||||
wasm-opt = false
|
||||
@@ -0,0 +1,54 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta content="text/html;charset=utf-8" http-equiv="Content-Type"/>
|
||||
<title>Wasm Bench</title>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Note the usage of `type=module` here as this is an ES6 module -->
|
||||
<script type="module">
|
||||
// Use ES module import syntax to import functionality from the module
|
||||
// that we have compiled.
|
||||
//
|
||||
// Note that the `default` import is an initialization function which
|
||||
// will "boot" the module and make it ready to use. Currently, browsers
|
||||
// don't support natively imported WebAssembly as an ES module, but
|
||||
// eventually the manual initialization won't be required!
|
||||
import init, {run_benches} from './pkg/benches_wasm.js';
|
||||
|
||||
async function run() {
|
||||
// First up, we need to actually load the Wasm file, so we use the
|
||||
// default export to inform it where the Wasm file is located on the
|
||||
// server, and then we wait on the returned promise to wait for the
|
||||
// Wasm to be loaded.
|
||||
//
|
||||
// It may look like this: `await init('./benches_wasm_bg.wasm');`,
|
||||
// but there is also a handy default inside `init` function, which uses
|
||||
// `import.meta` to locate the Wasm file relatively to js file.
|
||||
//
|
||||
// Note that instead of a string, you can also pass in any of the
|
||||
// following things:
|
||||
//
|
||||
// * `WebAssembly.Module`
|
||||
//
|
||||
// * `ArrayBuffer`
|
||||
//
|
||||
// * `Response`
|
||||
//
|
||||
// * `Promise,` which returns any of the above, e.g. `fetch("./path/to/wasm")`
|
||||
//
|
||||
// This gives you complete control over how the module is loaded
|
||||
// and compiled.
|
||||
//
|
||||
// Also note that the promise, when resolved, yields the Wasm module's
|
||||
// exports, which is the same as importing the `*_bg` module in other
|
||||
// modes
|
||||
await init();
|
||||
// And afterward we can use all the functionality defined in wasm.
|
||||
await run_benches();
|
||||
}
|
||||
|
||||
run();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,71 @@
|
||||
#![cfg(target_arch = "wasm32")]
|
||||
|
||||
use fast_image_resize::pixels::U16x3;
|
||||
use gloo_timers::future::TimeoutFuture;
|
||||
use wasm_bindgen::prelude::*;
|
||||
use web_sys::Element;
|
||||
|
||||
use crate::load_images::{load_big_image, load_big_src_image};
|
||||
|
||||
pub mod load_images;
|
||||
pub mod resize_functions;
|
||||
mod utils;
|
||||
|
||||
// Called when the Wasm module is instantiated
|
||||
#[wasm_bindgen(start)]
|
||||
fn main() -> Result<(), JsValue> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub async fn run_benches() -> Result<(), JsValue> {
|
||||
utils::set_panic_hook();
|
||||
|
||||
let window = web_sys::window().expect("no global `window` exists");
|
||||
let document = window.document().expect("should have a document on window");
|
||||
let body = document.body().expect("document should have a body");
|
||||
|
||||
let code = document.create_element("pre")?;
|
||||
body.append_child(&code)?;
|
||||
|
||||
code.set_inner_html("Hello from Rust!\n");
|
||||
|
||||
bench_downscale_rgb16(&code).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn bench_downscale_rgb16(code: &Element) -> Result<(), JsValue> {
|
||||
use resize::Pixel::RGB16;
|
||||
use rgb::FromSlice;
|
||||
type P = U16x3;
|
||||
print(code, "Bench downscale RGB16\n").await;
|
||||
print(code, "'image' crate:\n").await;
|
||||
let src_image = load_big_image::<P>().await?;
|
||||
for res in resize_functions::image_resize(&src_image) {
|
||||
print_results(code, res).await;
|
||||
}
|
||||
// let res = resize_functions::resize_resize(
|
||||
// RGB16,
|
||||
// src_image.as_raw().as_rgb(),
|
||||
// src_image.width(),
|
||||
// src_image.height(),
|
||||
// );
|
||||
// print_results(code, res);
|
||||
// let src_image_data = load_big_src_image::<P>().await?;
|
||||
// let res = resize_functions::fir_resize::<P>(src_image_data, false);
|
||||
// print_results(code, res);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn print_results(code: &Element, results: (String, String, f64)) {
|
||||
let (lib_name, alg_name, speed) = results;
|
||||
let line = &format!("{} {}: {:.2}ms\n", lib_name, alg_name, speed);
|
||||
print(code, line).await;
|
||||
}
|
||||
|
||||
async fn print(code: &Element, line: &str) {
|
||||
code.append_with_str_1(line).unwrap();
|
||||
// Let the browser process events and repaint.
|
||||
TimeoutFuture::new(0).await;
|
||||
}
|
||||
@@ -0,0 +1,350 @@
|
||||
use std::io::Cursor;
|
||||
use std::ops::Deref;
|
||||
|
||||
use fast_image_resize::images::Image;
|
||||
use fast_image_resize::pixels::*;
|
||||
use fast_image_resize::{CpuExtensions, PixelTrait, PixelType};
|
||||
use image::{ImageBuffer, ImageFormat, ImageReader};
|
||||
use wasm_bindgen::JsValue;
|
||||
|
||||
use super::utils::load_file;
|
||||
|
||||
pub trait PixelTestingExt: PixelTrait {
|
||||
type ImagePixel: image::Pixel;
|
||||
type Container: Deref<Target = [<Self::ImagePixel as image::Pixel>::Subpixel]>;
|
||||
|
||||
fn pixel_type_str() -> &'static str {
|
||||
match Self::pixel_type() {
|
||||
PixelType::U8 => "u8",
|
||||
PixelType::U8x2 => "u8x2",
|
||||
PixelType::U8x3 => "u8x3",
|
||||
PixelType::U8x4 => "u8x4",
|
||||
PixelType::U16 => "u16",
|
||||
PixelType::U16x2 => "u16x2",
|
||||
PixelType::U16x3 => "u16x3",
|
||||
PixelType::U16x4 => "u16x4",
|
||||
PixelType::I32 => "i32",
|
||||
PixelType::F32 => "f32",
|
||||
PixelType::F32x2 => "f32x2",
|
||||
PixelType::F32x3 => "f32x3",
|
||||
PixelType::F32x4 => "f32x4",
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn cpu_extensions() -> Vec<CpuExtensions> {
|
||||
vec![CpuExtensions::None, CpuExtensions::Simd128]
|
||||
}
|
||||
|
||||
fn img_paths() -> (&'static str, &'static str, &'static str) {
|
||||
match Self::pixel_type() {
|
||||
PixelType::U8
|
||||
| PixelType::U8x3
|
||||
| PixelType::U16
|
||||
| PixelType::U16x3
|
||||
| PixelType::I32
|
||||
| PixelType::F32
|
||||
| PixelType::F32x3 => (
|
||||
"../data/nasa-4928x3279.png",
|
||||
"../data/nasa-4019x4019.png",
|
||||
"../data/nasa-852x567.png",
|
||||
),
|
||||
PixelType::U8x2
|
||||
| PixelType::U8x4
|
||||
| PixelType::U16x2
|
||||
| PixelType::U16x4
|
||||
| PixelType::F32x2
|
||||
| PixelType::F32x4 => (
|
||||
"../data/nasa-4928x3279-rgba.png",
|
||||
"../data/nasa-4019x4019-rgba.png",
|
||||
"../data/nasa-852x567-rgba.png",
|
||||
),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn load_image_buffer(
|
||||
img_reader: ImageReader<Cursor<Vec<u8>>>,
|
||||
) -> ImageBuffer<Self::ImagePixel, Self::Container>;
|
||||
|
||||
fn img_into_bytes(img: ImageBuffer<Self::ImagePixel, Self::Container>) -> Vec<u8>;
|
||||
}
|
||||
|
||||
pub async fn load_big_image<T: PixelTestingExt>()
|
||||
-> Result<ImageBuffer<T::ImagePixel, T::Container>, JsValue> {
|
||||
let path = T::img_paths().0;
|
||||
let data = load_file(path).await?;
|
||||
let mut reader = ImageReader::new(Cursor::new(data));
|
||||
reader.set_format(ImageFormat::Png);
|
||||
Ok(T::load_image_buffer(reader))
|
||||
}
|
||||
|
||||
pub async fn load_big_square_image<T: PixelTestingExt>()
|
||||
-> Result<ImageBuffer<T::ImagePixel, T::Container>, JsValue> {
|
||||
let path = T::img_paths().1;
|
||||
let data = load_file(path).await?;
|
||||
let mut reader = ImageReader::new(Cursor::new(data));
|
||||
reader.set_format(ImageFormat::Png);
|
||||
Ok(T::load_image_buffer(reader))
|
||||
}
|
||||
|
||||
pub async fn load_small_image<T: PixelTestingExt>()
|
||||
-> Result<ImageBuffer<T::ImagePixel, T::Container>, JsValue> {
|
||||
let path = T::img_paths().2;
|
||||
let data = load_file(path).await?;
|
||||
let mut reader = ImageReader::new(Cursor::new(data));
|
||||
reader.set_format(ImageFormat::Png);
|
||||
Ok(T::load_image_buffer(reader))
|
||||
}
|
||||
|
||||
pub async fn load_big_src_image<T: PixelTestingExt>() -> Result<Image<'static>, JsValue> {
|
||||
let img = load_big_image::<T>().await?;
|
||||
Ok(Image::from_vec_u8(
|
||||
img.width(),
|
||||
img.height(),
|
||||
T::img_into_bytes(img),
|
||||
T::pixel_type(),
|
||||
)
|
||||
.unwrap())
|
||||
}
|
||||
|
||||
pub async fn load_big_square_src_image<T: PixelTestingExt>() -> Result<Image<'static>, JsValue> {
|
||||
let img = load_big_square_image::<T>().await?;
|
||||
Ok(Image::from_vec_u8(
|
||||
img.width(),
|
||||
img.height(),
|
||||
T::img_into_bytes(img),
|
||||
T::pixel_type(),
|
||||
)
|
||||
.unwrap())
|
||||
}
|
||||
|
||||
pub async fn load_small_src_image<T: PixelTestingExt>() -> Result<Image<'static>, JsValue> {
|
||||
let img = load_small_image::<T>().await?;
|
||||
Ok(Image::from_vec_u8(
|
||||
img.width(),
|
||||
img.height(),
|
||||
T::img_into_bytes(img),
|
||||
T::pixel_type(),
|
||||
)
|
||||
.unwrap())
|
||||
}
|
||||
|
||||
impl PixelTestingExt for U8 {
|
||||
type ImagePixel = image::Luma<u8>;
|
||||
type Container = Vec<u8>;
|
||||
|
||||
fn load_image_buffer(
|
||||
img_reader: ImageReader<Cursor<Vec<u8>>>,
|
||||
) -> ImageBuffer<Self::ImagePixel, Self::Container> {
|
||||
img_reader.decode().unwrap().to_luma8()
|
||||
}
|
||||
|
||||
fn img_into_bytes(img: ImageBuffer<Self::ImagePixel, Self::Container>) -> Vec<u8> {
|
||||
img.into_raw()
|
||||
}
|
||||
}
|
||||
|
||||
impl PixelTestingExt for U8x2 {
|
||||
type ImagePixel = image::LumaA<u8>;
|
||||
type Container = Vec<u8>;
|
||||
|
||||
fn load_image_buffer(
|
||||
img_reader: ImageReader<Cursor<Vec<u8>>>,
|
||||
) -> ImageBuffer<Self::ImagePixel, Self::Container> {
|
||||
img_reader.decode().unwrap().to_luma_alpha8()
|
||||
}
|
||||
|
||||
fn img_into_bytes(img: ImageBuffer<Self::ImagePixel, Self::Container>) -> Vec<u8> {
|
||||
img.into_raw()
|
||||
}
|
||||
}
|
||||
|
||||
impl PixelTestingExt for U8x3 {
|
||||
type ImagePixel = image::Rgb<u8>;
|
||||
type Container = Vec<u8>;
|
||||
|
||||
fn load_image_buffer(
|
||||
img_reader: ImageReader<Cursor<Vec<u8>>>,
|
||||
) -> ImageBuffer<Self::ImagePixel, Self::Container> {
|
||||
img_reader.decode().unwrap().to_rgb8()
|
||||
}
|
||||
|
||||
fn img_into_bytes(img: ImageBuffer<Self::ImagePixel, Self::Container>) -> Vec<u8> {
|
||||
img.into_raw()
|
||||
}
|
||||
}
|
||||
|
||||
impl PixelTestingExt for U16 {
|
||||
type ImagePixel = image::Luma<u16>;
|
||||
type Container = Vec<u16>;
|
||||
|
||||
fn load_image_buffer(
|
||||
img_reader: ImageReader<Cursor<Vec<u8>>>,
|
||||
) -> ImageBuffer<Self::ImagePixel, Self::Container> {
|
||||
img_reader.decode().unwrap().to_luma16()
|
||||
}
|
||||
|
||||
fn img_into_bytes(img: ImageBuffer<Self::ImagePixel, Self::Container>) -> Vec<u8> {
|
||||
img.as_raw().iter().flat_map(|&c| c.to_le_bytes()).collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl PixelTestingExt for U16x2 {
|
||||
type ImagePixel = image::LumaA<u16>;
|
||||
type Container = Vec<u16>;
|
||||
|
||||
fn load_image_buffer(
|
||||
img_reader: ImageReader<Cursor<Vec<u8>>>,
|
||||
) -> ImageBuffer<Self::ImagePixel, Self::Container> {
|
||||
img_reader.decode().unwrap().to_luma_alpha16()
|
||||
}
|
||||
|
||||
fn img_into_bytes(img: ImageBuffer<Self::ImagePixel, Self::Container>) -> Vec<u8> {
|
||||
img.as_raw().iter().flat_map(|&c| c.to_le_bytes()).collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl PixelTestingExt for U16x3 {
|
||||
type ImagePixel = image::Rgb<u16>;
|
||||
type Container = Vec<u16>;
|
||||
|
||||
fn load_image_buffer(
|
||||
img_reader: ImageReader<Cursor<Vec<u8>>>,
|
||||
) -> ImageBuffer<Self::ImagePixel, Self::Container> {
|
||||
img_reader.decode().unwrap().to_rgb16()
|
||||
}
|
||||
|
||||
fn img_into_bytes(img: ImageBuffer<Self::ImagePixel, Self::Container>) -> Vec<u8> {
|
||||
img.as_raw().iter().flat_map(|&c| c.to_le_bytes()).collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl PixelTestingExt for U16x4 {
|
||||
type ImagePixel = image::Rgba<u16>;
|
||||
type Container = Vec<u16>;
|
||||
|
||||
fn load_image_buffer(
|
||||
img_reader: ImageReader<Cursor<Vec<u8>>>,
|
||||
) -> ImageBuffer<Self::ImagePixel, Self::Container> {
|
||||
img_reader.decode().unwrap().to_rgba16()
|
||||
}
|
||||
|
||||
fn img_into_bytes(img: ImageBuffer<Self::ImagePixel, Self::Container>) -> Vec<u8> {
|
||||
img.as_raw().iter().flat_map(|&c| c.to_le_bytes()).collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl PixelTestingExt for I32 {
|
||||
type ImagePixel = image::Luma<i32>;
|
||||
type Container = Vec<i32>;
|
||||
|
||||
fn cpu_extensions() -> Vec<CpuExtensions> {
|
||||
vec![CpuExtensions::None]
|
||||
}
|
||||
|
||||
fn load_image_buffer(
|
||||
img_reader: ImageReader<Cursor<Vec<u8>>>,
|
||||
) -> ImageBuffer<Self::ImagePixel, Self::Container> {
|
||||
let image_u16 = img_reader.decode().unwrap().to_luma32f();
|
||||
ImageBuffer::from_fn(image_u16.width(), image_u16.height(), |x, y| {
|
||||
let pixel = image_u16.get_pixel(x, y);
|
||||
image::Luma::from([(pixel.0[0] * i32::MAX as f32).round() as i32])
|
||||
})
|
||||
}
|
||||
|
||||
fn img_into_bytes(img: ImageBuffer<Self::ImagePixel, Self::Container>) -> Vec<u8> {
|
||||
img.as_raw()
|
||||
.iter()
|
||||
.flat_map(|val| val.to_le_bytes())
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl PixelTestingExt for F32 {
|
||||
type ImagePixel = image::Luma<f32>;
|
||||
type Container = Vec<f32>;
|
||||
|
||||
fn load_image_buffer(
|
||||
img_reader: ImageReader<Cursor<Vec<u8>>>,
|
||||
) -> ImageBuffer<Self::ImagePixel, Self::Container> {
|
||||
img_reader.decode().unwrap().to_luma32f()
|
||||
}
|
||||
|
||||
fn img_into_bytes(img: ImageBuffer<Self::ImagePixel, Self::Container>) -> Vec<u8> {
|
||||
img.as_raw()
|
||||
.iter()
|
||||
.flat_map(|val| val.to_le_bytes())
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl PixelTestingExt for F32x2 {
|
||||
type ImagePixel = image::LumaA<f32>;
|
||||
type Container = Vec<f32>;
|
||||
|
||||
fn load_image_buffer(
|
||||
img_reader: ImageReader<Cursor<Vec<u8>>>,
|
||||
) -> ImageBuffer<Self::ImagePixel, Self::Container> {
|
||||
img_reader.decode().unwrap().to_luma_alpha32f()
|
||||
}
|
||||
|
||||
fn img_into_bytes(img: ImageBuffer<Self::ImagePixel, Self::Container>) -> Vec<u8> {
|
||||
img.as_raw()
|
||||
.iter()
|
||||
.flat_map(|val| val.to_le_bytes())
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl PixelTestingExt for F32x3 {
|
||||
type ImagePixel = image::Rgb<f32>;
|
||||
type Container = Vec<f32>;
|
||||
|
||||
fn load_image_buffer(
|
||||
img_reader: ImageReader<Cursor<Vec<u8>>>,
|
||||
) -> ImageBuffer<Self::ImagePixel, Self::Container> {
|
||||
img_reader.decode().unwrap().to_rgb32f()
|
||||
}
|
||||
|
||||
fn img_into_bytes(img: ImageBuffer<Self::ImagePixel, Self::Container>) -> Vec<u8> {
|
||||
img.as_raw().iter().flat_map(|&c| c.to_le_bytes()).collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl PixelTestingExt for F32x4 {
|
||||
type ImagePixel = image::Rgba<f32>;
|
||||
type Container = Vec<f32>;
|
||||
|
||||
fn load_image_buffer(
|
||||
img_reader: ImageReader<Cursor<Vec<u8>>>,
|
||||
) -> ImageBuffer<Self::ImagePixel, Self::Container> {
|
||||
img_reader.decode().unwrap().to_rgba32f()
|
||||
}
|
||||
|
||||
fn img_into_bytes(img: ImageBuffer<Self::ImagePixel, Self::Container>) -> Vec<u8> {
|
||||
img.as_raw().iter().flat_map(|&c| c.to_le_bytes()).collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl PixelTestingExt for U8x4 {
|
||||
type ImagePixel = image::Rgba<u8>;
|
||||
type Container = Vec<u8>;
|
||||
|
||||
fn load_image_buffer(
|
||||
img_reader: ImageReader<Cursor<Vec<u8>>>,
|
||||
) -> ImageBuffer<Self::ImagePixel, Self::Container> {
|
||||
img_reader.decode().unwrap().to_rgba8()
|
||||
}
|
||||
|
||||
fn img_into_bytes(img: ImageBuffer<Self::ImagePixel, Self::Container>) -> Vec<u8> {
|
||||
img.into_raw()
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn cpu_ext_into_str(cpu_extensions: CpuExtensions) -> &'static str {
|
||||
match cpu_extensions {
|
||||
CpuExtensions::None => "rust",
|
||||
CpuExtensions::Simd128 => "simd128",
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
use std::ops::Deref;
|
||||
|
||||
use fast_image_resize::images::Image;
|
||||
use fast_image_resize::{FilterType, ResizeAlg, ResizeOptions, Resizer};
|
||||
use image::{ImageBuffer, imageops};
|
||||
|
||||
use super::load_images::{PixelTestingExt, cpu_ext_into_str};
|
||||
use super::utils::now;
|
||||
|
||||
const ALG_NAMES: [&str; 5] = ["Nearest", "Box", "Bilinear", "Bicubic", "Lanczos3"];
|
||||
const NEW_WIDTH: u32 = 852;
|
||||
const NEW_HEIGHT: u32 = 567;
|
||||
|
||||
/// Resize the image with the help of "image" crate (https://crates.io/crates/image)
|
||||
pub fn image_resize<P, C>(
|
||||
src_image: &ImageBuffer<P, C>,
|
||||
) -> impl Iterator<Item = (String, String, f64)>
|
||||
where
|
||||
P: image::Pixel + 'static,
|
||||
C: Deref<Target = [P::Subpixel]>,
|
||||
{
|
||||
let lib_name = "image".to_string();
|
||||
ALG_NAMES.iter().map(move |&alg_name| {
|
||||
let (filter, sample_size) = match alg_name {
|
||||
"Nearest" => (imageops::Nearest, 80),
|
||||
"Bilinear" => (imageops::Triangle, 50),
|
||||
"Bicubic" => (imageops::CatmullRom, 30),
|
||||
"Lanczos3" => (imageops::Lanczos3, 20),
|
||||
_ => return (lib_name.clone(), alg_name.to_string(), 0.0),
|
||||
};
|
||||
let start = now();
|
||||
for _ in 0..sample_size {
|
||||
imageops::resize(src_image, NEW_WIDTH, NEW_HEIGHT, filter);
|
||||
}
|
||||
let speed = (now() - start) / sample_size as f64;
|
||||
(lib_name.clone(), alg_name.to_string(), speed)
|
||||
})
|
||||
}
|
||||
|
||||
/// Resize image with help of "resize" crate (https://crates.io/crates/resize)
|
||||
pub fn resize_resize<Format, Out>(
|
||||
pixel_format: Format,
|
||||
src_image: &[Format::InputPixel],
|
||||
src_width: u32,
|
||||
src_height: u32,
|
||||
) -> Vec<(String, String, f64)>
|
||||
where
|
||||
Out: Clone,
|
||||
Format: resize::PixelFormat<OutputPixel = Out> + Copy,
|
||||
{
|
||||
let lib_name = "resize".to_string();
|
||||
let mut results = Vec::with_capacity(ALG_NAMES.len());
|
||||
for alg_name in ALG_NAMES {
|
||||
let mut dst =
|
||||
vec![pixel_format.into_pixel(Format::new()); (NEW_WIDTH * NEW_HEIGHT) as usize];
|
||||
let sample_size = if alg_name == "Lanczos3" { 60 } else { 100 };
|
||||
|
||||
let filter = match alg_name {
|
||||
"Nearest" => resize::Type::Point,
|
||||
"Box" => resize::Type::Custom(resize::Filter::box_filter(0.5)),
|
||||
"Bilinear" => resize::Type::Triangle,
|
||||
"Bicubic" => resize::Type::Catrom,
|
||||
"Lanczos3" => resize::Type::Lanczos3,
|
||||
_ => continue,
|
||||
};
|
||||
let mut resizer = resize::new(
|
||||
src_width as usize,
|
||||
src_height as usize,
|
||||
NEW_WIDTH as usize,
|
||||
NEW_HEIGHT as usize,
|
||||
pixel_format,
|
||||
filter,
|
||||
)
|
||||
.unwrap();
|
||||
let start = now();
|
||||
for _ in 0..sample_size {
|
||||
resizer.resize(src_image, &mut dst).unwrap();
|
||||
}
|
||||
let speed = (now() - start) / sample_size as f64;
|
||||
results.push((lib_name.clone(), alg_name.to_string(), speed));
|
||||
}
|
||||
results
|
||||
}
|
||||
|
||||
/// Resize image with the help of "fast_imager_resize" crate
|
||||
pub fn fir_resize<P: PixelTestingExt>(
|
||||
src_image_data: Image<'static>,
|
||||
use_alpha: bool,
|
||||
) -> Vec<(String, String, f64)> {
|
||||
let mut results = Vec::with_capacity(ALG_NAMES.len());
|
||||
let mut dst_image = Image::new(NEW_WIDTH, NEW_HEIGHT, src_image_data.pixel_type());
|
||||
let cpu_extensions = P::cpu_extensions();
|
||||
for cpu_ext in cpu_extensions {
|
||||
let lib_name = format!("fir {}", cpu_ext_into_str(cpu_ext));
|
||||
for alg_name in ALG_NAMES {
|
||||
let resize_alg = match alg_name {
|
||||
"Nearest" => ResizeAlg::Nearest,
|
||||
"Box" => ResizeAlg::Convolution(FilterType::Box),
|
||||
"Bilinear" => ResizeAlg::Convolution(FilterType::Bilinear),
|
||||
"Bicubic" => ResizeAlg::Convolution(FilterType::CatmullRom),
|
||||
"Lanczos3" => ResizeAlg::Convolution(FilterType::Lanczos3),
|
||||
_ => continue,
|
||||
};
|
||||
let mut fast_resizer = Resizer::new();
|
||||
unsafe {
|
||||
fast_resizer.set_cpu_extensions(cpu_ext);
|
||||
}
|
||||
let sample_size = 100;
|
||||
let resize_options = ResizeOptions::new()
|
||||
.resize_alg(resize_alg)
|
||||
.use_alpha(use_alpha);
|
||||
let start = now();
|
||||
for _ in 0..sample_size {
|
||||
fast_resizer
|
||||
.resize(&src_image_data, &mut dst_image, &resize_options)
|
||||
.unwrap()
|
||||
}
|
||||
let speed = (now() - start) / sample_size as f64;
|
||||
results.push((lib_name.clone(), alg_name.to_string(), speed));
|
||||
}
|
||||
}
|
||||
results
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
use js_sys::Uint8Array;
|
||||
use wasm_bindgen::prelude::*;
|
||||
use web_sys::{Request, RequestInit, RequestMode, Response};
|
||||
|
||||
pub fn set_panic_hook() {
|
||||
// When the `console_error_panic_hook` feature is enabled, we can call the
|
||||
// `set_panic_hook` function at least once during initialization, and then
|
||||
// we will get better error messages if our code ever panics.
|
||||
//
|
||||
// For more details see
|
||||
// https://github.com/rustwasm/console_error_panic_hook#readme
|
||||
console_error_panic_hook::set_once();
|
||||
}
|
||||
|
||||
pub async fn load_file(path: &str) -> Result<Vec<u8>, JsValue> {
|
||||
let opts = RequestInit::new();
|
||||
opts.set_method("GET");
|
||||
opts.set_mode(RequestMode::Cors);
|
||||
|
||||
let request = Request::new_with_str_and_init(path, &opts)?;
|
||||
|
||||
let window = web_sys::window().unwrap();
|
||||
let resp = window.fetch_with_request(&request).await?;
|
||||
|
||||
let resp: Response = resp.dyn_into()?;
|
||||
|
||||
let buffer = resp.array_buffer()?.await?;
|
||||
let bytes = Uint8Array::new(&buffer).to_vec();
|
||||
|
||||
Ok(bytes)
|
||||
}
|
||||
|
||||
/// Returns a high-resolution timestamp in milliseconds.
|
||||
pub fn now() -> f64 {
|
||||
web_sys::window().unwrap().performance().unwrap().now()
|
||||
}
|
||||
@@ -76,3 +76,15 @@ report files, such as `./benchmarks-wasm32.md`:
|
||||
```shell
|
||||
CARGO_TARGET_WASM32_WASIP2_RUNNER="wasmtime --dir=. --env WRITE_COMPARE_RESULT=1 --" cargo bench --no-fail-fast -- --color=always Compare
|
||||
```
|
||||
|
||||
# Wasm32 in browser
|
||||
|
||||
Install Wasm-Pack:
|
||||
|
||||
```shell
|
||||
cargo install wasm-pack
|
||||
cargo install wasm-bindgen-cli
|
||||
cargo install geckodriver
|
||||
|
||||
NO_HEADLESS=1 cargo bench --bench bench_wasm -- bench_image_l8
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user