Write new benches for WASM32 to run them in headless browser.

This commit is contained in:
Kirill Kuzminykh
2026-07-21 00:21:02 +03:00
parent 8435a527f1
commit 1e09ebf727
18 changed files with 1626 additions and 154 deletions
Generated
+194
View File
@@ -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"
+9
View File
@@ -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
View File
@@ -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")]
+105
View File
@@ -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)
}
+123
View File
@@ -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());
}
+2
View File
@@ -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;
+2 -1
View File
@@ -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
View File
@@ -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);
+350
View File
@@ -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",
}
}
+25
View File
@@ -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)
}
+107
View File
@@ -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()
})
});
}
}
}
+29
View File
@@ -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
+54
View File
@@ -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>
+71
View File
@@ -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;
}
+350
View File
@@ -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",
}
}
+123
View File
@@ -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
}
+36
View File
@@ -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()
}
+12
View File
@@ -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
```