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();
|
||||
|
||||
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 inputs = [
|
||||
|
|
|
@ -12,6 +12,9 @@ struct Args {
|
|||
/// Height of the provided image
|
||||
#[clap(long)]
|
||||
height: u32,
|
||||
|
||||
#[clap(long, default_value = "8")]
|
||||
skip: u32,
|
||||
}
|
||||
|
||||
// Example usage:
|
||||
|
@ -20,8 +23,16 @@ struct Args {
|
|||
// cargo r --example --release -- --width blah --height blah
|
||||
// ```
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let Args { width, height } = Args::parse();
|
||||
let mut encoder = Encoder::new(Components { x: 4, y: 3 }, ImageBounds { width, height }, 8)?;
|
||||
let Args {
|
||||
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 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
|
||||
#[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
|
||||
pub struct Encoder {
|
||||
|
@ -60,15 +66,95 @@ pub fn encode(
|
|||
components: Components,
|
||||
bounds: ImageBounds,
|
||||
rgba8_image: &[u8],
|
||||
) -> Result<String, ComponentError> {
|
||||
) -> Result<String, ConfigurationError> {
|
||||
let mut encoder = Encoder::new(components, bounds, 1)?;
|
||||
encoder.update(rgba8_image);
|
||||
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 {
|
||||
/// 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
|
||||
///
|
||||
/// 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
|
||||
/// 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
|
||||
|
@ -79,9 +165,13 @@ impl Encoder {
|
|||
Components { x, y }: Components,
|
||||
bounds: ImageBounds,
|
||||
skip: u32,
|
||||
) -> Result<Self, ComponentError> {
|
||||
) -> Result<Self, ConfigurationError> {
|
||||
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 {
|
||||
|
@ -266,13 +356,16 @@ fn sign_pow(val: f32, exp: f32) -> f32 {
|
|||
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 {
|
||||
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)]
|
||||
mod tests {
|
||||
|
@ -317,7 +410,31 @@ mod tests {
|
|||
)
|
||||
.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();
|
||||
|
||||
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();
|
||||
|
||||
assert_eq!(b1, b2);
|
||||
assert_eq!(b1, b2, "wrong hash for {input} with {chunk_count} chunks");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue