Bench and test auto
All checks were successful
All checks were successful
This commit is contained in:
parent
f25d41a29d
commit
1f7957778f
|
@ -59,6 +59,33 @@ pub fn criterion_benchmark(c: &mut Criterion) {
|
||||||
|
|
||||||
group.finish();
|
group.finish();
|
||||||
|
|
||||||
|
let mut group = c.benchmark_group("blurhash-update-auto");
|
||||||
|
|
||||||
|
let inputs = [
|
||||||
|
"data/19dd1c444d1c7939.png",
|
||||||
|
"data/f73d2ee39133d871.jpg",
|
||||||
|
"data/shenzi.png",
|
||||||
|
];
|
||||||
|
|
||||||
|
for input in inputs {
|
||||||
|
group.bench_with_input(BenchmarkId::from_parameter(input), input, |b, i| {
|
||||||
|
let img = image::open(i).unwrap();
|
||||||
|
let width = img.width();
|
||||||
|
let height = img.height();
|
||||||
|
let rgba = img.to_rgba8();
|
||||||
|
let bytes = rgba.as_bytes();
|
||||||
|
|
||||||
|
b.iter(|| {
|
||||||
|
let _bhash = black_box(blurhash_update::auto_encode(
|
||||||
|
ImageBounds { width, height },
|
||||||
|
bytes,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
group.finish();
|
||||||
|
|
||||||
let mut group = c.benchmark_group("blurhash-update-skip");
|
let mut group = c.benchmark_group("blurhash-update-skip");
|
||||||
|
|
||||||
let inputs = [
|
let inputs = [
|
||||||
|
|
|
@ -12,6 +12,9 @@ struct Args {
|
||||||
/// Height of the provided image
|
/// Height of the provided image
|
||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
height: u32,
|
height: u32,
|
||||||
|
|
||||||
|
#[clap(long, default_value = "8")]
|
||||||
|
skip: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Example usage:
|
// Example usage:
|
||||||
|
@ -20,8 +23,16 @@ struct Args {
|
||||||
// cargo r --example --release -- --width blah --height blah
|
// cargo r --example --release -- --width blah --height blah
|
||||||
// ```
|
// ```
|
||||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let Args { width, height } = Args::parse();
|
let Args {
|
||||||
let mut encoder = Encoder::new(Components { x: 4, y: 3 }, ImageBounds { width, height }, 8)?;
|
width,
|
||||||
|
height,
|
||||||
|
skip,
|
||||||
|
} = Args::parse();
|
||||||
|
let mut encoder = Encoder::new(
|
||||||
|
Components { x: 4, y: 3 },
|
||||||
|
ImageBounds { width, height },
|
||||||
|
skip,
|
||||||
|
)?;
|
||||||
|
|
||||||
let mut stdin = std::io::stdin().lock();
|
let mut stdin = std::io::stdin().lock();
|
||||||
let mut buf = [0u8; 1024];
|
let mut buf = [0u8; 1024];
|
||||||
|
|
137
src/lib.rs
137
src/lib.rs
|
@ -42,7 +42,13 @@ struct ComponentState {
|
||||||
|
|
||||||
/// Error raised when too many components are requested
|
/// Error raised when too many components are requested
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ComponentError;
|
pub enum ConfigurationError {
|
||||||
|
/// Component values are not within the required range.
|
||||||
|
InvalidComponentCount,
|
||||||
|
|
||||||
|
/// Skip value must not be zero
|
||||||
|
ZeroSkip,
|
||||||
|
}
|
||||||
|
|
||||||
/// Encoder type used to produce blurhashes
|
/// Encoder type used to produce blurhashes
|
||||||
pub struct Encoder {
|
pub struct Encoder {
|
||||||
|
@ -60,15 +66,95 @@ pub fn encode(
|
||||||
components: Components,
|
components: Components,
|
||||||
bounds: ImageBounds,
|
bounds: ImageBounds,
|
||||||
rgba8_image: &[u8],
|
rgba8_image: &[u8],
|
||||||
) -> Result<String, ComponentError> {
|
) -> Result<String, ConfigurationError> {
|
||||||
let mut encoder = Encoder::new(components, bounds, 1)?;
|
let mut encoder = Encoder::new(components, bounds, 1)?;
|
||||||
encoder.update(rgba8_image);
|
encoder.update(rgba8_image);
|
||||||
Ok(encoder.finalize())
|
Ok(encoder.finalize())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A simple "encode this image please" function that automatically selects component and skip
|
||||||
|
/// values
|
||||||
|
///
|
||||||
|
/// The input image must be in the sRGB colorspace and be formatted as 8bit RGBA
|
||||||
|
pub fn auto_encode(bounds: ImageBounds, rgba8_image: &[u8]) -> String {
|
||||||
|
let mut encoder = Encoder::auto(bounds);
|
||||||
|
encoder.update(rgba8_image);
|
||||||
|
encoder.finalize()
|
||||||
|
}
|
||||||
|
|
||||||
|
// determine closest component ratio to input bounds
|
||||||
|
fn calculate_components(ImageBounds { width, height }: ImageBounds) -> Components {
|
||||||
|
let mut out = Components { x: 0, y: 0 };
|
||||||
|
|
||||||
|
let (out_longer, out_shorter, in_longer, in_shorter) = if width > height {
|
||||||
|
(&mut out.x, &mut out.y, width as f32, height as f32)
|
||||||
|
} else {
|
||||||
|
(&mut out.y, &mut out.x, height as f32, width as f32)
|
||||||
|
};
|
||||||
|
|
||||||
|
struct State {
|
||||||
|
similarity: f32,
|
||||||
|
ratio: (u32, u32),
|
||||||
|
}
|
||||||
|
|
||||||
|
let ratios = [(3, 3), (4, 3), (5, 3), (6, 3), (5, 2), (6, 2), (7, 2)];
|
||||||
|
|
||||||
|
let in_ratio = in_longer / in_shorter;
|
||||||
|
|
||||||
|
let State { ratio, .. } = ratios.into_iter().fold(
|
||||||
|
State {
|
||||||
|
similarity: f32::MAX,
|
||||||
|
ratio: (0, 0),
|
||||||
|
},
|
||||||
|
|state, (ratio_longer, ratio_shorter)| {
|
||||||
|
let ratio = ratio_longer as f32 / ratio_shorter as f32;
|
||||||
|
let diff = (ratio - in_ratio).abs();
|
||||||
|
|
||||||
|
if diff < state.similarity {
|
||||||
|
State {
|
||||||
|
similarity: diff,
|
||||||
|
ratio: (ratio_longer, ratio_shorter),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
state
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
*out_longer = ratio.0;
|
||||||
|
*out_shorter = ratio.1;
|
||||||
|
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
// target 256ish total pixels to process
|
||||||
|
fn calculate_skip(ImageBounds { width, height }: ImageBounds) -> u32 {
|
||||||
|
let target_1d = f32::sqrt((width * height / 256) as f32).floor() as u32;
|
||||||
|
|
||||||
|
let mut base = 1;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
if base * 2 < target_1d {
|
||||||
|
base *= 2;
|
||||||
|
} else {
|
||||||
|
break base;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Encoder {
|
impl Encoder {
|
||||||
|
/// Create an encoder that automatically picks Compoent and Skip values
|
||||||
|
///
|
||||||
|
/// This is a best-effort configuration
|
||||||
|
pub fn auto(bounds: ImageBounds) -> Self {
|
||||||
|
Self::new(calculate_components(bounds), bounds, calculate_skip(bounds))
|
||||||
|
.expect("Generated bounds are always valid")
|
||||||
|
}
|
||||||
|
|
||||||
/// Create a new Encoder to produce a blurhash
|
/// Create a new Encoder to produce a blurhash
|
||||||
///
|
///
|
||||||
|
/// The provided component x and y values must be between 1 and 9 inclusive.
|
||||||
|
///
|
||||||
/// The `skip` value indicates how many pixels can be skipped when proccessing the image. this
|
/// The `skip` value indicates how many pixels can be skipped when proccessing the image. this
|
||||||
/// value will be squared to produce the final skip value. When set to 1, no pixels will be
|
/// value will be squared to produce the final skip value. When set to 1, no pixels will be
|
||||||
/// skipped, when set to 2, one in four pixels will be processed. when set to 3, one in 9
|
/// skipped, when set to 2, one in four pixels will be processed. when set to 3, one in 9
|
||||||
|
@ -79,9 +165,13 @@ impl Encoder {
|
||||||
Components { x, y }: Components,
|
Components { x, y }: Components,
|
||||||
bounds: ImageBounds,
|
bounds: ImageBounds,
|
||||||
skip: u32,
|
skip: u32,
|
||||||
) -> Result<Self, ComponentError> {
|
) -> Result<Self, ConfigurationError> {
|
||||||
if !(1..=9).contains(&x) || !(1..=9).contains(&y) {
|
if !(1..=9).contains(&x) || !(1..=9).contains(&y) {
|
||||||
return Err(ComponentError);
|
return Err(ConfigurationError::InvalidComponentCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
if skip == 0 {
|
||||||
|
return Err(ConfigurationError::ZeroSkip);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
|
@ -266,13 +356,16 @@ fn sign_pow(val: f32, exp: f32) -> f32 {
|
||||||
sign(val) * val.abs().powf(exp)
|
sign(val) * val.abs().powf(exp)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for ComponentError {
|
impl std::fmt::Display for ConfigurationError {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
write!(f, "Components out of bounds")
|
match self {
|
||||||
|
Self::InvalidComponentCount => write!(f, "Components out of bounds"),
|
||||||
|
Self::ZeroSkip => write!(f, "Skip value cannot be zero"),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::error::Error for ComponentError {}
|
impl std::error::Error for ConfigurationError {}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
@ -317,7 +410,31 @@ mod tests {
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(hash, output, "expected output wrong");
|
assert_eq!(hash, output, "wrong output for {input}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn auto() {
|
||||||
|
let inputs = [
|
||||||
|
("data/19dd1c444d1c7939.png", (5, 3), 64),
|
||||||
|
("data/f73d2ee39133d871.jpg", (4, 3), 64),
|
||||||
|
("data/shenzi.png", (3, 3), 16),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (input, expected_components, expected_skip) in inputs {
|
||||||
|
let img = image::open(input).unwrap();
|
||||||
|
let (width, height) = img.dimensions();
|
||||||
|
|
||||||
|
let components = super::calculate_components(crate::ImageBounds { width, height });
|
||||||
|
let skip = super::calculate_skip(crate::ImageBounds { width, height });
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
(components.x, components.y),
|
||||||
|
expected_components,
|
||||||
|
"wrong ratio for {input}"
|
||||||
|
);
|
||||||
|
assert_eq!(skip, expected_skip, "wrong skip for {input}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -340,7 +457,7 @@ mod tests {
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(hash, output, "expected output wrong");
|
assert_eq!(hash, output, "wrong output for {input}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -381,7 +498,7 @@ mod tests {
|
||||||
|
|
||||||
let b2 = encoder.finalize();
|
let b2 = encoder.finalize();
|
||||||
|
|
||||||
assert_eq!(b1, b2);
|
assert_eq!(b1, b2, "wrong hash for {input} with {chunk_count} chunks");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue