Skip to content

Commit

Permalink
Improve the downsampling algorithm (#15)
Browse files Browse the repository at this point in the history
* Use improved downsampling algorithm for better quality

Add code to calculate the coefficients for the filter separate from the sampling

Add unit test for coefficient correctness. Improvements and fixes

Implement resampling based on cached weights

Optimize buffer read/writes for 3-channels

Remove debug code from lib.rs

Fix incorrect color clamping

Remove commented out code

Wrapped ispc pointer code into functions

Add getter and setter functions for 4 channels

Add support for 4 channel images using function pointers

Also tested it out with branching, but the performance was the same

Remove debug code. Add documentation for new downsampling function

Change resize crate dependency

Change back to previous test output name

Update build.rs to include new functions

Replace function pointers with branching

Split downsample function into two versions depending on channel count

Remove old downsampling functions. Add filter_scale variable that allows the kernel to be scaled.

This way the user can trade performance for detail, and the other way around

Remove old ISPC function anme from build.rs

Remove duplicate function

Remove a skippable write in the clean_and_write ISPC functions

Cargo fmt

Fix incorrect weight

Fix incorrect output address calculation

Fix benches

Update binaries

Some cleanup

More cleanup

Missed

* Fix incorrect assert

* Update binaries
  • Loading branch information
KYovchevski committed Feb 20, 2024
1 parent 4d259df commit bcd423a
Show file tree
Hide file tree
Showing 20 changed files with 621 additions and 109 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Generated by Cargo
# will have compiled files and executables
/target/
/.vscode/

Cargo.lock

Expand Down
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ image = "0.24.1"
stb_image = "0.3.0"
criterion = "0.5"
resize = "0.7"
fallible_collections = "0.4.5"

[[bench]]
name = "basic"
Expand Down
17 changes: 9 additions & 8 deletions benches/basic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,20 @@ use resize::{px::RGB, Type::Lanczos3};
use stb_image::image::{load, LoadResult};
use std::path::Path;

const DOWNSCALE: usize = 4;

pub fn ispc_downsampler(c: &mut Criterion) {
if let LoadResult::ImageU8(img) = load(Path::new("test_assets/square_test.png")) {
let src_fmt = if img.data.len() / (img.width * img.height) == 4 {
Format::RGBA8
Format::Rgba8
} else {
Format::RGB8
Format::Rgb8
};

let src_img = Image::new(&img.data, img.width as u32, img.height as u32, src_fmt);

let target_width = (img.width / 4) as u32;
let target_height = (img.height / 4) as u32;
let target_width = (img.width / DOWNSCALE) as u32;
let target_height = (img.height / DOWNSCALE) as u32;

c.bench_function("Downsample `square_test.png` using ispc_downsampler", |b| {
b.iter(|| downsample(&src_img, target_width, target_height))
Expand All @@ -25,19 +27,18 @@ pub fn ispc_downsampler(c: &mut Criterion) {

pub fn resize_rs(c: &mut Criterion) {
if let LoadResult::ImageU8(img) = load(Path::new("test_assets/square_test.png")) {
let target_width = img.width / 4;
let target_height = img.height / 4;
let target_width = img.width / DOWNSCALE;
let target_height = img.height / DOWNSCALE;

let src = img
.data
.chunks(3)
.map(|v| RGB::new(v[0], v[1], v[2]))
.collect::<Vec<_>>();

let mut dst = vec![RGB::new(0, 0, 0); target_width * target_height];

c.bench_function("Downsample `square_test.png` using resize", |b| {
b.iter(|| {
let mut dst = vec![RGB::new(0, 0, 0); target_width * target_height];
let mut resizer = resize::new(
img.width,
img.height,
Expand Down
5 changes: 4 additions & 1 deletion build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@ fn compile_bindings() {
.math_lib(MathLib::Fast)
.bindgen_builder(
builder()
.allowlist_function("resample")
.allowlist_function("resample_with_cached_weights_3")
.allowlist_function("resample_with_cached_weights_4")
.allowlist_function("calculate_weights")
.allowlist_function("calculate_weight_variables")
.allowlist_function("scale_to_alpha_coverage"),
)
.out_dir("src/ispc")
Expand Down
13 changes: 7 additions & 6 deletions examples/test.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use image::{RgbImage, RgbaImage};
use ispc_downsampler::{downsample, Format, Image};
use ispc_downsampler::{downsample_with_custom_scale, Format, Image};
use stb_image::image::{load, LoadResult};
use std::path::Path;
use std::time::Instant;
Expand All @@ -12,9 +12,9 @@ fn main() {
assert!(!img.data.is_empty());

let src_fmt = if img.data.len() / (img.width * img.height) == 4 {
Format::RGBA8
Format::Rgba8
} else {
Format::RGB8
Format::Rgb8
};

println!("Loaded image!");
Expand All @@ -26,20 +26,21 @@ fn main() {

let now = Instant::now();
println!("Downsampling started!");
let downsampled_pixels = downsample(&src_img, target_width, target_height);
let downsampled_pixels =
downsample_with_custom_scale(&src_img, target_width, target_height, 1.0);
println!("Finished downsampling in {:.2?}!", now.elapsed());

std::fs::create_dir_all("example_outputs").unwrap();
match src_fmt {
Format::RGBA8 => {
Format::Rgba8 | Format::Srgba8 => {
let save_image =
RgbaImage::from_vec(target_width, target_height, downsampled_pixels)
.unwrap();
save_image
.save("example_outputs/square_test_result.png")
.unwrap()
}
Format::RGB8 => {
Format::Rgb8 | Format::Srgb8 => {
let save_image =
RgbImage::from_vec(target_width, target_height, downsampled_pixels)
.unwrap();
Expand Down
185 changes: 179 additions & 6 deletions src/ispc/downsample_ispc.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,188 @@
#[allow(non_camel_case_types,dead_code,non_upper_case_globals,non_snake_case,improper_ctypes)]
pub mod downsample_ispc {
/* automatically generated by rust-bindgen 0.69.1 */
/* automatically generated by rust-bindgen 0.69.4 */

#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct WeightDimensions {
pub src_center: f32,
pub src_start: f32,
pub src_end: f32,
}
#[test]
fn bindgen_test_layout_WeightDimensions() {
const UNINIT: ::std::mem::MaybeUninit<WeightDimensions> = ::std::mem::MaybeUninit::uninit();
let ptr = UNINIT.as_ptr();
assert_eq!(
::std::mem::size_of::<WeightDimensions>(),
12usize,
concat!("Size of: ", stringify!(WeightDimensions))
);
assert_eq!(
::std::mem::align_of::<WeightDimensions>(),
4usize,
concat!("Alignment of ", stringify!(WeightDimensions))
);
assert_eq!(
unsafe { ::std::ptr::addr_of!((*ptr).src_center) as usize - ptr as usize },
0usize,
concat!(
"Offset of field: ",
stringify!(WeightDimensions),
"::",
stringify!(src_center)
)
);
assert_eq!(
unsafe { ::std::ptr::addr_of!((*ptr).src_start) as usize - ptr as usize },
4usize,
concat!(
"Offset of field: ",
stringify!(WeightDimensions),
"::",
stringify!(src_start)
)
);
assert_eq!(
unsafe { ::std::ptr::addr_of!((*ptr).src_end) as usize - ptr as usize },
8usize,
concat!(
"Offset of field: ",
stringify!(WeightDimensions),
"::",
stringify!(src_end)
)
);
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct SampleWeights {
pub vertical_weights: *const WeightCollection,
pub horizontal_weights: *const WeightCollection,
}
#[test]
fn bindgen_test_layout_SampleWeights() {
const UNINIT: ::std::mem::MaybeUninit<SampleWeights> = ::std::mem::MaybeUninit::uninit();
let ptr = UNINIT.as_ptr();
assert_eq!(
::std::mem::size_of::<SampleWeights>(),
16usize,
concat!("Size of: ", stringify!(SampleWeights))
);
assert_eq!(
::std::mem::align_of::<SampleWeights>(),
8usize,
concat!("Alignment of ", stringify!(SampleWeights))
);
assert_eq!(
unsafe { ::std::ptr::addr_of!((*ptr).vertical_weights) as usize - ptr as usize },
0usize,
concat!(
"Offset of field: ",
stringify!(SampleWeights),
"::",
stringify!(vertical_weights)
)
);
assert_eq!(
unsafe { ::std::ptr::addr_of!((*ptr).horizontal_weights) as usize - ptr as usize },
8usize,
concat!(
"Offset of field: ",
stringify!(SampleWeights),
"::",
stringify!(horizontal_weights)
)
);
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct WeightCollection {
pub starts: *const u32,
pub weight_counts: *const u32,
pub values: *const *const f32,
}
#[test]
fn bindgen_test_layout_WeightCollection() {
const UNINIT: ::std::mem::MaybeUninit<WeightCollection> = ::std::mem::MaybeUninit::uninit();
let ptr = UNINIT.as_ptr();
assert_eq!(
::std::mem::size_of::<WeightCollection>(),
24usize,
concat!("Size of: ", stringify!(WeightCollection))
);
assert_eq!(
::std::mem::align_of::<WeightCollection>(),
8usize,
concat!("Alignment of ", stringify!(WeightCollection))
);
assert_eq!(
unsafe { ::std::ptr::addr_of!((*ptr).starts) as usize - ptr as usize },
0usize,
concat!(
"Offset of field: ",
stringify!(WeightCollection),
"::",
stringify!(starts)
)
);
assert_eq!(
unsafe { ::std::ptr::addr_of!((*ptr).weight_counts) as usize - ptr as usize },
8usize,
concat!(
"Offset of field: ",
stringify!(WeightCollection),
"::",
stringify!(weight_counts)
)
);
assert_eq!(
unsafe { ::std::ptr::addr_of!((*ptr).values) as usize - ptr as usize },
16usize,
concat!(
"Offset of field: ",
stringify!(WeightCollection),
"::",
stringify!(values)
)
);
}
extern "C" {
pub fn calculate_weight_variables(
filter_scale: f32,
src: u32,
target: u32,
out_variables: *mut WeightDimensions,
);
}
extern "C" {
pub fn calculate_weights(
image_scale: f32,
filter_scale: f32,
vars: *const WeightDimensions,
weights: *mut f32,
);
}
extern "C" {
pub fn resample_with_cached_weights_3(
src_width: u32,
src_height: u32,
target_width: u32,
target_height: u32,
cached_weights: *const SampleWeights,
scratch_space: *mut u8,
src_data: *const u8,
out_data: *mut u8,
);
}
extern "C" {
pub fn resample(
width: u32,
height: u32,
stride: u32,
num_channels: u8,
pub fn resample_with_cached_weights_4(
src_width: u32,
src_height: u32,
target_width: u32,
target_height: u32,
cached_weights: *const SampleWeights,
scratch_space: *mut u8,
src_data: *const u8,
out_data: *mut u8,
);
Expand Down
Binary file modified src/ispc/downsample_ispcaarch64-pc-windows-msvc.lib
Binary file not shown.
Binary file modified src/ispc/downsample_ispcx86_64-pc-windows-msvc.lib
Binary file not shown.
Loading

0 comments on commit bcd423a

Please sign in to comment.