Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
pub type Argb8888 = u32;
/// Average of 2 pixels, rounding up on tie.
///
/// It's fine if they're actually BGRA8888 or any other format,
/// this treats every channel the same.
pub fn pixel_average(c0: Argb8888, c1: Argb8888) -> Argb8888 {
(c0 | c1).wrapping_sub(((c0 ^ c1) >> 1) & 0x7f7f7f7f)
// version that rounds down:
// (c0 & c1).wrapping_add(((c0 ^ c1) >> 1) & 0x7f7f7f7f)
}
// pub just for easier asm viewing.
pub fn naive_pixel_average(a: Argb8888, b: Argb8888) -> Argb8888 {
let avg = |a: u8, b: u8| -> u8 {
// + 1 is to round up.
let v = (a as u32 + b as u32 + 1) / 2;
debug_assert!(v < 256, "{:?}", (a, b, v));
v as u8
};
let abs = a.to_ne_bytes();
let bbs = b.to_ne_bytes();
u32::from_ne_bytes([
avg(abs[0], bbs[0]),
avg(abs[1], bbs[1]),
avg(abs[2], bbs[2]),
avg(abs[3], bbs[3]),
])
}
#[test]
fn check_pixavg() {
for ch0 in 0..=255 {
for ch1 in 0..=255 {
// more fun color bithacks: this is equivalent to
// `ch0 as u32 * 0x1010101` 😉
let a = u32::from_ne_bytes([ch0, ch0, ch0, ch0]);
let b = u32::from_ne_bytes([ch1, ch1, ch1, ch1]);
check(a, b);
}
}
// smoke check to make sure nothing weird can happen,
// like overflow that crosses channels or something.
use rand::prelude::*;
let mut rng = thread_rng();
for _ in 0..100000 {
check(rng.gen(), rng.gen());
}
#[track_caller]
fn check(a: u32, b: u32) {
let avg0 = pixel_average(a, b);
let avg1 = naive_pixel_average(a, b);
assert_eq!(avg0, avg1, "{:#010x}, {:#010x} => {:#010x}, {:#010x}", a, b, avg0, avg1);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment