Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
name = "webp"
version = "0.3.1"
authors = ["Jared Forth <[email protected]>"]
edition = "2021"
edition = "2024"

description = "WebP conversion library."

Expand All @@ -15,12 +15,12 @@ keywords = ["image", "webp", "conversion"]
categories = ["external-ffi-bindings"]

[dependencies]
libwebp-sys = "0.9.3"
libwebp-sys = "0.13"
image = { version = "^0.25.0", default-features = false, optional = true }

[features]
default = ["img"]
img = [ "image" ]
img = ["image"]

[dev-dependencies]
image = "0.25"
2 changes: 1 addition & 1 deletion examples/animation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ fn main() {
config.lossless = 1;
config.alpha_compression = 0;
config.quality = 75f32;
let mut encoder = AnimEncoder::new(width as u32, height as u32, &config);
let mut encoder = AnimEncoder::new(width, height, &config);
encoder.set_bgcolor([255, 0, 0, 255]);
encoder.set_loop_count(3);
let mut time_ms = 1000;
Expand Down
8 changes: 3 additions & 5 deletions examples/animation_decode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,11 @@ fn main() {
let webp = std::fs::read(input).unwrap();
match AnimDecoder::new(&webp).decode() {
Ok(frames) => {
let mut file_number = 0;
println!("has_animation {}", frames.has_animation());
println!("loop_count {}", frames.loop_count);
println!("bg_color {}", frames.bg_color);
let mut last_ms = 0;
for f in frames.into_iter() {
for (file_number, f) in frames.into_iter().enumerate() {
let delay_ms = f.get_time_ms() - last_ms;
println!(
"{}x{} {:?} time{}ms delay{}ms",
Expand All @@ -54,14 +53,13 @@ fn main() {
last_ms += delay_ms;
let webp = Encoder::from(&f).encode_simple(true, 100f32);
let output = std::path::Path::new("assets")
.join(format!("{}{}", src, file_number))
.join(format!("{src}{file_number}"))
.with_extension("webp");
file_number += 1;
std::fs::write(&output, &*webp.unwrap()).unwrap();
}
}
Err(mes) => {
println!("{}", mes);
println!("{mes}");
}
}
}
137 changes: 73 additions & 64 deletions src/animation_decoder.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#![allow(clippy::uninit_vec)]
use libwebp_sys::*;

use crate::shared::PixelLayout;
Expand All @@ -14,69 +15,72 @@ impl<'a> AnimDecoder<'a> {
unsafe { self.decode_internal(true) }
}
unsafe fn decode_internal(&self, mut has_alpha: bool) -> Result<DecodeAnimImage, String> {
let mut dec_options: WebPAnimDecoderOptions = std::mem::zeroed();
dec_options.color_mode = if has_alpha {
WEBP_CSP_MODE::MODE_RGBA
} else {
WEBP_CSP_MODE::MODE_RGB
};
let ok = WebPAnimDecoderOptionsInitInternal(&mut dec_options, WebPGetDemuxABIVersion());
if ok == 0 {
return Err(String::from("option init error"));
}
match dec_options.color_mode {
WEBP_CSP_MODE::MODE_RGBA | WEBP_CSP_MODE::MODE_RGB => {}
_ => return Err(String::from("unsupport color mode")),
}
has_alpha = dec_options.color_mode == WEBP_CSP_MODE::MODE_RGBA;
let webp_data = WebPData {
bytes: self.data.as_ptr(),
size: self.data.len(),
};
let dec = WebPAnimDecoderNewInternal(&webp_data, &dec_options, WebPGetDemuxABIVersion());
if dec.is_null() {
return Err(String::from("null_decoder"));
}
let mut anim_info: WebPAnimInfo = std::mem::zeroed();
let ok = WebPAnimDecoderGetInfo(dec, &mut anim_info);
if ok == 0 {
return Err(String::from("null info"));
}
let width = anim_info.canvas_width;
let height = anim_info.canvas_height;
let mut list: Vec<DecodeAnimFrame> = vec![];
while WebPAnimDecoderHasMoreFrames(dec) > 0 {
let mut buf: *mut u8 = std::ptr::null_mut();
let mut timestamp: std::os::raw::c_int = 0;
let ok = WebPAnimDecoderGetNext(dec, &mut buf, &mut timestamp);
if ok != 0 {
let len = (if has_alpha { 4 } else { 3 } * width * height) as usize;
let mut img = Vec::with_capacity(len);
buf.copy_to(img.spare_capacity_mut().as_mut_ptr().cast(), len);
img.set_len(len);
let layout = if has_alpha {
PixelLayout::Rgba
} else {
PixelLayout::Rgb
};
let frame = DecodeAnimFrame {
img,
width,
height,
layout,
timestamp,
};
list.push(frame);
unsafe {
let mut dec_options: WebPAnimDecoderOptions = std::mem::zeroed();
dec_options.color_mode = if has_alpha {
WEBP_CSP_MODE::MODE_RGBA
} else {
WEBP_CSP_MODE::MODE_RGB
};
let ok = WebPAnimDecoderOptionsInitInternal(&mut dec_options, WebPGetDemuxABIVersion());
if ok == 0 {
return Err(String::from("option init error"));
}
match dec_options.color_mode {
WEBP_CSP_MODE::MODE_RGBA | WEBP_CSP_MODE::MODE_RGB => {}
_ => return Err(String::from("unsupport color mode")),
}
has_alpha = dec_options.color_mode == WEBP_CSP_MODE::MODE_RGBA;
let webp_data = WebPData {
bytes: self.data.as_ptr(),
size: self.data.len(),
};
let dec =
WebPAnimDecoderNewInternal(&webp_data, &dec_options, WebPGetDemuxABIVersion());
if dec.is_null() {
return Err(String::from("null_decoder"));
}
let mut anim_info: WebPAnimInfo = std::mem::zeroed();
let ok = WebPAnimDecoderGetInfo(dec, &mut anim_info);
if ok == 0 {
return Err(String::from("null info"));
}
let width = anim_info.canvas_width;
let height = anim_info.canvas_height;
let mut list: Vec<DecodeAnimFrame> = vec![];
while WebPAnimDecoderHasMoreFrames(dec) > 0 {
let mut buf: *mut u8 = std::ptr::null_mut();
let mut timestamp: std::os::raw::c_int = 0;
let ok = WebPAnimDecoderGetNext(dec, &mut buf, &mut timestamp);
if ok != 0 {
let len = (if has_alpha { 4 } else { 3 } * width * height) as usize;
let mut img = Vec::with_capacity(len);
buf.copy_to(img.spare_capacity_mut().as_mut_ptr().cast(), len);
img.set_len(len);
let layout = if has_alpha {
PixelLayout::Rgba
} else {
PixelLayout::Rgb
};
let frame = DecodeAnimFrame {
img,
width,
height,
layout,
timestamp,
};
list.push(frame);
}
}
WebPAnimDecoderReset(dec);
//let demuxer:WebPDemuxer=WebPAnimDecoderGetDemuxer(dec);
// ... (Do something using 'demuxer'; e.g. get EXIF/XMP/ICC data).
WebPAnimDecoderDelete(dec);
let mut anim = DecodeAnimImage::from(list);
anim.loop_count = anim_info.loop_count;
anim.bg_color = anim_info.bgcolor;
Ok(anim)
}
WebPAnimDecoderReset(dec);
//let demuxer:WebPDemuxer=WebPAnimDecoderGetDemuxer(dec);
// ... (Do something using 'demuxer'; e.g. get EXIF/XMP/ICC data).
WebPAnimDecoderDelete(dec);
let mut anim = DecodeAnimImage::from(list);
anim.loop_count = anim_info.loop_count;
anim.bg_color = anim_info.bgcolor;
Ok(anim)
}
}
struct DecodeAnimFrame {
Expand All @@ -102,7 +106,7 @@ impl From<Vec<DecodeAnimFrame>> for DecodeAnimImage {
}
impl DecodeAnimImage {
#[inline]
pub fn get_frame(&self, index: usize) -> Option<AnimFrame> {
pub fn get_frame(&self, index: usize) -> Option<AnimFrame<'_>> {
let f = self.frames.get(index)?;
Some(AnimFrame::new(
&f.img,
Expand All @@ -114,7 +118,7 @@ impl DecodeAnimImage {
))
}
#[inline]
pub fn get_frames(&self, index: core::ops::Range<usize>) -> Option<Vec<AnimFrame>> {
pub fn get_frames(&self, index: core::ops::Range<usize>) -> Option<Vec<AnimFrame<'_>>> {
let dec_frames = self.frames.get(index)?;
let mut frames = Vec::with_capacity(dec_frames.len());
for f in dec_frames {
Expand All @@ -132,6 +136,11 @@ impl DecodeAnimImage {
pub fn len(&self) -> usize {
self.frames.len()
}

pub fn is_empty(&self) -> bool {
self.frames.is_empty()
}

pub fn has_animation(&self) -> bool {
self.len() > 1
}
Expand Down Expand Up @@ -186,7 +195,7 @@ mod tests {
let result = decoder.decode();
assert!(result.is_ok(), "Decoding should succeed for valid data");
let anim = result.unwrap();
assert!(anim.len() > 0, "Animation should have at least one frame");
assert!(!anim.is_empty(), "Animation should have at least one frame");
let _ = anim.loop_count;
let _ = anim.bg_color;
}
Expand Down
100 changes: 53 additions & 47 deletions src/animation_encoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,59 +148,65 @@ unsafe fn anim_encode(all_frame: &AnimEncoder) -> Result<WebPMemory, AnimEncodeE
let mut uninit = std::mem::MaybeUninit::<WebPAnimEncoderOptions>::uninit();

let mux_abi_version = WebPGetMuxABIVersion();
WebPAnimEncoderOptionsInitInternal(uninit.as_mut_ptr(), mux_abi_version);
let encoder = WebPAnimEncoderNewInternal(
width as i32,
height as i32,
uninit.as_ptr(),
mux_abi_version,
);
let mut frame_pictures = vec![];
for frame in all_frame.frames.iter() {
let mut pic = crate::new_picture(frame.image, frame.layout, width, height);
let config = frame.config.unwrap_or(all_frame.config);
let ok = WebPAnimEncoderAdd(
encoder,
&mut *pic as *mut _,
frame.timestamp as std::os::raw::c_int,
config,
unsafe {
WebPAnimEncoderOptionsInitInternal(uninit.as_mut_ptr(), mux_abi_version);
let encoder = WebPAnimEncoderNewInternal(
width as i32,
height as i32,
uninit.as_ptr(),
mux_abi_version,
);

let mut frame_pictures = vec![];
for frame in all_frame.frames.iter() {
let mut pic = crate::new_picture(frame.image, frame.layout, width, height);
let config = frame.config.unwrap_or(all_frame.config);
let ok = WebPAnimEncoderAdd(
encoder,
&mut *pic as *mut _,
frame.timestamp as std::os::raw::c_int,
config,
);
if ok == 0 {
//ok == false
WebPAnimEncoderDelete(encoder);
return Err(AnimEncodeError::WebPEncodingError(pic.error_code));
}
frame_pictures.push(pic);
}
WebPAnimEncoderAdd(encoder, std::ptr::null_mut(), 0, std::ptr::null());

let mut webp_data = std::mem::MaybeUninit::<WebPData>::uninit();
let ok = WebPAnimEncoderAssemble(encoder, webp_data.as_mut_ptr());
if ok == 0 {
//ok == false
let err_ptr = WebPAnimEncoderGetError(encoder);
let string = if !err_ptr.is_null() {
std::ffi::CStr::from_ptr(err_ptr)
.to_string_lossy()
.into_owned()
} else {
String::from("Unknown error")
};
WebPAnimEncoderDelete(encoder);
return Err(AnimEncodeError::WebPEncodingError(pic.error_code));
return Err(AnimEncodeError::WebPAnimEncoderGetError(string));
}
frame_pictures.push(pic);
}
WebPAnimEncoderAdd(encoder, std::ptr::null_mut(), 0, std::ptr::null());

let mut webp_data = std::mem::MaybeUninit::<WebPData>::uninit();
let ok = WebPAnimEncoderAssemble(encoder, webp_data.as_mut_ptr());
if ok == 0 {
let err_ptr = WebPAnimEncoderGetError(encoder);
let string = if !err_ptr.is_null() {
unsafe { std::ffi::CStr::from_ptr(err_ptr) }
.to_string_lossy()
.into_owned()
} else {
String::from("Unknown error")
};
WebPAnimEncoderDelete(encoder);
return Err(AnimEncodeError::WebPAnimEncoderGetError(string));
}
WebPAnimEncoderDelete(encoder);
let mux = WebPMuxCreateInternal(webp_data.as_ptr(), 1, mux_abi_version);
let mux_error = WebPMuxSetAnimationParams(mux, &all_frame.muxparams);
if mux_error != WebPMuxError::WEBP_MUX_OK {
return Err(AnimEncodeError::WebPMuxError(mux_error));

let mux = WebPMuxCreateInternal(webp_data.as_ptr(), 1, mux_abi_version);
let mux_error = WebPMuxSetAnimationParams(mux, &all_frame.muxparams);
if mux_error != WebPMuxError::WEBP_MUX_OK {
return Err(AnimEncodeError::WebPMuxError(mux_error));
}
let mut raw_data: WebPData = webp_data.assume_init();

WebPDataClear(&mut raw_data);
let mut webp_data = std::mem::MaybeUninit::<WebPData>::uninit();
WebPMuxAssemble(mux, webp_data.as_mut_ptr());
WebPMuxDelete(mux);

let raw_data: WebPData = webp_data.assume_init();
Ok(WebPMemory(raw_data.bytes as *mut u8, raw_data.size))
}
let mut raw_data: WebPData = webp_data.assume_init();
WebPDataClear(&mut raw_data);
let mut webp_data = std::mem::MaybeUninit::<WebPData>::uninit();
WebPMuxAssemble(mux, webp_data.as_mut_ptr());
WebPMuxDelete(mux);
let raw_data: WebPData = webp_data.assume_init();
Ok(WebPMemory(raw_data.bytes as *mut u8, raw_data.size))
}

#[cfg(test)]
Expand Down
3 changes: 1 addition & 2 deletions src/decoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -244,8 +244,7 @@ mod tests {
assert!(dbg.contains(&format!("has_animation: {}", features.has_animation())));
assert!(
dbg.contains(format_str),
"Debug output missing expected format string: {}",
format_str
"Debug output missing expected format string: {format_str}"
);
}
}
Expand Down
Loading