A couple simplifications
This commit is contained in:
parent
a07038a61d
commit
d55e17ec78
|
@ -45,13 +45,14 @@ pub fn criterion_benchmark(c: &mut Criterion) {
|
|||
let bytes = rgba.as_bytes();
|
||||
|
||||
b.iter(|| {
|
||||
let mut encoder = blurhash_update::encoder(
|
||||
Components { x: 4, y: 3 },
|
||||
ImageBounds { width, height },
|
||||
)
|
||||
.unwrap();
|
||||
encoder.update(bytes);
|
||||
let _bhash = black_box(encoder.finalize());
|
||||
let _bhash = black_box(
|
||||
blurhash_update::encode(
|
||||
Components { x: 4, y: 3 },
|
||||
ImageBounds { width, height },
|
||||
bytes,
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
101
src/lib.rs
101
src/lib.rs
|
@ -3,7 +3,7 @@ mod srgb_lookup;
|
|||
|
||||
use std::f32::consts::PI;
|
||||
|
||||
use srgb_lookup::SRGB_LOOKUP;
|
||||
use srgb_lookup::srgb_to_linear;
|
||||
|
||||
pub struct Components {
|
||||
pub x: u32,
|
||||
|
@ -38,12 +38,21 @@ pub struct Encoder {
|
|||
bounds: ImageBounds,
|
||||
}
|
||||
|
||||
pub fn encoder(components: Components, bounds: ImageBounds) -> Result<Encoder, ComponentError> {
|
||||
Encoder::new(components, bounds)
|
||||
pub fn encode(
|
||||
components: Components,
|
||||
bounds: ImageBounds,
|
||||
rgba8_image: &[u8],
|
||||
) -> Result<String, ComponentError> {
|
||||
let mut encoder = Encoder::new(components, bounds)?;
|
||||
encoder.update(rgba8_image);
|
||||
Ok(encoder.finalize())
|
||||
}
|
||||
|
||||
impl Encoder {
|
||||
fn new(Components { x, y }: Components, bounds: ImageBounds) -> Result<Self, ComponentError> {
|
||||
pub fn new(
|
||||
Components { x, y }: Components,
|
||||
bounds: ImageBounds,
|
||||
) -> Result<Self, ComponentError> {
|
||||
if !(1..=9).contains(&x) || !(1..=9).contains(&y) {
|
||||
return Err(ComponentError);
|
||||
}
|
||||
|
@ -60,27 +69,27 @@ impl Encoder {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn update(&mut self, buf: &[u8]) {
|
||||
pub fn update(&mut self, rgba8_image: &[u8]) {
|
||||
const BYTES_PER_PIXEL: usize = 4;
|
||||
|
||||
// get offset in terms of already-processed bytes
|
||||
let offset = self.index % BYTES_PER_PIXEL;
|
||||
// get offset in terms of remaining bytes on head of buf
|
||||
// get offset in terms of remaining bytes on head of rgba8_image
|
||||
let offset = (BYTES_PER_PIXEL - offset) % BYTES_PER_PIXEL;
|
||||
|
||||
for (ComponentState { basis, .. }, [_, g, b]) in self.factors.iter_mut() {
|
||||
for (byte, value) in buf[..offset].iter().zip(
|
||||
[&mut *b, &mut *g][0..offset.saturating_sub(2)]
|
||||
for (byte, value) in rgba8_image[..offset].iter().zip(
|
||||
[&mut *b, &mut *g][..offset.saturating_sub(BYTES_PER_PIXEL - 2)]
|
||||
.iter_mut()
|
||||
.rev(),
|
||||
) {
|
||||
**value += *basis * SRGB_LOOKUP[*byte as usize]
|
||||
**value += *basis * srgb_to_linear(*byte);
|
||||
}
|
||||
}
|
||||
|
||||
let pixels = ((self.index + offset) / BYTES_PER_PIXEL) as u32;
|
||||
|
||||
let mut chunks = buf[offset..].chunks_exact(BYTES_PER_PIXEL);
|
||||
let mut chunks = rgba8_image[offset..].chunks_exact(BYTES_PER_PIXEL);
|
||||
|
||||
for (i, chunk) in (&mut chunks).enumerate() {
|
||||
let px = pixels + i as u32;
|
||||
|
@ -97,14 +106,14 @@ impl Encoder {
|
|||
self.bounds.height as _,
|
||||
);
|
||||
|
||||
*r += basis * SRGB_LOOKUP[chunk[0] as usize];
|
||||
*g += basis * SRGB_LOOKUP[chunk[1] as usize];
|
||||
*b += basis * SRGB_LOOKUP[chunk[2] as usize];
|
||||
*r += basis * srgb_to_linear(chunk[0]);
|
||||
*g += basis * srgb_to_linear(chunk[1]);
|
||||
*b += basis * srgb_to_linear(chunk[2]);
|
||||
}
|
||||
}
|
||||
|
||||
if !chunks.remainder().is_empty() {
|
||||
let px = pixels + (buf[offset..].len() / BYTES_PER_PIXEL) as u32;
|
||||
let px = pixels + (rgba8_image[offset..].len() / BYTES_PER_PIXEL) as u32;
|
||||
let px_x = px % self.bounds.width;
|
||||
let px_y = px / self.bounds.width;
|
||||
|
||||
|
@ -119,12 +128,12 @@ impl Encoder {
|
|||
);
|
||||
|
||||
for (byte, value) in chunks.remainder().iter().zip([&mut *r, &mut *g, &mut *b]) {
|
||||
*value += *basis * SRGB_LOOKUP[*byte as usize]
|
||||
*value += *basis * srgb_to_linear(*byte);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.index += buf.len();
|
||||
self.index += rgba8_image.len();
|
||||
}
|
||||
|
||||
pub fn finalize(mut self) -> String {
|
||||
|
@ -146,21 +155,15 @@ impl Encoder {
|
|||
let size_flag = self.components.x - 1 + (self.components.y - 1) * 9;
|
||||
base83::encode(size_flag, 1, &mut blurhash);
|
||||
|
||||
let maximum_value = if !ac.is_empty() {
|
||||
let maximum = ac.iter().fold(0.0_f32, |maximum, (_, [r, g, b])| {
|
||||
maximum.max(r.abs()).max(g.abs()).max(b.abs())
|
||||
});
|
||||
let maximum = ac.iter().fold(0.0_f32, |maximum, (_, [r, g, b])| {
|
||||
maximum.max(r.abs()).max(g.abs()).max(b.abs())
|
||||
});
|
||||
|
||||
let quantized_maximum = (maximum * 166. - 0.5).floor().max(0.) as u32;
|
||||
let quantized_maximum = (maximum * 166. - 0.5).floor().max(0.) as u32;
|
||||
|
||||
base83::encode(quantized_maximum, 1, &mut blurhash);
|
||||
base83::encode(quantized_maximum, 1, &mut blurhash);
|
||||
|
||||
(quantized_maximum + 1) as f32 / 166.
|
||||
} else {
|
||||
base83::encode(0, 1, &mut blurhash);
|
||||
|
||||
1.
|
||||
};
|
||||
let maximum_value = (quantized_maximum + 1) as f32 / 166.;
|
||||
|
||||
base83::encode(encode_dc(dc), 4, &mut blurhash);
|
||||
|
||||
|
@ -238,19 +241,43 @@ mod tests {
|
|||
let width = 4;
|
||||
let height = 4;
|
||||
|
||||
let mut encoder = super::encoder(
|
||||
let b1 = super::encode(
|
||||
crate::Components { x: 4, y: 3 },
|
||||
crate::ImageBounds { width, height },
|
||||
&input,
|
||||
)
|
||||
.unwrap();
|
||||
encoder.update(&input);
|
||||
let b1 = encoder.finalize();
|
||||
|
||||
let b2 = blurhash::encode(4, 3, width, height, &input).unwrap();
|
||||
|
||||
assert_eq!(b1, b2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn one_component() {
|
||||
let inputs = [
|
||||
"data/19dd1c444d1c7939.png",
|
||||
"data/f73d2ee39133d871.jpg",
|
||||
"data/shenzi.png",
|
||||
];
|
||||
|
||||
for input in inputs {
|
||||
let img = image::open(input).unwrap();
|
||||
let (width, height) = img.dimensions();
|
||||
|
||||
let b1 = super::encode(
|
||||
crate::Components { x: 1, y: 1 },
|
||||
crate::ImageBounds { width, height },
|
||||
img.to_rgba8().as_bytes(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let b2 = blurhash::encode(1, 1, width, height, img.to_rgba8().as_bytes()).unwrap();
|
||||
|
||||
assert_eq!(b1, b2);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn matches_blurhash() {
|
||||
let inputs = [
|
||||
|
@ -263,13 +290,12 @@ mod tests {
|
|||
let img = image::open(input).unwrap();
|
||||
let (width, height) = img.dimensions();
|
||||
|
||||
let mut encoder = super::encoder(
|
||||
let b1 = super::encode(
|
||||
crate::Components { x: 4, y: 3 },
|
||||
crate::ImageBounds { width, height },
|
||||
img.to_rgba8().as_bytes(),
|
||||
)
|
||||
.unwrap();
|
||||
encoder.update(img.to_rgba8().as_bytes());
|
||||
let b1 = encoder.finalize();
|
||||
|
||||
let b2 = blurhash::encode(4, 3, width, height, img.to_rgba8().as_bytes()).unwrap();
|
||||
|
||||
|
@ -291,16 +317,15 @@ mod tests {
|
|||
let rgba8_img = img.to_rgba8();
|
||||
let bytes = rgba8_img.as_bytes();
|
||||
|
||||
let mut encoder = super::encoder(
|
||||
let b1 = super::encode(
|
||||
crate::Components { x: 4, y: 3 },
|
||||
crate::ImageBounds { width, height },
|
||||
bytes,
|
||||
)
|
||||
.unwrap();
|
||||
encoder.update(bytes);
|
||||
let b1 = encoder.finalize();
|
||||
|
||||
for chunk_count in 2..20 {
|
||||
encoder = super::encoder(
|
||||
let mut encoder = super::Encoder::new(
|
||||
crate::Components { x: 4, y: 3 },
|
||||
crate::ImageBounds { width, height },
|
||||
)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
pub(crate) static SRGB_LOOKUP: [f32; 256] = [
|
||||
static SRGB_LOOKUP: [f32; 256] = [
|
||||
0.0,
|
||||
0.000303527,
|
||||
0.000607054,
|
||||
|
@ -256,3 +256,7 @@ pub(crate) static SRGB_LOOKUP: [f32; 256] = [
|
|||
0.9911022,
|
||||
1.0,
|
||||
];
|
||||
|
||||
pub(crate) fn srgb_to_linear(value: u8) -> f32 {
|
||||
SRGB_LOOKUP[value as usize]
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue