1use crate::error::CodecError;
28
29#[derive(Debug, Clone)]
33pub struct AvifConfig {
34 pub quality: u8,
36 pub speed: u8,
38 pub color_primaries: u8,
41 pub transfer_characteristics: u8,
44 pub matrix_coefficients: u8,
47 pub full_range: bool,
49 pub alpha_quality: Option<u8>,
51}
52
53impl Default for AvifConfig {
54 fn default() -> Self {
55 Self {
56 quality: 60,
57 speed: 6,
58 color_primaries: 1,
59 transfer_characteristics: 1,
60 matrix_coefficients: 1,
61 full_range: false,
62 alpha_quality: None,
63 }
64 }
65}
66
67#[derive(Debug, Clone, Copy, PartialEq, Eq)]
69pub enum YuvFormat {
70 Yuv420,
72 Yuv422,
74 Yuv444,
76}
77
78#[derive(Debug, Clone)]
80pub struct AvifImage {
81 pub width: u32,
83 pub height: u32,
85 pub depth: u8,
87 pub yuv_format: YuvFormat,
89 pub y_plane: Vec<u8>,
91 pub u_plane: Vec<u8>,
93 pub v_plane: Vec<u8>,
95 pub alpha_plane: Option<Vec<u8>>,
97}
98
99#[derive(Debug, Clone)]
101pub struct AvifProbeResult {
102 pub width: u32,
104 pub height: u32,
106 pub bit_depth: u8,
108 pub has_alpha: bool,
110 pub color_primaries: u8,
112 pub transfer_characteristics: u8,
114}
115
116#[derive(Debug, Clone)]
120pub struct AvifEncoder {
121 config: AvifConfig,
122}
123
124impl AvifEncoder {
125 pub fn new(config: AvifConfig) -> Self {
127 Self { config }
128 }
129
130 pub fn encode(&self, image: &AvifImage) -> Result<Vec<u8>, CodecError> {
134 validate_image(image)?;
135
136 let av1_payload = build_av1_obu(image, &self.config);
138
139 let has_alpha = self.config.alpha_quality.is_some() && image.alpha_plane.is_some();
141 let alpha_payload = if has_alpha {
142 Some(build_alpha_av1_obu(image))
143 } else {
144 None
145 };
146
147 let mut out = Vec::with_capacity(4096 + av1_payload.len());
148
149 write_ftyp(&mut out);
151
152 let meta_buf = build_meta(image, &self.config, &av1_payload, &alpha_payload)?;
157 out.extend_from_slice(&meta_buf);
158
159 let mdat_header_size = 8u32; let mdat_size = mdat_header_size as usize
162 + av1_payload.len()
163 + alpha_payload.as_ref().map_or(0, |a| a.len());
164 write_u32(&mut out, mdat_size as u32);
165 out.extend_from_slice(b"mdat");
166 out.extend_from_slice(&av1_payload);
167 if let Some(ref ap) = alpha_payload {
168 out.extend_from_slice(ap);
169 }
170
171 patch_iloc_offsets(&mut out, &meta_buf, &av1_payload, alpha_payload.as_deref())?;
174
175 Ok(out)
176 }
177}
178
179#[derive(Debug, Default, Clone)]
183pub struct AvifDecoder;
184
185impl AvifDecoder {
186 pub fn new() -> Self {
188 Self
189 }
190
191 pub fn decode(data: &[u8]) -> Result<AvifImage, CodecError> {
198 let probe = Self::probe(data)?;
199 let (color_offset, color_len, alpha_offset, alpha_len) =
200 locate_mdat_items(data, probe.has_alpha)?;
201
202 let y_plane = data
206 .get(color_offset..color_offset + color_len)
207 .ok_or_else(|| CodecError::InvalidBitstream("mdat color extent out of range".into()))?
208 .to_vec();
209
210 let alpha_plane = if probe.has_alpha {
211 let (ao, al) = (alpha_offset, alpha_len);
212 let slice = data
213 .get(ao..ao + al)
214 .ok_or_else(|| {
215 CodecError::InvalidBitstream("mdat alpha extent out of range".into())
216 })?
217 .to_vec();
218 Some(slice)
219 } else {
220 None
221 };
222
223 Ok(AvifImage {
224 width: probe.width,
225 height: probe.height,
226 depth: probe.bit_depth,
227 yuv_format: YuvFormat::Yuv420,
228 y_plane,
229 u_plane: Vec::new(),
230 v_plane: Vec::new(),
231 alpha_plane,
232 })
233 }
234
235 pub fn probe(data: &[u8]) -> Result<AvifProbeResult, CodecError> {
237 check_avif_signature(data)?;
238 parse_meta_for_probe(data)
239 }
240}
241
242fn build_av1_obu(image: &AvifImage, config: &AvifConfig) -> Vec<u8> {
251 let mut bits = BitWriter::new();
252
253 let obu_header: u8 = (1 << 3) | (1 << 1); bits.write_byte(obu_header);
258
259 let mut seq = BitWriter::new();
261 write_sequence_header_payload(&mut seq, image, config);
262 let seq_bytes = seq.finish();
263
264 let mut leb = Vec::new();
266 write_leb128(&mut leb, seq_bytes.len() as u64);
267 bits.extend_bytes(&leb);
268 bits.extend_bytes(&seq_bytes);
269
270 bits.write_byte((2 << 3) | (1 << 1)); bits.write_byte(0); bits.finish()
275}
276
277fn build_alpha_av1_obu(image: &AvifImage) -> Vec<u8> {
279 let alpha_config = AvifConfig {
280 quality: 80,
281 color_primaries: 1,
282 transfer_characteristics: 1,
283 matrix_coefficients: 0, full_range: true,
285 ..AvifConfig::default()
286 };
287 let mono = AvifImage {
289 width: image.width,
290 height: image.height,
291 depth: image.depth,
292 yuv_format: YuvFormat::Yuv444,
293 y_plane: image.alpha_plane.clone().unwrap_or_default(),
294 u_plane: Vec::new(),
295 v_plane: Vec::new(),
296 alpha_plane: None,
297 };
298 build_av1_obu(&mono, &alpha_config)
299}
300
301fn write_sequence_header_payload(bits: &mut BitWriter, image: &AvifImage, config: &AvifConfig) {
303 let seq_profile: u8 = match image.yuv_format {
305 YuvFormat::Yuv444 => 1,
306 _ => 0,
307 };
308 bits.write_bits(seq_profile as u32, 3);
309
310 bits.write_bits(1, 1);
312 bits.write_bits(1, 1);
314
315 bits.write_bits(13, 5);
317
318 let high_bitdepth = image.depth >= 10;
321 bits.write_bits(high_bitdepth as u32, 1);
322 if seq_profile == 2 && high_bitdepth {
323 let twelve_bit = image.depth == 12;
325 bits.write_bits(twelve_bit as u32, 1);
326 }
327 bits.write_bits(0, 1);
329 bits.write_bits(1, 1);
331 bits.write_bits(config.color_primaries as u32, 8);
333 bits.write_bits(config.transfer_characteristics as u32, 8);
335 bits.write_bits(config.matrix_coefficients as u32, 8);
337 bits.write_bits(config.full_range as u32, 1);
339
340 let (sub_x, sub_y): (u32, u32) = match image.yuv_format {
342 YuvFormat::Yuv420 => (1, 1),
343 YuvFormat::Yuv422 => (1, 0),
344 YuvFormat::Yuv444 => (0, 0),
345 };
346 if seq_profile != 1 {
347 if config.color_primaries == 1
348 && config.transfer_characteristics == 1
349 && config.matrix_coefficients == 1
350 {
351 bits.write_bits(0, 1);
353 } else {
354 bits.write_bits(sub_x, 1);
355 if sub_x == 1 {
356 bits.write_bits(sub_y, 1);
357 }
358 if sub_x == 1 && sub_y == 1 {
359 bits.write_bits(0, 2); }
362 }
363 }
364 bits.write_bits(0, 1);
366
367 let w_bits = bits_needed(image.width);
371 let h_bits = bits_needed(image.height);
372 bits.write_bits((w_bits - 1) as u32, 4); bits.write_bits((h_bits - 1) as u32, 4); bits.write_bits((image.width - 1) as u32, w_bits as u32);
375 bits.write_bits((image.height - 1) as u32, h_bits as u32);
376
377 bits.write_bits(0, 1);
379}
380
381fn bits_needed(n: u32) -> u8 {
383 if n == 0 {
384 return 1;
385 }
386 let mut bits = 0u8;
387 let mut v = n;
388 while v > 0 {
389 bits += 1;
390 v >>= 1;
391 }
392 bits
393}
394
395fn write_leb128(buf: &mut Vec<u8>, mut value: u64) {
397 loop {
398 let mut byte = (value & 0x7F) as u8;
399 value >>= 7;
400 if value != 0 {
401 byte |= 0x80;
402 }
403 buf.push(byte);
404 if value == 0 {
405 break;
406 }
407 }
408}
409
410fn write_ftyp(out: &mut Vec<u8>) {
416 let compat: &[&[u8; 4]] = &[b"avif", b"mif1", b"miaf"];
418 let size = 4 + 4 + 4 + 4 + 4 * compat.len(); write_u32(out, size as u32);
420 out.extend_from_slice(b"ftyp");
421 out.extend_from_slice(b"avif"); write_u32(out, 0); for brand in compat {
424 out.extend_from_slice(*brand);
425 }
426}
427
428fn build_meta(
433 image: &AvifImage,
434 config: &AvifConfig,
435 av1_payload: &[u8],
436 alpha_payload: &Option<Vec<u8>>,
437) -> Result<Vec<u8>, CodecError> {
438 let has_alpha = alpha_payload.is_some();
439
440 let mut body = Vec::<u8>::new();
441
442 body.extend_from_slice(&build_hdlr());
444
445 body.extend_from_slice(&build_pitm(1));
447
448 body.extend_from_slice(&build_iloc(
450 has_alpha,
451 av1_payload.len(),
452 alpha_payload.as_ref().map_or(0, |a| a.len()),
453 ));
454
455 body.extend_from_slice(&build_iinf(has_alpha));
457
458 body.extend_from_slice(&build_iprp(image, config, has_alpha)?);
460
461 let meta_size = 4 + 4 + 4 + body.len(); let mut meta = Vec::with_capacity(meta_size);
464 write_u32(&mut meta, meta_size as u32);
465 meta.extend_from_slice(b"meta");
466 write_u32(&mut meta, 0u32); meta.extend_from_slice(&body);
468 Ok(meta)
469}
470
471fn build_hdlr() -> Vec<u8> {
474 let size = 4 + 4 + 4 + 4 + 4 + 12 + 1; let mut b = Vec::with_capacity(size);
480 write_u32(&mut b, size as u32);
481 b.extend_from_slice(b"hdlr");
482 write_u32(&mut b, 0); write_u32(&mut b, 0); b.extend_from_slice(b"pict"); b.extend_from_slice(&[0u8; 12]); b.push(0); b
488}
489
490fn build_pitm(item_id: u16) -> Vec<u8> {
493 let size = 4 + 4 + 4 + 2; let mut b = Vec::with_capacity(size);
495 write_u32(&mut b, size as u32);
496 b.extend_from_slice(b"pitm");
497 write_u32(&mut b, 0); write_u16(&mut b, item_id);
499 b
500}
501
502fn build_iloc(has_alpha: bool, color_len: usize, alpha_len: usize) -> Vec<u8> {
509 let item_count: u16 = if has_alpha { 2 } else { 1 };
516 let item_entry_size = 2 + 2 + 2 + 2 + 4 + 4;
520 let payload_size = 1 + 1 + 2 + item_count as usize * item_entry_size;
521 let size = 8 + 4 + payload_size;
523 let mut b = Vec::with_capacity(size);
524 write_u32(&mut b, size as u32);
525 b.extend_from_slice(b"iloc");
526 write_u32(&mut b, 1 << 24); b.push(0x44); b.push(0x00);
532 write_u16(&mut b, item_count);
534
535 write_u16(&mut b, 1); write_u16(&mut b, 0); write_u16(&mut b, 0); write_u16(&mut b, 1); write_u32(&mut b, 0); write_u32(&mut b, color_len as u32); if has_alpha {
544 write_u16(&mut b, 2); write_u16(&mut b, 0); write_u16(&mut b, 0); write_u16(&mut b, 1); write_u32(&mut b, 0); write_u32(&mut b, alpha_len as u32); }
552
553 b
554}
555
556fn build_iinf(has_alpha: bool) -> Vec<u8> {
559 let item_count: u16 = if has_alpha { 2 } else { 1 };
560 let entry1 = build_infe(1, b"av01", b"Color Image\0");
561 let entry2 = if has_alpha {
562 Some(build_infe(2, b"av01", b"Alpha Image\0"))
563 } else {
564 None
565 };
566
567 let entries_size = entry1.len() + entry2.as_ref().map_or(0, |e| e.len());
568 let size = 8 + 4 + 2 + entries_size; let mut b = Vec::with_capacity(size);
570 write_u32(&mut b, size as u32);
571 b.extend_from_slice(b"iinf");
572 write_u32(&mut b, 0); write_u16(&mut b, item_count);
574 b.extend_from_slice(&entry1);
575 if let Some(e2) = entry2 {
576 b.extend_from_slice(&e2);
577 }
578 b
579}
580
581fn build_infe(item_id: u16, item_type: &[u8; 4], item_name: &[u8]) -> Vec<u8> {
583 let payload = 2 + 2 + 4 + item_name.len();
585 let size = 8 + 4 + payload; let mut b = Vec::with_capacity(size);
587 write_u32(&mut b, size as u32);
588 b.extend_from_slice(b"infe");
589 write_u32(&mut b, 2 << 24); write_u16(&mut b, item_id);
591 write_u16(&mut b, 0); b.extend_from_slice(item_type);
593 b.extend_from_slice(item_name);
594 b
595}
596
597fn build_iprp(
600 image: &AvifImage,
601 config: &AvifConfig,
602 has_alpha: bool,
603) -> Result<Vec<u8>, CodecError> {
604 let ispe = build_ispe(image.width, image.height);
605 let colr = build_colr(config);
606 let av1c = build_av1c(image, config);
607 let pixi = build_pixi(image.depth);
608
609 let ipco_payload_len = ispe.len() + colr.len() + av1c.len() + pixi.len();
611 let ipco_size = 8 + ipco_payload_len;
612 let mut ipco = Vec::with_capacity(ipco_size);
613 write_u32(&mut ipco, ipco_size as u32);
614 ipco.extend_from_slice(b"ipco");
615 ipco.extend_from_slice(&ispe);
616 ipco.extend_from_slice(&colr);
617 ipco.extend_from_slice(&av1c);
618 ipco.extend_from_slice(&pixi);
619
620 let ipma = build_ipma(has_alpha);
624
625 let iprp_size = 8 + ipco.len() + ipma.len();
626 let mut b = Vec::with_capacity(iprp_size);
627 write_u32(&mut b, iprp_size as u32);
628 b.extend_from_slice(b"iprp");
629 b.extend_from_slice(&ipco);
630 b.extend_from_slice(&ipma);
631 Ok(b)
632}
633
634fn build_ispe(width: u32, height: u32) -> Vec<u8> {
636 let size = 8 + 4 + 4 + 4; let mut b = Vec::with_capacity(size);
638 write_u32(&mut b, size as u32);
639 b.extend_from_slice(b"ispe");
640 write_u32(&mut b, 0); write_u32(&mut b, width);
642 write_u32(&mut b, height);
643 b
644}
645
646fn build_colr(config: &AvifConfig) -> Vec<u8> {
648 let payload_size = 4 + 2 + 2 + 2 + 1;
651 let size = 8 + payload_size;
652 let mut b = Vec::with_capacity(size);
653 write_u32(&mut b, size as u32);
654 b.extend_from_slice(b"colr");
655 b.extend_from_slice(b"nclx"); write_u16(&mut b, config.color_primaries as u16);
657 write_u16(&mut b, config.transfer_characteristics as u16);
658 write_u16(&mut b, config.matrix_coefficients as u16);
659 let full_range_byte: u8 = if config.full_range { 0x80 } else { 0x00 };
660 b.push(full_range_byte);
661 b
662}
663
664fn build_av1c(image: &AvifImage, config: &AvifConfig) -> Vec<u8> {
668 let seq_profile: u8 = match image.yuv_format {
675 YuvFormat::Yuv444 => 1,
676 _ => 0,
677 };
678 let seq_level_idx_0: u8 = 13; let byte0: u8 = 0x81; let byte1: u8 = (seq_profile << 5) | seq_level_idx_0;
682 let high_bitdepth = image.depth >= 10;
683 let twelve_bit = image.depth == 12;
684 let (sub_x, sub_y): (u8, u8) = match image.yuv_format {
685 YuvFormat::Yuv420 => (1, 1),
686 YuvFormat::Yuv422 => (1, 0),
687 YuvFormat::Yuv444 => (0, 0),
688 };
689 let byte2: u8 = (high_bitdepth as u8) << 6
690 | (twelve_bit as u8) << 5
691 | 0 << 4 | sub_x << 3
693 | sub_y << 2
694 | 0; let _ = config; let byte3: u8 = 0x00; let size = 8 + 4; let mut b = Vec::with_capacity(size);
700 write_u32(&mut b, size as u32);
701 b.extend_from_slice(b"av1C");
702 b.push(byte0);
703 b.push(byte1);
704 b.push(byte2);
705 b.push(byte3);
706 b
707}
708
709fn build_pixi(depth: u8) -> Vec<u8> {
711 let num_channels: u8 = 3;
713 let size = 8 + 4 + 1 + num_channels as usize;
714 let mut b = Vec::with_capacity(size);
715 write_u32(&mut b, size as u32);
716 b.extend_from_slice(b"pixi");
717 write_u32(&mut b, 0); b.push(num_channels);
719 for _ in 0..num_channels {
720 b.push(depth);
721 }
722 b
723}
724
725fn build_ipma(has_alpha: bool) -> Vec<u8> {
730 let item_count: u32 = if has_alpha { 2 } else { 1 };
734 let assoc_per_item: &[(u8, u8)] = &[
737 (0, 1), (0, 2), (1, 3), (0, 4), ];
742 let per_item_size = 2 + 1 + assoc_per_item.len(); let payload_size = 4 + item_count as usize * per_item_size;
744 let size = 8 + 4 + payload_size;
745 let mut b = Vec::with_capacity(size);
746 write_u32(&mut b, size as u32);
747 b.extend_from_slice(b"ipma");
748 write_u32(&mut b, 0); write_u32(&mut b, item_count);
750
751 for item_id in 1..=item_count as u16 {
752 write_u16(&mut b, item_id);
753 b.push(assoc_per_item.len() as u8);
754 for &(essential, prop_idx) in assoc_per_item {
755 b.push((essential << 7) | (prop_idx & 0x7F));
756 }
757 }
758 b
759}
760
761fn patch_iloc_offsets(
773 out: &mut Vec<u8>,
774 meta_buf: &[u8],
775 av1_payload: &[u8],
776 alpha_payload: Option<&[u8]>,
777) -> Result<(), CodecError> {
778 let ftyp_size = u32::from_be_bytes(
780 out.get(0..4)
781 .ok_or_else(|| CodecError::Internal("output too short for ftyp size".into()))?
782 .try_into()
783 .map_err(|_| CodecError::Internal("slice conversion error".into()))?,
784 ) as usize;
785
786 let meta_size = meta_buf.len();
787 let mdat_data_start = ftyp_size + meta_size + 8;
789
790 let color_offset = mdat_data_start as u32;
791 let alpha_offset = (mdat_data_start + av1_payload.len()) as u32;
792
793 let meta_start = ftyp_size;
797 let meta_body_start = meta_start + 12; let iloc_pos = find_box_in(out, meta_body_start, meta_start + meta_size, b"iloc")
801 .ok_or_else(|| CodecError::Internal("iloc box not found in output".into()))?;
802
803 let item0_start = iloc_pos + 16;
807 let color_extent_offset_pos = item0_start + 6; let color_extent_offset_pos = item0_start + 8;
812
813 patch_u32(out, color_extent_offset_pos, color_offset)?;
814
815 if alpha_payload.is_some() {
816 let item1_start = item0_start + 16;
817 let alpha_extent_offset_pos = item1_start + 8;
818 patch_u32(out, alpha_extent_offset_pos, alpha_offset)?;
819 }
820
821 Ok(())
822}
823
824fn find_box_in(data: &[u8], start: usize, end: usize, box_type: &[u8; 4]) -> Option<usize> {
826 let mut pos = start;
827 while pos + 8 <= end.min(data.len()) {
828 let size = u32::from_be_bytes(data[pos..pos + 4].try_into().ok()?) as usize;
829 if size < 8 {
830 break;
831 }
832 if &data[pos + 4..pos + 8] == box_type {
833 return Some(pos);
834 }
835 pos += size;
836 }
837 None
838}
839
840fn patch_u32(buf: &mut Vec<u8>, pos: usize, value: u32) -> Result<(), CodecError> {
842 if pos + 4 > buf.len() {
843 return Err(CodecError::Internal(format!(
844 "patch_u32: pos={pos} out of range (buf.len={})",
845 buf.len()
846 )));
847 }
848 let bytes = value.to_be_bytes();
849 buf[pos] = bytes[0];
850 buf[pos + 1] = bytes[1];
851 buf[pos + 2] = bytes[2];
852 buf[pos + 3] = bytes[3];
853 Ok(())
854}
855
856fn check_avif_signature(data: &[u8]) -> Result<(), CodecError> {
862 if data.len() < 12 {
863 return Err(CodecError::InvalidBitstream(
864 "file too short to be AVIF".into(),
865 ));
866 }
867 let size = u32::from_be_bytes(
868 data[0..4]
869 .try_into()
870 .map_err(|_| CodecError::InvalidBitstream("cannot read ftyp size".into()))?,
871 ) as usize;
872 if size < 12 || size > data.len() {
873 return Err(CodecError::InvalidBitstream("invalid ftyp box size".into()));
874 }
875 if &data[4..8] != b"ftyp" {
876 return Err(CodecError::InvalidBitstream("first box is not ftyp".into()));
877 }
878 let brands_region = &data[8..size];
880 let has_avif = brands_region
881 .chunks(4)
882 .any(|c| c.len() == 4 && c == b"avif");
883 if !has_avif {
884 return Err(CodecError::InvalidBitstream(
885 "ftyp does not contain 'avif' brand".into(),
886 ));
887 }
888 Ok(())
889}
890
891fn parse_meta_for_probe(data: &[u8]) -> Result<AvifProbeResult, CodecError> {
893 let meta_pos = find_top_level_box(data, b"meta")
895 .ok_or_else(|| CodecError::InvalidBitstream("meta box not found".into()))?;
896 let meta_size = u32::from_be_bytes(
897 data[meta_pos..meta_pos + 4]
898 .try_into()
899 .map_err(|_| CodecError::InvalidBitstream("meta size read error".into()))?,
900 ) as usize;
901 let meta_end = meta_pos + meta_size;
902
903 let meta_body = meta_pos + 12;
905
906 let (width, height) = parse_ispe(data, meta_body, meta_end)?;
908
909 let (color_primaries, transfer_characteristics) =
911 parse_colr(data, meta_body, meta_end).unwrap_or((1, 1));
912
913 let bit_depth = parse_pixi(data, meta_body, meta_end).unwrap_or(8);
915
916 let has_alpha = parse_iinf_has_alpha(data, meta_body, meta_end);
918
919 Ok(AvifProbeResult {
920 width,
921 height,
922 bit_depth,
923 has_alpha,
924 color_primaries,
925 transfer_characteristics,
926 })
927}
928
929fn parse_ispe(data: &[u8], start: usize, end: usize) -> Result<(u32, u32), CodecError> {
930 let pos = find_box_in(data, start, end, b"iprp")
931 .and_then(|iprp| {
932 let iprp_end =
933 iprp + u32::from_be_bytes(data[iprp..iprp + 4].try_into().ok()?) as usize;
934 find_box_in(data, iprp + 8, iprp_end, b"ipco").and_then(|ipco| {
935 let ipco_end =
936 ipco + u32::from_be_bytes(data[ipco..ipco + 4].try_into().ok()?) as usize;
937 find_box_in(data, ipco + 8, ipco_end, b"ispe")
938 })
939 })
940 .ok_or_else(|| CodecError::InvalidBitstream("ispe not found".into()))?;
941
942 if pos + 20 > data.len() {
944 return Err(CodecError::InvalidBitstream("ispe box truncated".into()));
945 }
946 let w = u32::from_be_bytes(
947 data[pos + 12..pos + 16]
948 .try_into()
949 .map_err(|_| CodecError::InvalidBitstream("ispe width read error".into()))?,
950 );
951 let h = u32::from_be_bytes(
952 data[pos + 16..pos + 20]
953 .try_into()
954 .map_err(|_| CodecError::InvalidBitstream("ispe height read error".into()))?,
955 );
956 Ok((w, h))
957}
958
959fn parse_colr(data: &[u8], start: usize, end: usize) -> Option<(u8, u8)> {
960 let iprp = find_box_in(data, start, end, b"iprp")?;
961 let iprp_end = iprp + u32::from_be_bytes(data[iprp..iprp + 4].try_into().ok()?) as usize;
962 let ipco = find_box_in(data, iprp + 8, iprp_end, b"ipco")?;
963 let ipco_end = ipco + u32::from_be_bytes(data[ipco..ipco + 4].try_into().ok()?) as usize;
964 let pos = find_box_in(data, ipco + 8, ipco_end, b"colr")?;
965 if pos + 15 > data.len() {
967 return None;
968 }
969 if &data[pos + 8..pos + 12] != b"nclx" {
970 return None;
971 }
972 let cp = u16::from_be_bytes(data[pos + 12..pos + 14].try_into().ok()?) as u8;
974 let tc = u16::from_be_bytes(data[pos + 14..pos + 16].try_into().ok()?) as u8;
975 Some((cp, tc))
976}
977
978fn parse_pixi(data: &[u8], start: usize, end: usize) -> Option<u8> {
979 let iprp = find_box_in(data, start, end, b"iprp")?;
980 let iprp_end = iprp + u32::from_be_bytes(data[iprp..iprp + 4].try_into().ok()?) as usize;
981 let ipco = find_box_in(data, iprp + 8, iprp_end, b"ipco")?;
982 let ipco_end = ipco + u32::from_be_bytes(data[ipco..ipco + 4].try_into().ok()?) as usize;
983 let pos = find_box_in(data, ipco + 8, ipco_end, b"pixi")?;
984 if pos + 14 > data.len() {
986 return None;
987 }
988 Some(data[pos + 13])
989}
990
991fn parse_iinf_has_alpha(data: &[u8], start: usize, end: usize) -> bool {
992 let pos = match find_box_in(data, start, end, b"iinf") {
993 Some(p) => p,
994 None => return false,
995 };
996 let iinf_size = u32::from_be_bytes(match data[pos..pos + 4].try_into() {
997 Ok(b) => b,
998 Err(_) => return false,
999 }) as usize;
1000 let entry_count = u16::from_be_bytes(match data[pos + 12..pos + 14].try_into() {
1002 Ok(b) => b,
1003 Err(_) => return false,
1004 });
1005 entry_count >= 2 && iinf_size >= 14
1007}
1008
1009fn locate_mdat_items(
1015 data: &[u8],
1016 has_alpha: bool,
1017) -> Result<(usize, usize, usize, usize), CodecError> {
1018 let meta_pos = find_top_level_box(data, b"meta")
1020 .ok_or_else(|| CodecError::InvalidBitstream("meta box not found".into()))?;
1021 let meta_size = u32::from_be_bytes(
1022 data[meta_pos..meta_pos + 4]
1023 .try_into()
1024 .map_err(|_| CodecError::InvalidBitstream("meta size".into()))?,
1025 ) as usize;
1026 let meta_end = meta_pos + meta_size;
1027 let meta_body = meta_pos + 12;
1028
1029 let iloc_pos = find_box_in(data, meta_body, meta_end, b"iloc")
1030 .ok_or_else(|| CodecError::InvalidBitstream("iloc not found".into()))?;
1031
1032 let version = data[iloc_pos + 8];
1038 if version != 1 {
1039 return Err(CodecError::UnsupportedFeature(format!(
1040 "iloc version {version} not supported"
1041 )));
1042 }
1043
1044 let item_count = u16::from_be_bytes(
1048 data[iloc_pos + 14..iloc_pos + 16]
1049 .try_into()
1050 .map_err(|_| CodecError::InvalidBitstream("iloc item_count".into()))?,
1051 );
1052
1053 if item_count == 0 {
1054 return Err(CodecError::InvalidBitstream("iloc has no items".into()));
1055 }
1056
1057 let item0 = iloc_pos + 16;
1059 let color_offset = u32::from_be_bytes(
1062 data[item0 + 8..item0 + 12]
1063 .try_into()
1064 .map_err(|_| CodecError::InvalidBitstream("color extent offset".into()))?,
1065 ) as usize;
1066 let color_len = u32::from_be_bytes(
1067 data[item0 + 12..item0 + 16]
1068 .try_into()
1069 .map_err(|_| CodecError::InvalidBitstream("color extent length".into()))?,
1070 ) as usize;
1071
1072 let (alpha_offset, alpha_len) = if has_alpha && item_count >= 2 {
1073 let item1 = item0 + 16;
1074 let ao = u32::from_be_bytes(
1075 data[item1 + 8..item1 + 12]
1076 .try_into()
1077 .map_err(|_| CodecError::InvalidBitstream("alpha extent offset".into()))?,
1078 ) as usize;
1079 let al = u32::from_be_bytes(
1080 data[item1 + 12..item1 + 16]
1081 .try_into()
1082 .map_err(|_| CodecError::InvalidBitstream("alpha extent length".into()))?,
1083 ) as usize;
1084 (ao, al)
1085 } else {
1086 (0, 0)
1087 };
1088
1089 Ok((color_offset, color_len, alpha_offset, alpha_len))
1090}
1091
1092fn find_top_level_box(data: &[u8], box_type: &[u8; 4]) -> Option<usize> {
1094 let mut pos = 0usize;
1095 while pos + 8 <= data.len() {
1096 let size = u32::from_be_bytes(data[pos..pos + 4].try_into().ok()?) as usize;
1097 if size < 8 {
1098 break;
1099 }
1100 if &data[pos + 4..pos + 8] == box_type {
1101 return Some(pos);
1102 }
1103 pos += size;
1104 }
1105 None
1106}
1107
1108fn validate_image(image: &AvifImage) -> Result<(), CodecError> {
1113 if image.width == 0 || image.height == 0 {
1114 return Err(CodecError::InvalidParameter(
1115 "image dimensions must be non-zero".into(),
1116 ));
1117 }
1118 if ![8u8, 10, 12].contains(&image.depth) {
1119 return Err(CodecError::InvalidParameter(format!(
1120 "unsupported bit depth {}; must be 8, 10, or 12",
1121 image.depth
1122 )));
1123 }
1124 let luma_samples = image.width as usize * image.height as usize;
1125 let bytes_per_sample: usize = if image.depth > 8 { 2 } else { 1 };
1126 let min_y = luma_samples * bytes_per_sample;
1127 if image.y_plane.len() < min_y {
1128 return Err(CodecError::InvalidParameter(format!(
1129 "y_plane too small: need {min_y}, have {}",
1130 image.y_plane.len()
1131 )));
1132 }
1133 Ok(())
1134}
1135
1136fn write_u32(out: &mut Vec<u8>, v: u32) {
1141 out.extend_from_slice(&v.to_be_bytes());
1142}
1143
1144fn write_u16(out: &mut Vec<u8>, v: u16) {
1145 out.extend_from_slice(&v.to_be_bytes());
1146}
1147
1148struct BitWriter {
1154 buf: Vec<u8>,
1155 current: u8,
1156 bits_in_current: u8,
1157}
1158
1159impl BitWriter {
1160 fn new() -> Self {
1161 Self {
1162 buf: Vec::new(),
1163 current: 0,
1164 bits_in_current: 0,
1165 }
1166 }
1167
1168 fn write_bits(&mut self, value: u32, n: u32) {
1170 for i in (0..n).rev() {
1171 let bit = ((value >> i) & 1) as u8;
1172 self.current = (self.current << 1) | bit;
1173 self.bits_in_current += 1;
1174 if self.bits_in_current == 8 {
1175 self.buf.push(self.current);
1176 self.current = 0;
1177 self.bits_in_current = 0;
1178 }
1179 }
1180 }
1181
1182 fn write_byte(&mut self, byte: u8) {
1183 self.write_bits(byte as u32, 8);
1184 }
1185
1186 fn extend_bytes(&mut self, bytes: &[u8]) {
1187 for &b in bytes {
1188 self.write_byte(b);
1189 }
1190 }
1191
1192 fn finish(mut self) -> Vec<u8> {
1194 if self.bits_in_current > 0 {
1195 self.current <<= 8 - self.bits_in_current;
1196 self.buf.push(self.current);
1197 }
1198 self.buf
1199 }
1200}
1201
1202#[cfg(test)]
1207mod tests {
1208 use super::*;
1209
1210 fn make_test_image(width: u32, height: u32, depth: u8, fmt: YuvFormat) -> AvifImage {
1211 let luma = width as usize * height as usize * if depth > 8 { 2 } else { 1 };
1212 let chroma = match fmt {
1213 YuvFormat::Yuv420 => (width as usize / 2) * (height as usize / 2),
1214 YuvFormat::Yuv422 => (width as usize / 2) * height as usize,
1215 YuvFormat::Yuv444 => width as usize * height as usize,
1216 } * if depth > 8 { 2 } else { 1 };
1217 AvifImage {
1218 width,
1219 height,
1220 depth,
1221 yuv_format: fmt,
1222 y_plane: vec![128u8; luma],
1223 u_plane: vec![128u8; chroma],
1224 v_plane: vec![128u8; chroma],
1225 alpha_plane: None,
1226 }
1227 }
1228
1229 #[test]
1230 fn test_ftyp_box() {
1231 let mut out = Vec::new();
1232 write_ftyp(&mut out);
1233 assert!(out.len() >= 20, "ftyp must be at least 20 bytes");
1234 assert_eq!(&out[4..8], b"ftyp", "box type must be 'ftyp'");
1235 assert_eq!(&out[8..12], b"avif", "major brand must be 'avif'");
1236 let brands_region = &out[8..];
1238 let has_avif = brands_region
1239 .chunks(4)
1240 .any(|c| c.len() == 4 && c == b"avif");
1241 assert!(has_avif, "compatible brands must contain 'avif'");
1242 }
1243
1244 #[test]
1245 fn test_encode_produces_valid_ftyp() {
1246 let image = make_test_image(64, 64, 8, YuvFormat::Yuv420);
1247 let config = AvifConfig::default();
1248 let encoder = AvifEncoder::new(config);
1249 let bytes = encoder.encode(&image).expect("encode failed");
1250 assert!(bytes.len() > 32, "encoded output too short");
1251 assert_eq!(&bytes[4..8], b"ftyp");
1253 assert_eq!(&bytes[8..12], b"avif");
1255 }
1256
1257 #[test]
1258 fn test_encode_contains_meta_and_mdat() {
1259 let image = make_test_image(128, 96, 8, YuvFormat::Yuv420);
1260 let encoder = AvifEncoder::new(AvifConfig::default());
1261 let bytes = encoder.encode(&image).expect("encode failed");
1262 assert!(
1263 find_top_level_box(&bytes, b"meta").is_some(),
1264 "meta box missing"
1265 );
1266 assert!(
1267 find_top_level_box(&bytes, b"mdat").is_some(),
1268 "mdat box missing"
1269 );
1270 }
1271
1272 #[test]
1273 fn test_probe_roundtrip_dimensions() {
1274 let image = make_test_image(320, 240, 8, YuvFormat::Yuv420);
1275 let encoder = AvifEncoder::new(AvifConfig::default());
1276 let bytes = encoder.encode(&image).expect("encode failed");
1277 let probe = AvifDecoder::probe(&bytes).expect("probe failed");
1278 assert_eq!(probe.width, 320, "probed width mismatch");
1279 assert_eq!(probe.height, 240, "probed height mismatch");
1280 assert_eq!(probe.bit_depth, 8, "probed bit_depth mismatch");
1281 assert!(!probe.has_alpha, "should not have alpha");
1282 }
1283
1284 #[test]
1285 fn test_probe_10bit() {
1286 let image = make_test_image(160, 120, 10, YuvFormat::Yuv420);
1287 let encoder = AvifEncoder::new(AvifConfig::default());
1288 let bytes = encoder.encode(&image).expect("encode 10-bit failed");
1289 let probe = AvifDecoder::probe(&bytes).expect("probe 10-bit failed");
1290 assert_eq!(probe.bit_depth, 10);
1291 }
1292
1293 #[test]
1294 fn test_probe_12bit() {
1295 let image = make_test_image(64, 64, 12, YuvFormat::Yuv420);
1296 let encoder = AvifEncoder::new(AvifConfig::default());
1297 let bytes = encoder.encode(&image).expect("encode 12-bit failed");
1298 let probe = AvifDecoder::probe(&bytes).expect("probe 12-bit failed");
1299 assert_eq!(probe.bit_depth, 12);
1300 }
1301
1302 #[test]
1303 fn test_decode_roundtrip_color_payload() {
1304 let image = make_test_image(64, 48, 8, YuvFormat::Yuv420);
1305 let encoder = AvifEncoder::new(AvifConfig::default());
1306 let bytes = encoder.encode(&image).expect("encode failed");
1307 let decoded = AvifDecoder::decode(&bytes).expect("decode failed");
1308 assert_eq!(decoded.width, 64);
1309 assert_eq!(decoded.height, 48);
1310 assert!(
1312 !decoded.y_plane.is_empty(),
1313 "decoded y_plane (AV1 OBU) should not be empty"
1314 );
1315 }
1316
1317 #[test]
1318 fn test_encode_with_alpha() {
1319 let mut image = make_test_image(64, 64, 8, YuvFormat::Yuv420);
1320 image.alpha_plane = Some(vec![255u8; 64 * 64]);
1321 let config = AvifConfig {
1322 alpha_quality: Some(80),
1323 ..AvifConfig::default()
1324 };
1325 let encoder = AvifEncoder::new(config);
1326 let bytes = encoder.encode(&image).expect("encode with alpha failed");
1327 let probe = AvifDecoder::probe(&bytes).expect("probe with alpha failed");
1328 assert!(probe.has_alpha, "probe should detect alpha");
1329 }
1330
1331 #[test]
1332 fn test_decode_with_alpha() {
1333 let mut image = make_test_image(64, 64, 8, YuvFormat::Yuv420);
1334 image.alpha_plane = Some(vec![200u8; 64 * 64]);
1335 let config = AvifConfig {
1336 alpha_quality: Some(90),
1337 ..AvifConfig::default()
1338 };
1339 let encoder = AvifEncoder::new(config);
1340 let bytes = encoder.encode(&image).expect("encode failed");
1341 let decoded = AvifDecoder::decode(&bytes).expect("decode failed");
1342 assert!(
1343 decoded.alpha_plane.is_some(),
1344 "decoded image should have alpha"
1345 );
1346 assert!(!decoded
1347 .alpha_plane
1348 .expect("alpha plane should exist")
1349 .is_empty());
1350 }
1351
1352 #[test]
1353 fn test_invalid_signature_rejected() {
1354 let garbage = b"not an avif file at all".to_vec();
1355 assert!(
1356 AvifDecoder::probe(&garbage).is_err(),
1357 "garbage input must be rejected"
1358 );
1359 }
1360
1361 #[test]
1362 fn test_zero_dimension_rejected() {
1363 let image = AvifImage {
1364 width: 0,
1365 height: 100,
1366 depth: 8,
1367 yuv_format: YuvFormat::Yuv420,
1368 y_plane: vec![0u8; 100],
1369 u_plane: vec![],
1370 v_plane: vec![],
1371 alpha_plane: None,
1372 };
1373 let encoder = AvifEncoder::new(AvifConfig::default());
1374 assert!(encoder.encode(&image).is_err());
1375 }
1376
1377 #[test]
1378 fn test_invalid_bit_depth_rejected() {
1379 let image = AvifImage {
1380 width: 8,
1381 height: 8,
1382 depth: 9, yuv_format: YuvFormat::Yuv420,
1384 y_plane: vec![0u8; 64],
1385 u_plane: vec![0u8; 16],
1386 v_plane: vec![0u8; 16],
1387 alpha_plane: None,
1388 };
1389 let encoder = AvifEncoder::new(AvifConfig::default());
1390 assert!(encoder.encode(&image).is_err());
1391 }
1392
1393 #[test]
1394 fn test_colr_box_written() {
1395 let image = make_test_image(32, 32, 8, YuvFormat::Yuv420);
1396 let config = AvifConfig {
1397 color_primaries: 9,
1398 transfer_characteristics: 16,
1399 matrix_coefficients: 9,
1400 ..AvifConfig::default()
1401 };
1402 let encoder = AvifEncoder::new(config);
1403 let bytes = encoder.encode(&image).expect("encode failed");
1404 let probe = AvifDecoder::probe(&bytes).expect("probe failed");
1405 assert_eq!(probe.color_primaries, 9);
1406 assert_eq!(probe.transfer_characteristics, 16);
1407 }
1408
1409 #[test]
1410 fn test_yuv444_encode() {
1411 let image = make_test_image(64, 64, 8, YuvFormat::Yuv444);
1412 let encoder = AvifEncoder::new(AvifConfig::default());
1413 let bytes = encoder.encode(&image).expect("yuv444 encode failed");
1414 let probe = AvifDecoder::probe(&bytes).expect("yuv444 probe failed");
1415 assert_eq!(probe.width, 64);
1416 assert_eq!(probe.height, 64);
1417 }
1418
1419 #[test]
1420 fn test_leb128_encoding() {
1421 let mut buf = Vec::new();
1422 write_leb128(&mut buf, 0);
1423 assert_eq!(buf, &[0x00]);
1424
1425 buf.clear();
1426 write_leb128(&mut buf, 127);
1427 assert_eq!(buf, &[0x7F]);
1428
1429 buf.clear();
1430 write_leb128(&mut buf, 128);
1431 assert_eq!(buf, &[0x80, 0x01]);
1432
1433 buf.clear();
1434 write_leb128(&mut buf, 300);
1435 assert_eq!(buf, &[0xAC, 0x02]);
1436 }
1437
1438 #[test]
1439 fn test_bit_writer() {
1440 let mut bw = BitWriter::new();
1441 bw.write_bits(0b10110011, 8);
1442 let out = bw.finish();
1443 assert_eq!(out, &[0b10110011]);
1444
1445 let mut bw = BitWriter::new();
1446 bw.write_bits(1, 1);
1447 bw.write_bits(0, 1);
1448 bw.write_bits(1, 1);
1449 bw.write_bits(0, 4);
1450 bw.write_bits(1, 1);
1451 let out = bw.finish();
1452 assert_eq!(out, &[0b10100001]);
1453 }
1454
1455 #[test]
1456 fn test_av1c_box_structure() {
1457 let image = make_test_image(64, 64, 8, YuvFormat::Yuv420);
1458 let config = AvifConfig::default();
1459 let av1c = build_av1c(&image, &config);
1460 assert_eq!(av1c.len(), 12, "av1C box must be 12 bytes");
1461 assert_eq!(&av1c[4..8], b"av1C");
1462 assert_eq!(av1c[8], 0x81, "marker+version byte must be 0x81");
1463 }
1464
1465 #[test]
1466 fn test_ispe_box_structure() {
1467 let ispe = build_ispe(1920, 1080);
1468 assert_eq!(ispe.len(), 20);
1469 assert_eq!(&ispe[4..8], b"ispe");
1470 let w = u32::from_be_bytes(ispe[12..16].try_into().expect("4-byte slice for width"));
1471 let h = u32::from_be_bytes(ispe[16..20].try_into().expect("4-byte slice for height"));
1472 assert_eq!(w, 1920);
1473 assert_eq!(h, 1080);
1474 }
1475
1476 #[test]
1477 fn test_large_image_encode() {
1478 let image = make_test_image(3840, 2160, 8, YuvFormat::Yuv420);
1479 let encoder = AvifEncoder::new(AvifConfig::default());
1480 let bytes = encoder.encode(&image).expect("4K encode failed");
1481 let probe = AvifDecoder::probe(&bytes).expect("4K probe failed");
1482 assert_eq!(probe.width, 3840);
1483 assert_eq!(probe.height, 2160);
1484 }
1485}