1use super::bitreader::BitWriter;
8use super::modular::{ModularEncoder, ModularTransform};
9use super::types::{JxlAnimation, JxlColorSpace, JxlConfig, JxlHeader, JXL_CODESTREAM_SIGNATURE};
10use crate::container::isobmff::make_box;
11use crate::error::{CodecError, CodecResult};
12
13pub struct JxlEncoder {
19 config: JxlConfig,
20}
21
22impl JxlEncoder {
23 pub fn new(config: JxlConfig) -> Self {
25 Self { config }
26 }
27
28 pub fn lossless() -> Self {
30 Self {
31 config: JxlConfig::new_lossless(),
32 }
33 }
34
35 pub fn lossless_with_effort(effort: u8) -> Self {
37 Self {
38 config: JxlConfig::new_lossless().with_effort(effort),
39 }
40 }
41
42 pub fn encode(
56 &self,
57 data: &[u8],
58 width: u32,
59 height: u32,
60 channels: u8,
61 bit_depth: u8,
62 ) -> CodecResult<Vec<u8>> {
63 let mut header = JxlHeader::srgb(width, height, channels)?;
65 header.bits_per_sample = bit_depth;
66 let expected_size = width as usize
67 * height as usize
68 * channels as usize
69 * (if bit_depth > 8 { 2 } else { 1 });
70
71 if data.len() < expected_size {
72 return Err(CodecError::BufferTooSmall {
73 needed: expected_size,
74 have: data.len(),
75 });
76 }
77
78 self.config.validate()?;
79
80 let channels_data = self.deinterleave(data, width, height, channels, bit_depth)?;
82
83 let modular_data = self.encode_modular(&channels_data, width, height, &header)?;
85
86 let mut writer = BitWriter::with_capacity(modular_data.len() + 32);
88
89 self.write_signature(&mut writer);
91
92 self.write_size_header(&mut writer, width, height);
94
95 self.write_image_metadata(&mut writer, &header);
97
98 writer.align_to_byte();
100
101 for &byte in &modular_data {
103 writer.write_bits(byte as u32, 8);
104 }
105
106 Ok(writer.finish())
107 }
108
109 pub(crate) fn write_signature(&self, writer: &mut BitWriter) {
111 writer.write_bits(JXL_CODESTREAM_SIGNATURE[0] as u32, 8);
112 writer.write_bits(JXL_CODESTREAM_SIGNATURE[1] as u32, 8);
113 }
114
115 pub(crate) fn write_size_header(&self, writer: &mut BitWriter, width: u32, height: u32) {
120 let can_use_small = width > 0
121 && height > 0
122 && width % 8 == 0
123 && height % 8 == 0
124 && width / 8 <= 32
125 && height / 8 <= 32;
126
127 if can_use_small {
128 writer.write_bool(true); let height_div8 = height / 8;
130 let width_div8 = width / 8;
131 writer.write_bits(height_div8 - 1, 5);
132 if width_div8 == height_div8 {
133 writer.write_bits(0, 5); } else {
135 writer.write_bits(width_div8, 5);
136 }
137 } else {
138 writer.write_bool(false); self.write_size_u32(writer, height);
140 self.write_size_u32(writer, width);
141 }
142 }
143
144 pub(crate) fn write_size_u32(&self, writer: &mut BitWriter, value: u32) {
146 if value == 1 {
147 writer.write_bits(0, 2); } else if value <= 512 {
149 writer.write_bits(1, 2); writer.write_bits(value - 1, 9);
151 } else if value <= 8192 {
152 writer.write_bits(2, 2); writer.write_bits(value - 1, 13);
154 } else {
155 writer.write_bits(3, 2); writer.write_bits(value - 1, 18);
157 }
158 }
159
160 pub(crate) fn write_image_metadata(&self, writer: &mut BitWriter, header: &JxlHeader) {
162 let is_default = header.bits_per_sample == 8
164 && !header.is_float
165 && header.color_space == JxlColorSpace::Srgb
166 && !header.has_alpha
167 && header.orientation == 1
168 && header.num_channels == 3
169 && header.animation.is_none();
170
171 if is_default {
172 writer.write_bool(true); return;
174 }
175
176 writer.write_bool(false); let has_extra = header.orientation != 1;
180 writer.write_bool(has_extra);
181 if has_extra {
182 writer.write_bits((header.orientation - 1) as u32, 3);
183 }
184
185 writer.write_bool(header.is_float); if !header.is_float {
188 match header.bits_per_sample {
189 8 => writer.write_bits(0, 2),
190 10 => writer.write_bits(1, 2),
191 12 => writer.write_bits(2, 2),
192 other => {
193 writer.write_bits(3, 2); writer.write_bits((other - 1) as u32, 6);
195 }
196 }
197 }
198
199 let cs_selector = match header.color_space {
201 JxlColorSpace::Srgb => 0u32,
202 JxlColorSpace::LinearSrgb => 1,
203 JxlColorSpace::Gray => 2,
204 JxlColorSpace::Xyb => 3,
205 };
206 writer.write_bits(cs_selector, 2);
207
208 writer.write_bool(header.has_alpha);
210
211 writer.write_bool(header.animation.is_some());
213 if let Some(ref anim) = header.animation {
214 Self::write_animation_header(writer, anim);
215 }
216 }
217
218 pub(crate) fn write_animation_header(writer: &mut BitWriter, anim: &JxlAnimation) {
220 writer.write_bits(anim.tps_numerator, 32);
221 writer.write_bits(anim.tps_denominator, 32);
222 writer.write_bits(anim.num_loops, 32);
223 writer.write_bool(anim.have_timecodes);
224 }
225
226 pub(crate) fn write_frame_header(writer: &mut BitWriter, duration_ticks: u32, is_last: bool) {
228 writer.write_bits(duration_ticks, 32);
229 writer.write_bool(is_last);
230 }
231
232 pub(crate) fn encode_modular(
234 &self,
235 channels: &[Vec<i32>],
236 width: u32,
237 height: u32,
238 header: &JxlHeader,
239 ) -> CodecResult<Vec<u8>> {
240 let mut encoder = ModularEncoder::new().with_effort(self.config.effort);
241
242 if header.color_channels() >= 3 {
244 encoder.add_transform(ModularTransform::Rct {
245 begin_channel: 0,
246 rct_type: 0,
247 });
248 }
249
250 encoder.encode_image(channels, width, height, header.bits_per_sample)
251 }
252
253 pub(crate) fn deinterleave(
255 &self,
256 data: &[u8],
257 width: u32,
258 height: u32,
259 channels: u8,
260 bit_depth: u8,
261 ) -> CodecResult<Vec<Vec<i32>>> {
262 let pixel_count = width as usize * height as usize;
263 let num_channels = channels as usize;
264 let bytes_per_sample = if bit_depth > 8 { 2usize } else { 1usize };
265
266 let mut channel_data: Vec<Vec<i32>> = (0..num_channels)
267 .map(|_| Vec::with_capacity(pixel_count))
268 .collect();
269
270 for i in 0..pixel_count {
271 for ch in 0..num_channels {
272 let offset = (i * num_channels + ch) * bytes_per_sample;
273
274 let value = if bytes_per_sample == 1 {
275 data[offset] as i32
276 } else {
277 let lo = data[offset] as i32;
279 let hi = data.get(offset + 1).copied().unwrap_or(0) as i32;
280 lo | (hi << 8)
281 };
282
283 channel_data[ch].push(value);
284 }
285 }
286
287 Ok(channel_data)
288 }
289}
290
291impl Default for JxlEncoder {
292 fn default() -> Self {
293 Self::lossless()
294 }
295}
296
297struct PendingFrame {
299 modular_data: Vec<u8>,
301 duration_ticks: u32,
303}
304
305pub struct AnimatedJxlEncoder {
333 animation: JxlAnimation,
335 width: u32,
337 height: u32,
339 channels: u8,
341 bit_depth: u8,
343 effort: u8,
345 frames: Vec<PendingFrame>,
347}
348
349impl AnimatedJxlEncoder {
350 pub fn new(
364 animation: JxlAnimation,
365 width: u32,
366 height: u32,
367 channels: u8,
368 bit_depth: u8,
369 ) -> CodecResult<Self> {
370 let mut header = JxlHeader::srgb(width, height, channels)?;
372 header.bits_per_sample = bit_depth;
373 header.validate()?;
374
375 Ok(Self {
376 animation,
377 width,
378 height,
379 channels,
380 bit_depth,
381 effort: 7,
382 frames: Vec::new(),
383 })
384 }
385
386 pub fn with_effort(mut self, effort: u8) -> Self {
388 self.effort = effort.clamp(1, 9);
389 self
390 }
391
392 pub fn add_frame(&mut self, data: &[u8], duration_ticks: u32) -> CodecResult<()> {
403 let bytes_per_sample: usize = if self.bit_depth > 8 { 2 } else { 1 };
404 let expected_size =
405 self.width as usize * self.height as usize * self.channels as usize * bytes_per_sample;
406
407 if data.len() < expected_size {
408 return Err(CodecError::BufferTooSmall {
409 needed: expected_size,
410 have: data.len(),
411 });
412 }
413
414 let single_encoder = JxlEncoder::lossless_with_effort(self.effort);
416 let channels_data = single_encoder.deinterleave(
417 data,
418 self.width,
419 self.height,
420 self.channels,
421 self.bit_depth,
422 )?;
423
424 let mut header = JxlHeader::srgb(self.width, self.height, self.channels)?;
425 header.bits_per_sample = self.bit_depth;
426
427 let modular_data =
428 single_encoder.encode_modular(&channels_data, self.width, self.height, &header)?;
429
430 self.frames.push(PendingFrame {
431 modular_data,
432 duration_ticks,
433 });
434
435 Ok(())
436 }
437
438 pub fn frame_count(&self) -> usize {
440 self.frames.len()
441 }
442
443 pub fn finish_isobmff(self) -> CodecResult<Vec<u8>> {
453 let codestream = self.finish()?;
454
455 let mut ftyp_payload = Vec::with_capacity(16);
457 ftyp_payload.extend_from_slice(b"jxl ");
458 ftyp_payload.extend_from_slice(&0u32.to_be_bytes());
459 ftyp_payload.extend_from_slice(b"jxl ");
460 ftyp_payload.extend_from_slice(b"isom");
461 let ftyp = make_box(*b"ftyp", &ftyp_payload);
462
463 let jxll = make_box(*b"jxll", &[5u8]);
465
466 let index_val: u32 = 0u32 | 0x8000_0000;
468 let mut jxlp_payload = Vec::with_capacity(4 + codestream.len());
469 jxlp_payload.extend_from_slice(&index_val.to_be_bytes());
470 jxlp_payload.extend_from_slice(&codestream);
471 let jxlp = make_box(*b"jxlp", &jxlp_payload);
472
473 let mut out = Vec::with_capacity(ftyp.len() + jxll.len() + jxlp.len());
474 out.extend_from_slice(&ftyp);
475 out.extend_from_slice(&jxll);
476 out.extend_from_slice(&jxlp);
477 Ok(out)
478 }
479
480 pub fn finish(self) -> CodecResult<Vec<u8>> {
489 if self.frames.is_empty() {
490 return Err(CodecError::InvalidParameter(
491 "Animated JPEG-XL requires at least one frame".into(),
492 ));
493 }
494
495 let mut header = JxlHeader::srgb(self.width, self.height, self.channels)?;
496 header.bits_per_sample = self.bit_depth;
497 header.animation = Some(self.animation);
498
499 let total_modular: usize = self.frames.iter().map(|f| f.modular_data.len()).sum();
501 let estimated_capacity = 64 + total_modular + self.frames.len() * 8;
502 let mut writer = BitWriter::with_capacity(estimated_capacity);
503
504 let helper = JxlEncoder::lossless();
506
507 helper.write_signature(&mut writer);
509
510 helper.write_size_header(&mut writer, self.width, self.height);
512
513 helper.write_image_metadata(&mut writer, &header);
515
516 let frame_count = self.frames.len();
518 for (i, frame) in self.frames.into_iter().enumerate() {
519 let is_last = i == frame_count - 1;
520
521 JxlEncoder::write_frame_header(&mut writer, frame.duration_ticks, is_last);
523
524 writer.align_to_byte();
526
527 let data_len = frame.modular_data.len() as u32;
529 writer.write_bits(data_len, 32);
530
531 for &byte in &frame.modular_data {
533 writer.write_bits(byte as u32, 8);
534 }
535 }
536
537 Ok(writer.finish())
538 }
539}
540
541#[cfg(test)]
542mod tests {
543 use super::super::decoder::JxlDecoder;
544 use super::*;
545
546 #[test]
547 #[ignore]
548 fn test_lossless_roundtrip_rgb8() {
549 let width = 8u32;
550 let height = 8u32;
551 let channels = 3u8;
552 let pixel_count = (width * height) as usize;
553
554 let mut data = Vec::with_capacity(pixel_count * channels as usize);
556 for i in 0..pixel_count {
557 data.push(((i * 3) % 256) as u8); data.push(((i * 5 + 50) % 256) as u8); data.push(((i * 7 + 100) % 256) as u8); }
561
562 let encoder = JxlEncoder::lossless();
563 let encoded = encoder
564 .encode(&data, width, height, channels, 8)
565 .expect("encode ok");
566
567 assert!(JxlDecoder::is_codestream(&encoded));
569
570 let decoder = JxlDecoder::new();
571 let decoded = decoder.decode(&encoded).expect("decode ok");
572
573 assert_eq!(decoded.width, width);
574 assert_eq!(decoded.height, height);
575 assert_eq!(decoded.channels, channels);
576 assert_eq!(decoded.bit_depth, 8);
577 assert_eq!(decoded.data, data, "Lossless roundtrip failed for RGB8");
578 }
579
580 #[test]
581 #[ignore]
582 fn test_lossless_roundtrip_grayscale() {
583 let width = 16u32;
584 let height = 16u32;
585 let channels = 1u8;
586 let pixel_count = (width * height) as usize;
587
588 let mut data = Vec::with_capacity(pixel_count);
589 for i in 0..pixel_count {
590 data.push((i % 256) as u8);
591 }
592
593 let encoder = JxlEncoder::lossless();
594 let encoded = encoder
595 .encode(&data, width, height, channels, 8)
596 .expect("encode ok");
597
598 let decoder = JxlDecoder::new();
599 let decoded = decoder.decode(&encoded).expect("decode ok");
600
601 assert_eq!(decoded.width, width);
602 assert_eq!(decoded.height, height);
603 assert_eq!(decoded.channels, 1);
604 assert_eq!(
605 decoded.data, data,
606 "Lossless roundtrip failed for grayscale"
607 );
608 }
609
610 #[test]
611 #[ignore]
612 fn test_lossless_roundtrip_16bit() {
613 let width = 4u32;
614 let height = 4u32;
615 let channels = 3u8;
616 let pixel_count = (width * height) as usize;
617
618 let mut data = Vec::with_capacity(pixel_count * channels as usize * 2);
620 for i in 0..pixel_count {
621 for ch in 0..channels as usize {
622 let val = ((i * 1000 + ch * 3000) % 65536) as u16;
623 data.push(val as u8); data.push((val >> 8) as u8); }
626 }
627
628 let encoder = JxlEncoder::lossless();
629 let encoded = encoder
630 .encode(&data, width, height, channels, 16)
631 .expect("encode ok");
632
633 let decoder = JxlDecoder::new();
634 let decoded = decoder.decode(&encoded).expect("decode ok");
635
636 assert_eq!(decoded.width, width);
637 assert_eq!(decoded.height, height);
638 assert_eq!(decoded.channels, channels);
639 assert_eq!(decoded.bit_depth, 16);
640 assert_eq!(decoded.data, data, "Lossless roundtrip failed for 16-bit");
641 }
642
643 #[test]
644 #[ignore]
645 fn test_lossless_roundtrip_rgba() {
646 let width = 4u32;
647 let height = 4u32;
648 let channels = 4u8;
649 let pixel_count = (width * height) as usize;
650
651 let mut data = Vec::with_capacity(pixel_count * channels as usize);
652 for i in 0..pixel_count {
653 data.push(((i * 13) % 256) as u8); data.push(((i * 17 + 30) % 256) as u8); data.push(((i * 23 + 60) % 256) as u8); data.push(((i * 31 + 90) % 256) as u8); }
658
659 let encoder = JxlEncoder::lossless();
660 let encoded = encoder
661 .encode(&data, width, height, channels, 8)
662 .expect("encode ok");
663
664 let decoder = JxlDecoder::new();
665 let decoded = decoder.decode(&encoded).expect("decode ok");
666
667 assert_eq!(decoded.width, width);
668 assert_eq!(decoded.height, height);
669 assert_eq!(decoded.channels, channels);
670 assert_eq!(decoded.data, data, "Lossless roundtrip failed for RGBA");
671 }
672
673 #[test]
674 #[ignore]
675 fn test_lossless_roundtrip_flat_image() {
676 let width = 32u32;
678 let height = 32u32;
679 let channels = 3u8;
680 let data = vec![128u8; (width * height) as usize * channels as usize];
681
682 let encoder = JxlEncoder::lossless();
683 let encoded = encoder
684 .encode(&data, width, height, channels, 8)
685 .expect("encode ok");
686
687 let decoder = JxlDecoder::new();
688 let decoded = decoder.decode(&encoded).expect("decode ok");
689
690 assert_eq!(
691 decoded.data, data,
692 "Lossless roundtrip failed for flat image"
693 );
694 }
695
696 #[test]
697 #[ignore]
698 fn test_encode_invalid_buffer() {
699 let encoder = JxlEncoder::lossless();
700 let result = encoder.encode(&[0u8; 10], 100, 100, 3, 8);
701 assert!(result.is_err());
702 }
703
704 #[test]
705 #[ignore]
706 fn test_encode_zero_dimensions() {
707 let encoder = JxlEncoder::lossless();
708 assert!(encoder.encode(&[], 0, 100, 3, 8).is_err());
709 assert!(encoder.encode(&[], 100, 0, 3, 8).is_err());
710 }
711
712 #[test]
713 #[ignore]
714 fn test_signature_written() {
715 let encoder = JxlEncoder::lossless();
716 let data = vec![0u8; 64 * 64 * 3];
717 let encoded = encoder.encode(&data, 64, 64, 3, 8).expect("ok");
718 assert_eq!(encoded[0], 0xFF);
719 assert_eq!(encoded[1], 0x0A);
720 }
721
722 #[test]
723 #[ignore]
724 fn test_size_header_small() {
725 let encoder = JxlEncoder::new(JxlConfig::new_lossless());
727 let mut writer = BitWriter::new();
728 encoder.write_size_header(&mut writer, 64, 64);
729 let data = writer.finish();
730
731 assert_eq!(data[0] & 1, 1);
733 }
734
735 #[test]
736 #[ignore]
737 fn test_size_header_large() {
738 let encoder = JxlEncoder::new(JxlConfig::new_lossless());
740 let mut writer = BitWriter::new();
741 encoder.write_size_header(&mut writer, 1920, 1080);
742 let data = writer.finish();
743
744 assert_eq!(data[0] & 1, 0);
746 }
747
748 #[test]
749 #[ignore]
750 fn test_effort_levels() {
751 let e1 = JxlEncoder::lossless_with_effort(1);
752 let e9 = JxlEncoder::lossless_with_effort(9);
753 assert_eq!(e1.config.effort, 1);
754 assert_eq!(e9.config.effort, 9);
755 }
756
757 #[test]
758 #[ignore]
759 fn test_deinterleave_rgb() {
760 let encoder = JxlEncoder::lossless();
761 let data = [10u8, 20, 30, 40, 50, 60];
762 let channels = encoder.deinterleave(&data, 2, 1, 3, 8).expect("ok");
763 assert_eq!(channels.len(), 3);
764 assert_eq!(channels[0], vec![10, 40]); assert_eq!(channels[1], vec![20, 50]); assert_eq!(channels[2], vec![30, 60]); }
768
769 #[test]
770 #[ignore]
771 fn test_deinterleave_16bit() {
772 let encoder = JxlEncoder::lossless();
773 let data = [0x00u8, 0x01, 0x00, 0x02];
775 let channels = encoder.deinterleave(&data, 2, 1, 1, 16).expect("ok");
776 assert_eq!(channels.len(), 1);
777 assert_eq!(channels[0], vec![256, 512]);
778 }
779
780 fn make_test_frame(width: u32, height: u32, channels: u8, seed: u8) -> Vec<u8> {
784 let pixel_count = (width * height) as usize;
785 let mut data = Vec::with_capacity(pixel_count * channels as usize);
786 for i in 0..pixel_count {
787 for ch in 0..channels as usize {
788 let val = ((i.wrapping_mul(3 + ch) + seed as usize * 37 + ch * 50) % 256) as u8;
789 data.push(val);
790 }
791 }
792 data
793 }
794
795 #[test]
796 fn test_animated_encoder_three_frames_rgb() {
797 let anim = JxlAnimation::millisecond();
798 let width = 4u32;
799 let height = 4u32;
800 let channels = 3u8;
801
802 let mut encoder =
803 AnimatedJxlEncoder::new(anim, width, height, channels, 8).expect("create ok");
804
805 let f0 = make_test_frame(width, height, channels, 0);
806 let f1 = make_test_frame(width, height, channels, 1);
807 let f2 = make_test_frame(width, height, channels, 2);
808
809 encoder.add_frame(&f0, 100).expect("frame 0");
810 encoder.add_frame(&f1, 200).expect("frame 1");
811 encoder.add_frame(&f2, 150).expect("frame 2");
812
813 assert_eq!(encoder.frame_count(), 3);
814
815 let codestream = encoder.finish().expect("finish ok");
816
817 assert!(JxlDecoder::is_codestream(&codestream));
819
820 let decoder = JxlDecoder::new();
822 let frames = decoder.decode_animated(&codestream).expect("decode ok");
823
824 assert_eq!(frames.len(), 3);
825
826 assert_eq!(frames[0].width, width);
828 assert_eq!(frames[0].height, height);
829 assert_eq!(frames[0].channels, channels);
830 assert_eq!(frames[0].duration_ticks, 100);
831 assert!(!frames[0].is_last);
832 assert_eq!(frames[0].data, f0, "Frame 0 pixel data mismatch");
833
834 assert_eq!(frames[1].duration_ticks, 200);
836 assert!(!frames[1].is_last);
837 assert_eq!(frames[1].data, f1, "Frame 1 pixel data mismatch");
838
839 assert_eq!(frames[2].duration_ticks, 150);
841 assert!(frames[2].is_last);
842 assert_eq!(frames[2].data, f2, "Frame 2 pixel data mismatch");
843 }
844
845 #[test]
846 fn test_animated_encoder_single_frame_with_animation_header() {
847 let anim = JxlAnimation::millisecond().with_num_loops(1);
848 let width = 2u32;
849 let height = 2u32;
850 let channels = 3u8;
851
852 let mut encoder =
853 AnimatedJxlEncoder::new(anim, width, height, channels, 8).expect("create ok");
854
855 let frame_data = make_test_frame(width, height, channels, 42);
856 encoder.add_frame(&frame_data, 500).expect("frame ok");
857
858 let codestream = encoder.finish().expect("finish ok");
859
860 let decoder = JxlDecoder::new();
861 let frames = decoder.decode_animated(&codestream).expect("decode ok");
862
863 assert_eq!(frames.len(), 1);
864 assert_eq!(frames[0].duration_ticks, 500);
865 assert!(frames[0].is_last);
866 assert_eq!(frames[0].data, frame_data);
867 }
868
869 #[test]
870 fn test_animated_encoder_zero_duration() {
871 let anim = JxlAnimation::millisecond();
872 let width = 2u32;
873 let height = 2u32;
874 let channels = 1u8;
875
876 let mut encoder =
877 AnimatedJxlEncoder::new(anim, width, height, channels, 8).expect("create ok");
878
879 let f0 = vec![128u8; 4];
880 encoder.add_frame(&f0, 0).expect("frame ok");
881
882 let codestream = encoder.finish().expect("finish ok");
883
884 let decoder = JxlDecoder::new();
885 let frames = decoder.decode_animated(&codestream).expect("decode ok");
886
887 assert_eq!(frames.len(), 1);
888 assert_eq!(frames[0].duration_ticks, 0);
889 assert!(frames[0].is_last);
890 }
891
892 #[test]
893 fn test_animated_encoder_infinite_loop() {
894 let anim = JxlAnimation::millisecond().with_num_loops(0);
895 let width = 2u32;
896 let height = 2u32;
897 let channels = 3u8;
898
899 let mut encoder =
900 AnimatedJxlEncoder::new(anim, width, height, channels, 8).expect("create ok");
901 encoder
902 .add_frame(&make_test_frame(width, height, channels, 0), 100)
903 .expect("ok");
904
905 let codestream = encoder.finish().expect("finish ok");
906
907 let decoder = JxlDecoder::new();
908 let anim_header = decoder
909 .read_animation_header(&codestream)
910 .expect("header ok");
911 let anim_header = anim_header.expect("should have animation");
912 assert_eq!(anim_header.num_loops, 0);
913 }
914
915 #[test]
916 fn test_animated_encoder_no_frames_error() {
917 let anim = JxlAnimation::millisecond();
918 let encoder = AnimatedJxlEncoder::new(anim, 4, 4, 3, 8).expect("create ok");
919 assert!(encoder.finish().is_err());
920 }
921
922 #[test]
923 fn test_animated_encoder_invalid_buffer_size() {
924 let anim = JxlAnimation::millisecond();
925 let mut encoder = AnimatedJxlEncoder::new(anim, 4, 4, 3, 8).expect("create ok");
926 assert!(encoder.add_frame(&[0u8; 10], 100).is_err());
928 }
929
930 #[test]
931 fn test_animated_encoder_grayscale() {
932 let anim = JxlAnimation::new(24, 1).expect("valid");
933 let width = 4u32;
934 let height = 4u32;
935 let channels = 1u8;
936
937 let mut encoder =
938 AnimatedJxlEncoder::new(anim, width, height, channels, 8).expect("create ok");
939
940 let f0 = make_test_frame(width, height, channels, 10);
941 let f1 = make_test_frame(width, height, channels, 20);
942
943 encoder.add_frame(&f0, 1).expect("frame 0");
944 encoder.add_frame(&f1, 1).expect("frame 1");
945
946 let codestream = encoder.finish().expect("finish ok");
947
948 let decoder = JxlDecoder::new();
949 let frames = decoder.decode_animated(&codestream).expect("decode ok");
950
951 assert_eq!(frames.len(), 2);
952 assert_eq!(frames[0].channels, 1);
953 assert_eq!(frames[0].data, f0);
954 assert_eq!(frames[1].data, f1);
955 assert!(frames[1].is_last);
956 }
957
958 #[test]
959 fn test_animated_encoder_rgba() {
960 let anim = JxlAnimation::millisecond();
961 let width = 4u32;
962 let height = 4u32;
963 let channels = 4u8;
964
965 let mut encoder =
966 AnimatedJxlEncoder::new(anim, width, height, channels, 8).expect("create ok");
967
968 let f0 = make_test_frame(width, height, channels, 0);
969 let f1 = make_test_frame(width, height, channels, 1);
970
971 encoder.add_frame(&f0, 50).expect("frame 0");
972 encoder.add_frame(&f1, 50).expect("frame 1");
973
974 let codestream = encoder.finish().expect("finish ok");
975
976 let decoder = JxlDecoder::new();
977 let frames = decoder.decode_animated(&codestream).expect("decode ok");
978
979 assert_eq!(frames.len(), 2);
980 assert_eq!(frames[0].channels, 4);
981 assert_eq!(frames[0].data, f0, "RGBA frame 0 mismatch");
982 assert_eq!(frames[1].data, f1, "RGBA frame 1 mismatch");
983 }
984
985 #[test]
986 fn test_animated_encoder_with_effort() {
987 let anim = JxlAnimation::millisecond();
988 let width = 4u32;
989 let height = 4u32;
990 let channels = 3u8;
991
992 let mut encoder = AnimatedJxlEncoder::new(anim, width, height, channels, 8)
993 .expect("create ok")
994 .with_effort(3);
995
996 let f0 = make_test_frame(width, height, channels, 0);
997 encoder.add_frame(&f0, 100).expect("frame ok");
998
999 let codestream = encoder.finish().expect("finish ok");
1000
1001 let decoder = JxlDecoder::new();
1002 let frames = decoder.decode_animated(&codestream).expect("decode ok");
1003 assert_eq!(frames.len(), 1);
1004 assert_eq!(frames[0].data, f0);
1005 }
1006
1007 #[test]
1008 fn test_animated_encoder_animation_header_roundtrip() {
1009 let anim = JxlAnimation::new(30, 1)
1010 .expect("valid")
1011 .with_num_loops(5)
1012 .with_timecodes(true);
1013
1014 let width = 2u32;
1015 let height = 2u32;
1016 let channels = 3u8;
1017
1018 let mut encoder =
1019 AnimatedJxlEncoder::new(anim.clone(), width, height, channels, 8).expect("create ok");
1020 encoder
1021 .add_frame(&make_test_frame(width, height, channels, 0), 1)
1022 .expect("ok");
1023
1024 let codestream = encoder.finish().expect("finish ok");
1025
1026 let decoder = JxlDecoder::new();
1027 let header_anim = decoder
1028 .read_animation_header(&codestream)
1029 .expect("read ok")
1030 .expect("should be animated");
1031
1032 assert_eq!(header_anim.tps_numerator, 30);
1033 assert_eq!(header_anim.tps_denominator, 1);
1034 assert_eq!(header_anim.num_loops, 5);
1035 assert!(header_anim.have_timecodes);
1036 }
1037
1038 #[test]
1039 fn test_animated_encoder_is_animated_check() {
1040 let anim = JxlAnimation::millisecond();
1042 let mut anim_enc = AnimatedJxlEncoder::new(anim, 2, 2, 3, 8).expect("create ok");
1043 anim_enc
1044 .add_frame(&make_test_frame(2, 2, 3, 0), 100)
1045 .expect("ok");
1046 let animated_cs = anim_enc.finish().expect("ok");
1047
1048 let decoder = JxlDecoder::new();
1049 assert!(decoder.is_animated(&animated_cs).expect("check ok"));
1050
1051 let encoder = JxlEncoder::lossless();
1053 let data = vec![0u8; 8 * 8 * 3];
1054 let still_cs = encoder.encode(&data, 8, 8, 3, 8).expect("ok");
1055 assert!(!decoder.is_animated(&still_cs).expect("check ok"));
1056 }
1057
1058 #[test]
1059 fn test_still_image_backwards_compat() {
1060 let width = 4u32;
1062 let height = 4u32;
1063 let channels = 3u8;
1064
1065 let data = make_test_frame(width, height, channels, 99);
1066
1067 let encoder = JxlEncoder::lossless();
1068 let encoded = encoder
1069 .encode(&data, width, height, channels, 8)
1070 .expect("encode ok");
1071
1072 let decoder = JxlDecoder::new();
1074 let decoded = decoder.decode(&encoded).expect("decode ok");
1075 assert_eq!(decoded.data, data);
1076
1077 let frames = decoder
1079 .decode_animated(&encoded)
1080 .expect("animated decode ok");
1081 assert_eq!(frames.len(), 1);
1082 assert_eq!(frames[0].duration_ticks, 0);
1083 assert!(frames[0].is_last);
1084 assert_eq!(frames[0].data, data);
1085 }
1086
1087 #[test]
1088 fn test_animated_encoder_many_frames() {
1089 let anim = JxlAnimation::millisecond();
1090 let width = 2u32;
1091 let height = 2u32;
1092 let channels = 3u8;
1093
1094 let mut encoder =
1095 AnimatedJxlEncoder::new(anim, width, height, channels, 8).expect("create ok");
1096
1097 let frame_count = 10;
1098 let mut test_frames = Vec::with_capacity(frame_count);
1099 for i in 0..frame_count {
1100 let f = make_test_frame(width, height, channels, i as u8);
1101 encoder.add_frame(&f, (i as u32 + 1) * 50).expect("ok");
1102 test_frames.push(f);
1103 }
1104
1105 let codestream = encoder.finish().expect("finish ok");
1106
1107 let decoder = JxlDecoder::new();
1108 let frames = decoder.decode_animated(&codestream).expect("decode ok");
1109
1110 assert_eq!(frames.len(), frame_count);
1111 for (i, frame) in frames.iter().enumerate() {
1112 assert_eq!(frame.duration_ticks, (i as u32 + 1) * 50);
1113 assert_eq!(frame.data, test_frames[i], "Frame {i} data mismatch");
1114 assert_eq!(frame.is_last, i == frame_count - 1);
1115 }
1116 }
1117}