Skip to content

Commit

Permalink
Support for animated WebP
Browse files Browse the repository at this point in the history
  • Loading branch information
Lymphatus committed Aug 1, 2024
1 parent a67f247 commit 2114a02
Show file tree
Hide file tree
Showing 4 changed files with 141 additions and 31 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "libcaesium"
version = "0.16.2"
version = "0.16.3"
authors = ["Matteo Paonessa <matteo.paonessa@gmail.com>"]
edition = "2021"
categories = ["multimedia::images"]
Expand Down
150 changes: 120 additions & 30 deletions src/webp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ use std::io::{Read, Write};
use std::ops::Deref;

use bytes::Bytes;
use image::{DynamicImage, ImageBuffer};
use img_parts::{DynImage, ImageEXIF, ImageICC};
use img_parts::webp::WebP as PartsWebp;
use webp::{AnimDecoder, AnimEncoder, AnimFrame, WebPConfig};

use crate::CSParameters;
use crate::error::CaesiumError;
Expand Down Expand Up @@ -50,8 +52,6 @@ pub fn compress_in_memory(
) -> Result<Vec<u8>, CaesiumError> {
let mut iccp: Option<Bytes> = None;
let mut exif: Option<Bytes> = None;

let decoder = webp::Decoder::new(&in_file);

if parameters.keep_metadata {
(iccp, exif) = DynImage::from_bytes(in_file.clone().into())
Expand All @@ -62,41 +62,108 @@ pub fn compress_in_memory(
.map_or((None, None), |dimg| (dimg.icc_profile(), dimg.exif()));
}

let input_webp = match decoder.decode() {
Some(img) => img,
None => {
return Err(CaesiumError {
message: "Error decoding WebP image".to_string(),
code: 20304,
})
}
};

let mut input_image = input_webp.to_image();
let must_resize = parameters.width > 0 || parameters.height > 0;
if must_resize {
input_image = resize_image(input_image, parameters.width, parameters.height);
}

let encoder = match webp::Encoder::from_image(&input_image) {
Ok(encoder) => encoder,
Err(e) => {
return Err(CaesiumError {
message: e.to_string(),
code: 20305,
})
let anim_decoder = AnimDecoder::new(&in_file);
let frames = anim_decoder.decode().map_err(|e| CaesiumError {
message: e.to_string(),
code: 20304,
})?;
let is_animated = frames.has_animation();

let encoded_image_memory = if is_animated {
let mut config = match WebPConfig::new() {
Ok(c) => c,
Err(_) => {
return Err(CaesiumError {
message: "Cannot initialize WebP config".into(),
code: 20309,
});
}
};
config.lossless = if parameters.optimize { 1 } else { 0 };
config.alpha_compression = if parameters.optimize { 0 } else { 1 };
config.quality = parameters.webp.quality as f32;

let mut images_data = vec![];
let mut width = 0;
let mut height = 0;

for (i, f) in frames.into_iter().enumerate() {
if must_resize {
let mut dyn_image = to_dynamic_image(f);
dyn_image = resize_image(dyn_image, parameters.width, parameters.height);
if i == 0 {
width = dyn_image.width();
height = dyn_image.height();
}

images_data.push(dyn_image);
} else {
width = f.width();
height = f.height();
break;
}
}
};

let encoded_image_memory = if parameters.optimize {
let mut encoder = AnimEncoder::new(width, height, &config);
encoder.set_bgcolor(to_rgba(frames.bg_color));
encoder.set_loop_count(frames.loop_count as i32);

let mut last_ms = 0;
for (i, f) in frames.into_iter().enumerate() {
let delay_ms = f.get_time_ms() - last_ms;
last_ms += delay_ms;

if must_resize {
if images_data.get(i).is_some() {
encoder.add_frame(AnimFrame::from_image(images_data.get(i).unwrap(), last_ms)
.map_err(|e| CaesiumError {
message: e.to_string(),
code: 20310,
})?);
}
} else {
encoder.add_frame(f);
}
}

encoder.encode()
} else {
let first_frame = match frames.get_frame(0) {
None => {
return Err(CaesiumError {
message: "Cannot get first frame".into(),
code: 20311,
});
}
Some(f) => f
};
let mut input_image = (&first_frame).into();
if must_resize {
encoder.encode(100.0)
input_image = resize_image(input_image, parameters.width, parameters.height);
}

let encoder = match webp::Encoder::from_image(&input_image) {
Ok(encoder) => encoder,
Err(e) => {
return Err(CaesiumError {
message: e.to_string(),
code: 20305,
})
}
};

if parameters.optimize {
if must_resize {
encoder.encode(100.0)
} else {
//TODO With resize can throw an error
encoder.encode_lossless()
}
} else {
//TODO With resize can throw an error
encoder.encode_lossless()
encoder.encode(parameters.webp.quality as f32)
}
} else {
encoder.encode(parameters.webp.quality as f32)
};

let encoded_image = encoded_image_memory.deref().to_vec();
Expand All @@ -121,3 +188,26 @@ pub fn compress_in_memory(
Ok(encoded_image)
}
}

fn to_rgba(value: u32) -> [u8; 4] {
[
((value >> 24) & 0xFF) as u8,
((value >> 16) & 0xFF) as u8,
((value >> 8) & 0xFF) as u8,
(value & 0xFF) as u8,
]
}

fn to_dynamic_image(frame: AnimFrame) -> DynamicImage {
if frame.get_layout().is_alpha() {
let image =
ImageBuffer::from_raw(frame.width(), frame.height(), frame.get_image().to_owned())
.expect("ImageBuffer couldn't be created");
DynamicImage::ImageRgba8(image)
} else {
let image =
ImageBuffer::from_raw(frame.width(), frame.height(), frame.get_image().to_owned())
.expect("ImageBuffer couldn't be created");
DynamicImage::ImageRgb8(image)
}
}
Binary file added tests/samples/uncompressed_animated.webp
Binary file not shown.
20 changes: 20 additions & 0 deletions tests/webp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,3 +157,23 @@ fn downscale_optimize() {
assert_eq!(image::image_dimensions(output).unwrap(), (150, 100));
remove_compressed_test_file(output)
}

#[test]
fn compress_animated_80() {
let output = "tests/samples/output/compressed_animated_80.webp";
initialize(output);
let mut params = caesium::initialize_parameters();
params.webp.quality = 80;
caesium::compress(
String::from("tests/samples/uncompressed_animated.webp"),
String::from(output),
&params,
)
.unwrap();
assert!(std::path::Path::new(output).exists());
assert_eq!(
infer::get_from_path(output).unwrap().unwrap().mime_type(),
"image/webp"
);
remove_compressed_test_file(output)
}

0 comments on commit 2114a02

Please sign in to comment.