Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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
8 changes: 4 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
[package]
name = "webp"
version = "0.3.0"
version = "0.3.1"
authors = ["Jared Forth <[email protected]>"]
edition = "2018"
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}");
}
}
}
139 changes: 74 additions & 65 deletions src/animation_decoder.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
#![allow(clippy::uninit_vec)]
use libwebp_sys::*;

use crate::shared::PixelLayout;
use crate::AnimFrame;
use crate::shared::PixelLayout;

pub struct AnimDecoder<'a> {
data: &'a [u8],
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);
img.set_len(len);
buf.copy_to(img.as_mut_ptr(), 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);
img.set_len(len);
buf.copy_to(img.as_mut_ptr(), 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
Loading