1use std::{mem, pin::Pin, ptr};
2
3use libwebp_sys as webp;
4
5use crate::{ColorMode, ConfigContainer, EncoderOptions, EncodingConfig, Error, WebPData};
6
7#[allow(unused_imports)]
8use crate::LossyEncodingConfig; pub struct Encoder {
86 encoder_wr: EncoderWrapper,
87 frame: PictureWrapper,
88 options: EncoderOptions,
89 previous_timestamp: i32,
90 encoding_config: Option<ConfigContainer>,
91}
92
93impl Encoder {
94 pub fn new(dimensions: (u32, u32)) -> Result<Self, Error> {
96 Encoder::new_with_options(dimensions, Default::default())
97 }
98
99 pub fn new_with_options(
101 dimensions: (u32, u32),
102 options: EncoderOptions,
103 ) -> Result<Self, Error> {
104 if dimensions.0 == 0 || dimensions.1 == 0 {
105 return Err(Error::DimensionsMustbePositive);
106 }
107
108 let enc_options = convert_options(&options)?;
109 let encoder_wr = EncoderWrapper::new(dimensions, enc_options)?;
110
111 log::trace!("Encoder initialized with dimensions {:?}", dimensions);
112
113 let mut encoder = Self {
114 encoder_wr,
115 options: options.clone(),
116 frame: PictureWrapper::new(dimensions)?,
117 previous_timestamp: -1,
118 encoding_config: None,
119 };
120
121 if let Some(config) = options.encoding_config {
122 encoder.set_default_encoding_config(config)?;
123 }
124
125 Ok(encoder)
126 }
127
128 pub fn add_frame(&mut self, data: &[u8], timestamp_ms: i32) -> Result<(), Error> {
137 self.add_frame_internal(data, timestamp_ms, None)
138 }
139
140 pub fn add_frame_with_config(
144 &mut self,
145 data: &[u8],
146 timestamp_ms: i32,
147 config: &EncodingConfig,
148 ) -> Result<(), Error> {
149 self.add_frame_internal(data, timestamp_ms, Some(config))
150 }
151
152 fn add_frame_internal(
153 &mut self,
154 data: &[u8],
155 timestamp: i32,
156 config: Option<&EncodingConfig>,
157 ) -> Result<(), Error> {
158 if timestamp <= self.previous_timestamp {
159 return Err(Error::TimestampMustBeHigherThanPrevious(
160 timestamp,
161 self.previous_timestamp,
162 ));
163 }
164
165 self.frame.set_data(data, self.options.color_mode)?;
166
167 if unsafe {
168 webp::WebPAnimEncoderAdd(
169 self.encoder_wr.encoder,
170 self.frame.as_webp_picture_ref(),
171 timestamp,
172 match config {
173 Some(config) => {
174 let config = config.to_config_container()?;
175 config.as_ptr()
176 }
177 None => match &self.encoding_config {
178 Some(config) => config.as_ptr(),
179 None => std::ptr::null(),
180 },
181 },
182 )
183 } == 0
184 {
185 return Err(Error::EncoderAddFailed);
186 }
187
188 self.previous_timestamp = timestamp;
189
190 log::trace!(
191 "Add a frame at timestamp {}ms, {} bytes",
192 timestamp,
193 data.len()
194 );
195
196 Ok(())
197 }
198
199 pub fn set_default_encoding_config(&mut self, config: EncodingConfig) -> Result<(), Error> {
203 self.encoding_config = Some(config.to_config_container()?);
204 self.options.encoding_config = Some(config);
205 Ok(())
206 }
207
208 pub fn finalize(self, timestamp_ms: i32) -> Result<WebPData, Error> {
212 if self.previous_timestamp == -1 {
213 return Err(Error::NoFramesAdded);
215 }
216
217 if timestamp_ms < self.previous_timestamp {
218 return Err(Error::TimestampMustBeEqualOrHigherThanPrevious(
219 timestamp_ms,
220 self.previous_timestamp,
221 ));
222 }
223
224 if unsafe {
225 webp::WebPAnimEncoderAdd(
226 self.encoder_wr.encoder,
227 ptr::null_mut(),
228 timestamp_ms,
229 ptr::null_mut(),
230 )
231 } == 0
232 {
233 return Err(Error::EncoderAddFailed);
234 }
235
236 let mut data = WebPData::new();
237
238 if unsafe { webp::WebPAnimEncoderAssemble(self.encoder_wr.encoder, data.inner_ref()) } == 0
239 {
240 return Err(Error::EncoderAssmebleFailed);
241 }
242
243 log::trace!(
244 "Finalize encoding at timestamp {}ms, output binary size {} bytes",
245 timestamp_ms,
246 data.len()
247 );
248
249 Ok(data)
250 }
251}
252
253fn convert_options(
254 options: &EncoderOptions,
255) -> Result<Pin<Box<webp::WebPAnimEncoderOptions>>, Error> {
256 let mut enc_options = Box::pin(unsafe {
257 let mut enc_options = mem::zeroed();
258 if webp::WebPAnimEncoderOptionsInit(&mut enc_options) != 1 {
259 return Err(Error::OptionsInitFailed);
260 }
261 enc_options
262 });
263
264 enc_options.anim_params.loop_count = options.anim_params.loop_count;
265
266 enc_options.minimize_size = if options.minimize_size { 1 } else { 0 };
267 enc_options.kmin = options.kmin as i32;
268 enc_options.kmax = options.kmax as i32;
269 enc_options.allow_mixed = if options.allow_mixed { 1 } else { 0 };
270 enc_options.verbose = if options.verbose { 1 } else { 0 };
271
272 Ok(enc_options)
273}
274
275struct EncoderWrapper {
276 encoder: *mut webp::WebPAnimEncoder,
277
278 #[allow(dead_code)]
279 options: Pin<Box<webp::WebPAnimEncoderOptions>>,
280}
281
282impl EncoderWrapper {
283 pub fn new(
284 dimensions: (u32, u32),
285 options: Pin<Box<webp::WebPAnimEncoderOptions>>,
286 ) -> Result<Self, Error> {
287 let (width, height) = dimensions;
288
289 let encoder = unsafe { webp::WebPAnimEncoderNew(width as i32, height as i32, &*options) };
290 if encoder.is_null() {
291 return Err(Error::EncoderCreateFailed);
292 }
293
294 Ok(Self { encoder, options })
295 }
296}
297
298impl Drop for EncoderWrapper {
299 fn drop(&mut self) {
300 unsafe { webp::WebPAnimEncoderDelete(self.encoder) };
301 }
302}
303
304struct PictureWrapper {
305 picture: webp::WebPPicture,
306}
307
308impl PictureWrapper {
309 pub fn new(dimensions: (u32, u32)) -> Result<Self, Error> {
310 let mut picture = unsafe {
311 let mut picture = mem::zeroed();
312 assert!(webp::WebPPictureInit(&mut picture) != 0);
313 picture
314 };
315
316 picture.width = dimensions.0 as i32;
317 picture.height = dimensions.1 as i32;
318 picture.use_argb = 1;
319
320 Ok(Self { picture })
321 }
322
323 pub fn as_webp_picture_ref(&mut self) -> &mut webp::WebPPicture {
324 &mut self.picture
325 }
326
327 pub fn set_data(&mut self, data: &[u8], color_mode: ColorMode) -> Result<(), Error> {
328 let received_len = data.len();
329 let expected_len = self.data_size(color_mode);
330 if received_len != expected_len {
331 return Err(Error::BufferSizeFailed(expected_len, received_len));
332 }
333
334 if unsafe {
335 let size = color_mode.size() as i32;
336
337 match color_mode {
338 ColorMode::Rgba => webp::WebPPictureImportRGBA(
339 &mut self.picture,
340 data.as_ptr(),
341 self.picture.width * size,
342 ),
343 ColorMode::Bgra => webp::WebPPictureImportBGRA(
344 &mut self.picture,
345 data.as_ptr(),
346 self.picture.width * size,
347 ),
348 ColorMode::Rgb => webp::WebPPictureImportRGB(
349 &mut self.picture,
350 data.as_ptr(),
351 self.picture.width * size,
352 ),
353 ColorMode::Bgr => webp::WebPPictureImportBGR(
354 &mut self.picture,
355 data.as_ptr(),
356 self.picture.width * size,
357 ),
358 }
359 } == 0
360 {
361 return Err(Error::PictureImportFailed);
362 }
363
364 Ok(())
365 }
366
367 fn data_size(&self, color_mode: ColorMode) -> usize {
368 self.picture.width as usize * self.picture.height as usize * color_mode.size()
369 }
370}
371
372impl Drop for PictureWrapper {
373 fn drop(&mut self) {
374 unsafe { webp::WebPPictureFree(&mut self.picture) };
375 }
376}
377
378#[cfg(test)]
379mod tests {
380 use super::*;
381 use crate::{Decoder, EncodingType, Frame, LossyEncodingConfig};
382 use std::fs::File;
383 use std::io::prelude::*;
384
385 #[test]
386 fn test_encoder() {
387 let mut frames = read_frames();
389
390 let mut encoder = Encoder::new((400, 400)).unwrap();
392 for frame in &mut frames {
393 encoder.add_frame(frame.data(), frame.timestamp()).unwrap();
394 }
395 let webp_data = encoder.finalize(440).unwrap();
396 assert!(webp_data.len() > 0);
397 assert_eq!(&webp_data[..5], &[82, 73, 70, 70, 18]);
398
399 let decoder = Decoder::new(&webp_data).unwrap();
401 let decoded_frames: Vec<_> = decoder.into_iter().collect();
402
403 assert_eq!(frames.len(), decoded_frames.len());
405 for (f1, f2) in decoded_frames.iter().zip(frames) {
406 assert_eq!(f1.dimensions(), f2.dimensions());
407 assert_eq!(f1.color_mode(), f2.color_mode());
408 assert_eq!(f1.timestamp(), f2.timestamp());
409 assert_eq!(f1.data(), f2.data());
410 }
411 }
412
413 fn read_frames() -> Vec<Frame> {
414 let mut buf = Vec::new();
415 File::open("./data/animated.webp")
416 .unwrap()
417 .read_to_end(&mut buf)
418 .unwrap();
419
420 let decoder = Decoder::new(&buf).unwrap();
421 let frames: Vec<_> = decoder.into_iter().collect();
422 frames
423 }
424
425 #[test]
426 fn test_enc_options() {
427 let mut encoder = Encoder::new((400, 400)).unwrap();
428 encoder.add_frame(&[0u8; 400 * 400 * 4], 0).unwrap();
429 encoder
430 .add_frame_with_config(&[0u8; 400 * 400 * 4], 100, &EncodingConfig::default())
431 .unwrap();
432
433 let buf = encoder.finalize(200).unwrap();
434
435 let decoder = Decoder::new(&buf).unwrap();
436 let frames: Vec<_> = decoder.into_iter().collect();
437 assert_eq!(frames[0].dimensions(), (400, 400));
438 assert_eq!(frames[0].data(), &[0u8; 400 * 400 * 4]);
439 }
440
441 #[test]
442 fn test_failures() {
443 let mut encoder = Encoder::new((400, 400)).unwrap();
444 assert_eq!(
445 encoder.add_frame(&[0u8; 450 * 450 * 4], 0).unwrap_err(),
446 Error::BufferSizeFailed(640_000, 810_000)
447 );
448
449 assert_eq!(
450 encoder.add_frame(&[0u8; 50 * 50 * 4], 0).unwrap_err(),
451 Error::BufferSizeFailed(640_000, 10_000)
452 );
453
454 encoder.add_frame(&[0u8; 400 * 400 * 4], 0).unwrap();
455
456 assert_eq!(
457 encoder.add_frame(&[0u8; 400 * 400 * 4], -1).unwrap_err(),
458 Error::TimestampMustBeHigherThanPrevious(-1, 0)
459 );
460
461 assert_eq!(
462 encoder.add_frame(&[0u8; 400 * 400 * 4], 0).unwrap_err(),
463 Error::TimestampMustBeHigherThanPrevious(0, 0)
464 );
465
466 encoder.add_frame(&[0u8; 400 * 400 * 4], 10).unwrap();
467
468 assert_eq!(
469 encoder.finalize(0).unwrap_err(),
470 Error::TimestampMustBeEqualOrHigherThanPrevious(0, 10)
471 );
472 }
473
474 #[test]
475 fn test_wrong_encoding_config() {
476 let mut encoder = Encoder::new((4, 4)).unwrap();
477 assert!(encoder
478 .add_frame_with_config(
479 &[0u8; 4 * 4 * 4],
480 0,
481 &EncodingConfig {
482 quality: 100.,
483 ..Default::default()
484 },
485 )
486 .is_ok());
487
488 assert_eq!(
489 encoder
490 .add_frame_with_config(
491 &[0u8; 4 * 4 * 4],
492 5,
493 &EncodingConfig {
494 quality: 101.,
495 ..Default::default()
496 },
497 )
498 .unwrap_err(),
499 Error::InvalidEncodingConfig
500 );
501 }
502
503 #[test]
504 fn test_wrong_lossy_config() {
505 assert_eq!(
506 add_lossy_frame(LossyEncodingConfig {
507 segments: 9999,
508 ..Default::default()
509 })
510 .unwrap_err(),
511 Error::InvalidEncodingConfig
512 );
513
514 assert_eq!(
515 add_lossy_frame(LossyEncodingConfig {
516 pass: 11,
517 ..Default::default()
518 })
519 .unwrap_err(),
520 Error::InvalidEncodingConfig
521 );
522
523 assert_eq!(
524 add_lossy_frame(LossyEncodingConfig {
525 filter_sharpness: 8,
526 ..Default::default()
527 })
528 .unwrap_err(),
529 Error::InvalidEncodingConfig
530 );
531
532 assert!(add_lossy_frame(LossyEncodingConfig {
533 filter_sharpness: 7,
534 ..Default::default()
535 })
536 .is_ok());
537 }
538
539 fn add_lossy_frame(lossy_config: LossyEncodingConfig) -> Result<(), Error> {
540 let mut encoder = Encoder::new((4, 4)).unwrap();
541 encoder.add_frame_with_config(
542 &[0u8; 4 * 4 * 4],
543 0,
544 &EncodingConfig {
545 encoding_type: EncodingType::Lossy(lossy_config),
546 ..Default::default()
547 },
548 )
549 }
550}