mod animation_encoder;
#[doc(inline)]
pub use animation_encoder::*;
mod decoder;
#[doc(inline)]
pub use decoder::*;
mod animation_decoder;
#[doc(inline)]
pub use animation_decoder::*;
mod encoder;
#[doc(inline)]
pub use encoder::*;
pub use libwebp_sys::WebPEncodingError;
mod shared;
#[doc(inline)]
pub use shared::*;
pub use libwebp_sys::WebPConfig;
#[cfg(test)]
mod tests {
use std::ops::Deref;
use image::*;
use crate::*;
fn hsv_to_rgb(h: f64, s: f64, v: f64) -> [u8; 3] {
let h = (h - h.floor()) * 6.0;
let f = h - h.floor();
let u = (v * 255.0) as u8;
let p = (v * (1.0 - s) * 255.0) as u8;
let q = (v * (1.0 - s * f) * 255.0) as u8;
let t = (v * (1.0 - s * (1.0 - f)) * 255.0) as u8;
match h as u8 {
0 => [u, t, p],
1 => [q, u, p],
2 => [p, u, t],
3 => [p, q, u],
4 => [t, p, u],
5 => [u, p, q],
_ => unreachable!("h must be between 0.0 and 1.0"),
}
}
fn generate_color_wheel(width: u32, height: u32, background_alpha: bool) -> DynamicImage {
let f = |x, y| {
let width = width as f64;
let height = height as f64;
let x = x as f64 - width / 2.0;
let y = y as f64 - height / 2.0;
let theta = y.atan2(x);
let tau = 2.0 * std::f64::consts::PI;
let h = (theta + std::f64::consts::PI) / tau;
let s = (4.0 * (x * x + y * y) / width / height).sqrt();
let v = 1.0;
if s > 1.0 {
Rgba([0, 0, 0, if background_alpha { 0 } else { 255 }])
} else {
let [r, g, b] = hsv_to_rgb(h, s, v);
Rgba([r, g, b, 255])
}
};
DynamicImage::ImageRgba8(ImageBuffer::from_fn(width, height, f))
}
const SIZE: u32 = 96;
#[test]
fn encode_decode() {
let test_image_no_alpha = generate_color_wheel(SIZE, SIZE, false);
let encoded = Encoder::from_image(&test_image_no_alpha)
.unwrap()
.encode_lossless();
let decoded = Decoder::new(encoded.deref())
.decode()
.unwrap()
.to_image()
.to_rgb8();
assert_eq!(test_image_no_alpha.to_rgb8().deref(), decoded.deref());
let test_image_alpha = generate_color_wheel(SIZE, SIZE, true);
let encoded = Encoder::from_image(&test_image_alpha)
.unwrap()
.encode_lossless();
let decoded = Decoder::new(encoded.deref())
.decode()
.unwrap()
.to_image()
.to_rgba8();
fn compare(p1: &Rgba<u8>, p2: &Rgba<u8>) -> bool {
if p1.channels()[3] == 0 && p2.channels()[3] == 0 {
true
} else {
p1 == p2
}
}
for (p1, p2) in test_image_alpha.to_rgba8().pixels().zip(decoded.pixels()) {
assert!(compare(p1, p2))
}
}
#[test]
fn get_info() {
let test_image_no_alpha = generate_color_wheel(SIZE, SIZE, false);
let encoded = Encoder::from_image(&test_image_no_alpha)
.unwrap()
.encode_lossless();
let features = BitstreamFeatures::new(encoded.deref()).unwrap();
assert_eq!(features.width(), SIZE);
assert_eq!(features.height(), SIZE);
assert!(!features.has_alpha());
assert!(!features.has_animation());
let test_image_alpha = generate_color_wheel(SIZE, SIZE, true);
let encoded = Encoder::from_image(&test_image_alpha)
.unwrap()
.encode_lossless();
let features = BitstreamFeatures::new(encoded.deref()).unwrap();
assert_eq!(features.width(), SIZE);
assert_eq!(features.height(), SIZE);
assert!(features.has_alpha());
assert!(!features.has_animation());
}
#[test]
fn anim_encode_decode() {
let width = 32u32;
let height = 32u32;
let mut encode_images = vec![];
let mut config = WebPConfig::new().unwrap();
config.lossless = 1;
config.alpha_compression = 0;
config.alpha_filtering = 0;
config.quality = 75f32;
let mut encoder = AnimEncoder::new(width, height, &config);
encoder.set_bgcolor([255, 0, 0, 255]);
encoder.set_loop_count(3);
let v = generate_color_wheel(width, height, true);
encode_images.push(v);
let v = generate_color_wheel(width, height, true);
encode_images.push(v);
let v = generate_color_wheel(width, height, true);
encode_images.push(v);
let v = generate_color_wheel(width, height, true);
encode_images.push(v);
let mut t = 1000;
for v in encode_images.iter() {
encoder.add_frame(AnimFrame::from_image(v, t).unwrap());
t += 250;
}
let webp = encoder.encode();
let mut decode_images: Vec<DynamicImage> = vec![];
match AnimDecoder::new(&webp).decode() {
Ok(frames) => {
decode_images.extend((&frames).into_iter().map(|a| (&a).into()));
}
Err(mes) => {
println!("{}", mes);
}
}
let mut encode_rgba = vec![];
for v in encode_images.into_iter() {
let value = DynamicImage::ImageRgba8(v.to_rgba8());
encode_rgba.push(value);
}
fn compare(p1: &Rgba<u8>, p2: &Rgba<u8>) -> bool {
if p1.channels()[3] == 0 && p2.channels()[3] == 0 {
true
} else {
p1 == p2
}
}
for (i1, i2) in encode_rgba.iter().zip(decode_images.iter()) {
for (p1, p2) in i1.to_rgba8().pixels().zip(i2.to_rgba8().pixels()) {
assert!(compare(p1, p2))
}
}
}
}