1use crate::boxes::{FourCc, Reader};
24use crate::meta::Meta;
25use crate::props::{Av1Config, Colour, Subsampling};
26use crate::yuv::{Depths, Layout, YuvMatrix, identity_to_rgb, plane_to_grey, yuv_to_rgb};
27use otf_pixels_core::{
28 Codec, DecodeCapability, Decoder, Format, ImageDescriptor, Limits, Orientation, PixelFormat,
29 PixelsError, Result, Source, SourceInfo,
30};
31
32const MAX_CONTAINER: usize = 256 * 1024 * 1024;
40
41#[derive(Debug, Clone)]
46pub struct AvifInfo {
47 pub width: u32,
49 pub height: u32,
51 pub config: Av1Config,
54 pub has_alpha: bool,
56 pub is_grid: bool,
59 pub colour: Option<Colour>,
62 pub icc: Option<Vec<u8>>,
64 pub orientation: Orientation,
67}
68
69#[derive(Debug)]
71struct AlphaItem {
72 frame_data: Vec<u8>,
74 config_obus: Vec<u8>,
76 premultiplied: bool,
79}
80
81#[derive(Debug)]
83pub struct AvifDecoder {
84 descriptor: ImageDescriptor,
85 info: AvifInfo,
86 frame_data: Option<Vec<u8>>,
89 alpha: Option<AlphaItem>,
91 exif: Option<Vec<u8>>,
93 xmp: Option<Vec<u8>>,
95 raster: Option<Vec<u8>>,
97 row: u32,
99}
100
101impl AvifDecoder {
102 pub fn new<S: Source>(source: S, limits: Limits) -> Result<Self> {
114 let bytes = read_all(source)?;
115 let info = parse_container(&bytes)?;
116 let (frame_data, alpha) = if info.is_grid {
117 (None, None)
118 } else {
119 (Some(locate_primary_frame(&bytes)?), locate_alpha(&bytes)?)
120 };
121 let pixel = pixel_format(&info);
122 let descriptor = ImageDescriptor::with_limits(info.width, info.height, pixel, &limits)?;
123 let exif = locate_exif(&bytes);
124 let xmp = locate_xmp(&bytes);
125 Ok(Self {
126 descriptor,
127 info,
128 frame_data,
129 alpha,
130 exif,
131 xmp,
132 raster: None,
133 row: 0,
134 })
135 }
136
137 #[must_use]
139 pub const fn info(&self) -> &AvifInfo {
140 &self.info
141 }
142}
143
144fn read_all<S: Source>(mut source: S) -> Result<Vec<u8>> {
146 let mut bytes = Vec::new();
147 let mut chunk = [0_u8; 64 * 1024];
148 loop {
149 if bytes.len() > MAX_CONTAINER {
150 return Err(PixelsError::malformed(
151 "avif",
152 format!("stream exceeds {MAX_CONTAINER} bytes"),
153 ));
154 }
155 match source.read(&mut chunk)? {
156 0 => break,
157 read => {
158 let Some(filled) = chunk.get(..read) else {
159 break;
160 };
161 bytes.extend_from_slice(filled);
162 }
163 }
164 }
165 Ok(bytes)
166}
167
168fn parse_container(bytes: &[u8]) -> Result<AvifInfo> {
170 let mut reader = Reader::new(bytes);
171
172 let first = reader
175 .next_box()
176 .ok_or_else(|| PixelsError::malformed("avif", "the file holds no boxes"))??;
177 if first.kind != FourCc::new(b"ftyp") {
178 return Err(PixelsError::malformed(
179 "avif",
180 format!("the file begins with '{}' rather than 'ftyp'", first.kind),
181 ));
182 }
183 if !probe(bytes) {
184 return Err(PixelsError::malformed(
185 "avif",
186 "the ftyp box declares no brand this decoder recognises",
187 ));
188 }
189
190 let meta_reader = reader
191 .find(b"meta")?
192 .ok_or_else(|| PixelsError::malformed("avif", "the file has no meta box"))?;
193 let meta = Meta::parse(meta_reader)?;
194
195 let primary = meta
196 .primary_item()
197 .ok_or_else(|| PixelsError::malformed("avif", "the file names no primary image item"))?;
198
199 if let Some(kind) = meta.properties.essential_unknown(primary.id) {
203 return Err(PixelsError::unsupported(format!(
204 "avif: the primary item requires the '{kind}' property, which this decoder does not implement"
205 )));
206 }
207
208 if !primary.is_coded_image() && !primary.is_grid() {
209 return Err(PixelsError::unsupported(format!(
210 "avif: the primary item has type '{}', which is not an image this decoder produces",
211 primary.kind
212 )));
213 }
214
215 let extents = meta.properties.extents(primary.id).ok_or_else(|| {
216 PixelsError::malformed(
217 "avif",
218 format!(
219 "item {} has no ispe property, so its size is unknown",
220 primary.id
221 ),
222 )
223 })?;
224
225 let config_item = if primary.is_grid() {
228 *meta
229 .referenced(primary.id, b"dimg")
230 .first()
231 .ok_or_else(|| {
232 PixelsError::malformed(
233 "avif",
234 format!("grid item {} references no tiles", primary.id),
235 )
236 })?
237 } else {
238 primary.id
239 };
240
241 let config = meta
242 .properties
243 .av1_config(config_item)
244 .ok_or_else(|| {
245 PixelsError::malformed(
246 "avif",
247 format!("item {config_item} has no av1C property, so it is not a coded AV1 image"),
248 )
249 })?
250 .clone();
251
252 Ok(AvifInfo {
253 width: extents.width,
254 height: extents.height,
255 config,
256 has_alpha: meta.alpha_item(primary.id).is_some(),
257 is_grid: primary.is_grid(),
258 colour: meta.properties.colour(primary.id).cloned(),
259 icc: meta.properties.icc_profile(primary.id).map(<[u8]>::to_vec),
260 orientation: meta.properties.orientation(primary.id),
261 })
262}
263
264fn locate_primary_frame(bytes: &[u8]) -> Result<Vec<u8>> {
266 let mut reader = Reader::new(bytes);
267 reader.next_box();
268 let meta_reader = reader
269 .find(b"meta")?
270 .ok_or_else(|| PixelsError::malformed("avif", "the file has no meta box"))?;
271 let meta = Meta::parse(meta_reader)?;
272 let primary = meta
273 .primary_item()
274 .ok_or_else(|| PixelsError::malformed("avif", "the file names no primary image item"))?;
275 Ok(meta.item_data(bytes, primary)?.into_owned())
276}
277
278fn locate_exif(bytes: &[u8]) -> Option<Vec<u8>> {
283 let mut reader = Reader::new(bytes);
284 reader.next_box();
285 let meta = Meta::parse(reader.find(b"meta").ok()??).ok()?;
286 let primary = meta.primary_item()?.id;
287 let item = meta.items.iter().find(|item| {
288 item.kind == FourCc::new(b"Exif") && meta.referenced(item.id, b"cdsc").contains(&primary)
289 })?;
290 let data = meta.item_data(bytes, item).ok()?;
291 let offset = u32::from_be_bytes(data.get(..4)?.try_into().ok()?);
294 let start = 4_usize.checked_add(usize::try_from(offset).ok()?)?;
295 Some(data.get(start..)?.to_vec())
296}
297
298fn locate_xmp(bytes: &[u8]) -> Option<Vec<u8>> {
301 let mut reader = Reader::new(bytes);
302 reader.next_box();
303 let meta = Meta::parse(reader.find(b"meta").ok()??).ok()?;
304 let primary = meta.primary_item()?.id;
305 let item = meta.items.iter().find(|item| {
306 item.kind == FourCc::new(b"mime")
307 && item.content_type == "application/rdf+xml"
308 && meta.referenced(item.id, b"cdsc").contains(&primary)
309 })?;
310 Some(meta.item_data(bytes, item).ok()?.to_vec())
311}
312
313fn locate_alpha(bytes: &[u8]) -> Result<Option<AlphaItem>> {
315 let mut reader = Reader::new(bytes);
316 reader.next_box();
317 let meta_reader = reader
318 .find(b"meta")?
319 .ok_or_else(|| PixelsError::malformed("avif", "the file has no meta box"))?;
320 let meta = Meta::parse(meta_reader)?;
321 let primary = meta
322 .primary_item()
323 .ok_or_else(|| PixelsError::malformed("avif", "the file names no primary image item"))?;
324 let Some(alpha) = meta.alpha_item(primary.id) else {
325 return Ok(None);
326 };
327 let config = meta.properties.av1_config(alpha.id).ok_or_else(|| {
328 PixelsError::malformed(
329 "avif",
330 format!("alpha item {} has no av1C property", alpha.id),
331 )
332 })?;
333 Ok(Some(AlphaItem {
334 frame_data: meta.item_data(bytes, alpha)?.into_owned(),
335 config_obus: config.config_obus.clone(),
336 premultiplied: meta.referenced(primary.id, b"prem").contains(&alpha.id),
338 }))
339}
340
341fn decode_frame(
343 config_obus: &[u8],
344 frame_data: &[u8],
345) -> Result<(crate::av1::DecodedFrame, crate::av1::SequenceHeader)> {
346 use crate::av1::{StillPicture, decode_still};
347
348 let still = StillPicture::parse(config_obus, frame_data)?;
349 let groups = still.tile_group_data(frame_data)?;
350 let frame = decode_still(&still.sequence, &still.frame, &groups)?;
351 Ok((frame, still.sequence))
352}
353
354fn decode_raster(
357 info: &AvifInfo,
358 pixel: PixelFormat,
359 frame_data: &[u8],
360 alpha: Option<&AlphaItem>,
361) -> Result<Vec<u8>> {
362 let (frame, sequence) = decode_frame(&info.config.config_obus, frame_data)?;
363 let color = &sequence.color;
364 let layout = Layout {
365 width: info.width as usize,
366 height: info.height as usize,
367 subsampling_x: usize::from(color.subsampling_x),
368 subsampling_y: usize::from(color.subsampling_y),
369 };
370 let depths = Depths::for_input(color.bit_depth);
371 let (colour, channels) = if color.mono_chrome {
372 let luma = frame
373 .planes
374 .first()
375 .ok_or_else(|| PixelsError::malformed("avif", "the luma plane is missing"))?;
376 (plane_to_grey(luma, layout, depths, color.color_range), 1)
377 } else {
378 (
379 colour_to_rgb(info, &frame.planes, color, layout, depths)?,
380 3,
381 )
382 };
383 let mut colour = colour;
384 let alpha = match (info.has_alpha, alpha) {
385 (false, _) => None,
386 (true, Some(item)) => {
387 let samples = decode_alpha(item, layout, depths)?;
388 if item.premultiplied {
389 unpremultiply(&mut colour, channels, &samples, depths.output_max());
390 }
391 Some(samples)
392 }
393 (true, None) => {
396 return Err(PixelsError::unsupported(
397 "avif: this file's alpha plane is not decodable yet",
398 ));
399 }
400 };
401 Ok(pack(pixel, &colour, channels, alpha.as_deref()))
402}
403
404fn colour_to_rgb(
406 info: &AvifInfo,
407 planes: &[crate::av1::Plane],
408 color: &crate::av1::ColorConfig,
409 layout: Layout,
410 depths: Depths,
411) -> Result<Vec<u16>> {
412 let matrix = match &info.colour {
416 Some(Colour::Nclx { matrix, .. }) => *matrix,
417 _ => u16::from(color.matrix_coefficients),
418 };
419 if matrix != 0 {
420 let yuv = YuvMatrix::new(matrix, color.color_range, depths)?;
421 return yuv_to_rgb(planes, layout, &yuv);
422 }
423 if (layout.subsampling_x, layout.subsampling_y) != (0, 0) {
424 return Err(PixelsError::malformed(
425 "avif",
426 "the identity colour matrix requires 4:4:4, but the chroma is subsampled",
427 ));
428 }
429 identity_to_rgb(planes, layout, depths)
430}
431
432fn decode_alpha(item: &AlphaItem, layout: Layout, colour_depths: Depths) -> Result<Vec<u16>> {
436 let (frame, sequence) = decode_frame(&item.config_obus, &item.frame_data)?;
437 let luma = frame
438 .planes
439 .first()
440 .ok_or_else(|| PixelsError::malformed("avif", "the alpha plane is missing"))?;
441 let depths = Depths {
442 input: sequence.color.bit_depth,
443 output: colour_depths.output,
444 };
445 let alpha_layout = Layout {
446 subsampling_x: 0,
447 subsampling_y: 0,
448 ..layout
449 };
450 Ok(plane_to_grey(
451 luma,
452 alpha_layout,
453 depths,
454 sequence.color.color_range,
455 ))
456}
457
458fn unpremultiply(colour: &mut [u16], channels: usize, alpha: &[u16], max: i64) {
464 for (px, &a) in colour.chunks_exact_mut(channels.max(1)).zip(alpha) {
465 let a = i64::from(a);
466 if a >= max {
467 continue;
468 }
469 for sample in px {
470 *sample = if a == 0 {
471 0
472 } else {
473 ((i64::from(*sample) * max * 2 + a) / (2 * a)).min(max) as u16
474 };
475 }
476 }
477}
478
479fn pack(pixel: PixelFormat, colour: &[u16], channels: usize, alpha: Option<&[u16]>) -> Vec<u8> {
485 let wide = matches!(
486 pixel,
487 PixelFormat::Rgb16 | PixelFormat::Rgba16 | PixelFormat::Gray16
488 );
489 let out_colour = match pixel {
490 PixelFormat::Gray8 | PixelFormat::Gray16 | PixelFormat::GrayA8 => 1,
491 _ => 3,
492 };
493 let pixels = colour.len() / channels.max(1);
494 let mut raster = Vec::with_capacity(pixels * (out_colour + 1) * if wide { 2 } else { 1 });
495 let mut put = |v: u16| {
496 if wide {
497 raster.extend_from_slice(&v.to_ne_bytes());
498 } else {
499 raster.push(v as u8);
500 }
501 };
502 for (i, px) in colour.chunks_exact(channels.max(1)).enumerate() {
503 for c in 0..out_colour {
504 put(px.get(c).or_else(|| px.first()).copied().unwrap_or(0));
506 }
507 if let Some(alpha) = alpha {
508 put(alpha.get(i).copied().unwrap_or(0));
509 }
510 }
511 raster
512}
513
514fn pixel_format(info: &AvifInfo) -> PixelFormat {
522 let wide = info.config.bit_depth > 8;
523 let monochrome = info.config.subsampling == Subsampling::Monochrome;
524 match (monochrome, info.has_alpha, wide) {
525 (true, false, false) => PixelFormat::Gray8,
526 (true, false, true) => PixelFormat::Gray16,
527 (true, true, false) => PixelFormat::GrayA8,
528 (true, true, true) => PixelFormat::Rgba16,
532 (false, false, false) => PixelFormat::Rgb8,
533 (false, false, true) => PixelFormat::Rgb16,
534 (false, true, false) => PixelFormat::Rgba8,
535 (false, true, true) => PixelFormat::Rgba16,
536 }
537}
538
539impl Decoder for AvifDecoder {
540 fn descriptor(&self) -> ImageDescriptor {
541 self.descriptor
542 }
543
544 fn orientation(&self) -> Orientation {
545 self.info.orientation
546 }
547
548 fn icc_profile(&self) -> Option<&[u8]> {
549 self.info.icc.as_deref()
550 }
551
552 fn exif(&self) -> Option<&[u8]> {
554 self.exif.as_deref()
555 }
556
557 fn xmp(&self) -> Option<&[u8]> {
559 self.xmp.as_deref()
560 }
561
562 fn source_info(&self) -> SourceInfo {
564 let mut info = SourceInfo::default();
565 info.bits_per_sample = Some(self.info.config.bit_depth);
566 info.compression = Some("av1");
567 info.chroma_subsampling = match self.info.config.subsampling {
568 Subsampling::Monochrome => None,
569 Subsampling::Yuv420 => Some("4:2:0"),
570 Subsampling::Yuv422 => Some("4:2:2"),
571 Subsampling::Yuv444 => Some("4:4:4"),
572 };
573 info
574 }
575
576 fn capability(&self) -> DecodeCapability {
577 DecodeCapability::Sequential
583 }
584
585 fn read_row(&mut self, out: &mut [u8]) -> Result<()> {
586 if self.row >= self.descriptor.height {
589 return Err(PixelsError::invalid_argument(
590 "out",
591 format!("all {} rows have already been read", self.descriptor.height),
592 ));
593 }
594 let row_bytes = self.descriptor.row_bytes();
595 if out.len() != row_bytes {
596 return Err(PixelsError::invalid_argument(
597 "out",
598 format!("row buffer is {} bytes, expected {row_bytes}", out.len()),
599 ));
600 }
601
602 if self.raster.is_none() {
607 let frame_data = self
608 .frame_data
609 .as_deref()
610 .ok_or_else(|| PixelsError::unsupported("avif: grid images are not decoded yet"))?;
611 self.raster = Some(decode_raster(
612 &self.info,
613 self.descriptor.pixel,
614 frame_data,
615 self.alpha.as_ref(),
616 )?);
617 }
618 let raster = self.raster.as_deref().unwrap_or(&[]);
619 let start = self.row as usize * row_bytes;
620 let src = raster
621 .get(start..start + row_bytes)
622 .ok_or_else(|| PixelsError::malformed("avif", "the reconstructed raster is short"))?;
623 out.copy_from_slice(src);
624 self.row += 1;
625 Ok(())
626 }
627}
628
629#[must_use]
638pub fn probe(prefix: &[u8]) -> bool {
639 if prefix.get(4..8) != Some(&crate::SIGNATURE_FTYP[..]) {
640 return false;
641 }
642 let Some(brands) = prefix.get(8..) else {
643 return false;
644 };
645 brands
648 .chunks_exact(4)
649 .enumerate()
650 .filter(|(index, _)| *index != 1)
651 .any(|(_, brand)| is_known_brand(brand))
652}
653
654fn is_known_brand(brand: &[u8]) -> bool {
656 crate::BRANDS_STILL
657 .iter()
658 .chain(core::iter::once(&crate::BRAND_SEQUENCE))
659 .any(|known| known == brand)
660}
661
662#[derive(Debug, Clone, Copy, Default)]
664pub struct AvifCodec;
665
666impl Codec for AvifCodec {
667 fn format(&self) -> Format {
668 Format::Avif
669 }
670
671 fn magic_len(&self) -> usize {
672 32
677 }
678
679 fn probe(&self, prefix: &[u8]) -> bool {
680 probe(prefix)
681 }
682}
683
684#[cfg(test)]
685#[allow(
686 clippy::unwrap_used,
687 clippy::indexing_slicing,
688 reason = "tests operate on known-good values and assert shapes directly"
689)]
690mod tests {
691 use super::*;
692
693 #[test]
694 fn unpremultiplying_restores_straight_colour() {
695 let mut colour = vec![10, 20, 30, 50, 64, 200, 9, 9, 9];
698 unpremultiply(&mut colour, 3, &[255, 128, 0], 255);
699 assert_eq!(colour, vec![10, 20, 30, 100, 128, 255, 0, 0, 0]);
700 let mut wide = vec![1000, 30000, 65535];
701 unpremultiply(&mut wide, 3, &[32768], 65535);
702 assert_eq!(wide, vec![2000, 59999, 65535]);
704 let mut grey = vec![60, 255];
706 unpremultiply(&mut grey, 1, &[120, 255], 255);
707 assert_eq!(grey, vec![128, 255]);
708 }
709 use otf_pixels_core::ErrorCode;
710
711 fn boxed(kind: &[u8; 4], payload: &[u8]) -> Vec<u8> {
712 let mut out = Vec::new();
713 let total = u32::try_from(8 + payload.len()).unwrap();
714 out.extend_from_slice(&total.to_be_bytes());
715 out.extend_from_slice(kind);
716 out.extend_from_slice(payload);
717 out
718 }
719
720 fn ftyp(major: &[u8; 4], compatible: &[&[u8; 4]]) -> Vec<u8> {
721 let mut payload = Vec::from(*major);
722 payload.extend_from_slice(&0_u32.to_be_bytes());
723 for brand in compatible {
724 payload.extend_from_slice(*brand);
725 }
726 boxed(b"ftyp", &payload)
727 }
728
729 fn av1c() -> Vec<u8> {
731 boxed(b"av1C", &[0x81, 0x00, 0x0C, 0x00])
732 }
733
734 fn ispe(width: u32, height: u32) -> Vec<u8> {
735 let mut payload = vec![0, 0, 0, 0];
736 payload.extend_from_slice(&width.to_be_bytes());
737 payload.extend_from_slice(&height.to_be_bytes());
738 boxed(b"ispe", &payload)
739 }
740
741 fn infe(id: u16, kind: &[u8; 4], hidden: bool) -> Vec<u8> {
742 let mut payload = vec![2, 0, 0, u8::from(hidden)];
743 payload.extend_from_slice(&id.to_be_bytes());
744 payload.extend_from_slice(&[0, 0]);
745 payload.extend_from_slice(kind);
746 payload.push(0);
747 boxed(b"infe", &payload)
748 }
749
750 fn iinf(entries: &[Vec<u8>]) -> Vec<u8> {
751 let mut payload = vec![0, 0, 0, 0];
752 payload.extend_from_slice(&u16::try_from(entries.len()).unwrap().to_be_bytes());
753 for entry in entries {
754 payload.extend_from_slice(entry);
755 }
756 boxed(b"iinf", &payload)
757 }
758
759 fn iloc(rows: &[(u16, u32, u32)]) -> Vec<u8> {
760 let mut payload = vec![0, 0, 0, 0, 0x44, 0x00];
761 payload.extend_from_slice(&u16::try_from(rows.len()).unwrap().to_be_bytes());
762 for (id, offset, length) in rows {
763 payload.extend_from_slice(&id.to_be_bytes());
764 payload.extend_from_slice(&[0, 0]);
765 payload.extend_from_slice(&1_u16.to_be_bytes());
766 payload.extend_from_slice(&offset.to_be_bytes());
767 payload.extend_from_slice(&length.to_be_bytes());
768 }
769 boxed(b"iloc", &payload)
770 }
771
772 fn pitm(id: u16) -> Vec<u8> {
773 let mut payload = vec![0, 0, 0, 0];
774 payload.extend_from_slice(&id.to_be_bytes());
775 boxed(b"pitm", &payload)
776 }
777
778 fn ipma(rows: &[(u16, &[u8])]) -> Vec<u8> {
781 let mut payload = vec![0, 0, 0, 0];
782 payload.extend_from_slice(&u32::try_from(rows.len()).unwrap().to_be_bytes());
783 for (item, properties) in rows {
784 payload.extend_from_slice(&item.to_be_bytes());
785 payload.push(u8::try_from(properties.len()).unwrap());
786 payload.extend_from_slice(properties);
787 }
788 boxed(b"ipma", &payload)
789 }
790
791 fn iprp(properties: &[Vec<u8>], associations: &[(u16, &[u8])]) -> Vec<u8> {
792 let mut ipco = Vec::new();
793 for property in properties {
794 ipco.extend_from_slice(property);
795 }
796 let mut payload = boxed(b"ipco", &ipco);
797 payload.extend_from_slice(&ipma(associations));
798 boxed(b"iprp", &payload)
799 }
800
801 fn meta_box(children: &[Vec<u8>]) -> Vec<u8> {
802 let mut payload = vec![0, 0, 0, 0];
803 for child in children {
804 payload.extend_from_slice(child);
805 }
806 boxed(b"meta", &payload)
807 }
808
809 fn minimal_avif(width: u32, height: u32) -> Vec<u8> {
811 let mut file = ftyp(b"avif", &[b"mif1"]);
812 file.extend_from_slice(&meta_box(&[
813 pitm(1),
814 iinf(&[infe(1, b"av01", false)]),
815 iloc(&[(1, 0, 1)]),
816 iprp(&[ispe(width, height), av1c()], &[(1, &[1, 2])]),
817 ]));
818 file.extend_from_slice(&boxed(b"mdat", &[0]));
819 file
820 }
821
822 #[test]
823 fn reads_dimensions_without_decoding_pixels() {
824 let file = minimal_avif(320, 240);
825 let decoder = AvifDecoder::new(&file[..], Limits::default()).unwrap();
826 let descriptor = decoder.descriptor();
827 assert_eq!((descriptor.width, descriptor.height), (320, 240));
828 assert_eq!(descriptor.pixel, PixelFormat::Rgb8);
829 assert_eq!(decoder.info().config.bit_depth, 8);
830 assert_eq!(decoder.info().config.subsampling, Subsampling::Yuv420);
831 assert!(!decoder.info().has_alpha);
832 assert!(!decoder.info().is_grid);
833 }
834
835 #[test]
836 fn an_alpha_auxiliary_item_widens_the_pixel_format() {
837 let mut auxl = Vec::new();
838 auxl.extend_from_slice(&2_u16.to_be_bytes()); auxl.extend_from_slice(&1_u16.to_be_bytes());
840 auxl.extend_from_slice(&1_u16.to_be_bytes()); let mut iref_payload = vec![0, 0, 0, 0];
842 iref_payload.extend_from_slice(&boxed(b"auxl", &auxl));
843
844 let mut auxc_payload = vec![0, 0, 0, 0];
845 auxc_payload.extend_from_slice(crate::meta::URN_ALPHA.as_bytes());
846 auxc_payload.push(0);
847
848 let mut file = ftyp(b"avif", &[]);
849 file.extend_from_slice(&meta_box(&[
850 pitm(1),
851 iinf(&[infe(1, b"av01", false), infe(2, b"av01", true)]),
852 iloc(&[(1, 0, 1), (2, 0, 1)]),
853 boxed(b"iref", &iref_payload),
854 iprp(
855 &[ispe(8, 8), av1c(), boxed(b"auxC", &auxc_payload)],
856 &[(1, &[1, 2]), (2, &[1, 2, 3])],
857 ),
858 ]));
859
860 let decoder = AvifDecoder::new(&file[..], Limits::default()).unwrap();
861 assert!(decoder.info().has_alpha);
862 assert_eq!(decoder.descriptor().pixel, PixelFormat::Rgba8);
863 }
864
865 #[test]
866 fn a_grid_takes_its_configuration_from_its_first_tile() {
867 let mut dimg = Vec::new();
868 dimg.extend_from_slice(&1_u16.to_be_bytes()); dimg.extend_from_slice(&2_u16.to_be_bytes()); dimg.extend_from_slice(&2_u16.to_be_bytes());
871 dimg.extend_from_slice(&3_u16.to_be_bytes());
872 let mut iref_payload = vec![0, 0, 0, 0];
873 iref_payload.extend_from_slice(&boxed(b"dimg", &dimg));
874
875 let mut file = ftyp(b"avif", &[]);
876 file.extend_from_slice(&meta_box(&[
877 pitm(1),
878 iinf(&[
879 infe(1, b"grid", false),
880 infe(2, b"av01", true),
881 infe(3, b"av01", true),
882 ]),
883 iloc(&[(1, 0, 1), (2, 0, 1), (3, 0, 1)]),
884 boxed(b"iref", &iref_payload),
885 iprp(
887 &[ispe(128, 64), av1c(), ispe(64, 64)],
888 &[(1, &[1]), (2, &[3, 2]), (3, &[3, 2])],
889 ),
890 ]));
891
892 let decoder = AvifDecoder::new(&file[..], Limits::default()).unwrap();
893 assert!(decoder.info().is_grid);
894 assert_eq!(
895 (decoder.descriptor().width, decoder.descriptor().height),
896 (128, 64)
897 );
898 assert_eq!(decoder.info().config.bit_depth, 8);
899 }
900
901 #[test]
902 fn pack_interleaves_colour_and_alpha_in_the_formats_layout() {
903 assert_eq!(
905 pack(PixelFormat::GrayA8, &[10, 20], 1, Some(&[200, 100])),
906 [10, 200, 20, 100]
907 );
908 assert_eq!(
910 pack(PixelFormat::Rgba8, &[1, 2, 3], 3, Some(&[4])),
911 [1, 2, 3, 4]
912 );
913 let wide = pack(PixelFormat::Rgba16, &[0x1234], 1, Some(&[0xFFFF]));
916 let samples: Vec<u16> = wide
917 .chunks_exact(2)
918 .map(|b| u16::from_ne_bytes([b[0], b[1]]))
919 .collect();
920 assert_eq!(samples, [0x1234, 0x1234, 0x1234, 0xFFFF]);
921 assert_eq!(pack(PixelFormat::Gray8, &[7, 8], 1, None), [7, 8]);
923 }
924
925 #[test]
926 fn monochrome_and_wide_samples_choose_the_right_pixel_format() {
927 fn format_for(av1c_bytes: [u8; 4], has_alpha: bool) -> PixelFormat {
928 let info = AvifInfo {
929 width: 1,
930 height: 1,
931 config: Av1Config {
932 seq_profile: av1c_bytes[1] >> 5,
933 seq_level_idx0: 0,
934 seq_tier0: 0,
935 bit_depth: 8,
936 subsampling: Subsampling::Yuv420,
937 chroma_sample_position: 0,
938 config_obus: Vec::new(),
939 },
940 has_alpha,
941 is_grid: false,
942 colour: None,
943 icc: None,
944 orientation: Orientation::Normal,
945 };
946 pixel_format(&info)
947 }
948 assert_eq!(format_for([0x81, 0, 0x0C, 0], false), PixelFormat::Rgb8);
949 assert_eq!(format_for([0x81, 0, 0x0C, 0], true), PixelFormat::Rgba8);
950
951 let mono = |depth: u8, alpha: bool| {
952 pixel_format(&AvifInfo {
953 width: 1,
954 height: 1,
955 config: Av1Config {
956 seq_profile: 0,
957 seq_level_idx0: 0,
958 seq_tier0: 0,
959 bit_depth: depth,
960 subsampling: Subsampling::Monochrome,
961 chroma_sample_position: 0,
962 config_obus: Vec::new(),
963 },
964 has_alpha: alpha,
965 is_grid: false,
966 colour: None,
967 icc: None,
968 orientation: Orientation::Normal,
969 })
970 };
971 assert_eq!(mono(8, false), PixelFormat::Gray8);
972 assert_eq!(mono(8, true), PixelFormat::GrayA8);
973 assert_eq!(mono(10, false), PixelFormat::Gray16);
974 assert_eq!(mono(12, true), PixelFormat::Rgba16);
976 }
977
978 #[test]
979 fn the_row_buffer_size_contract_is_enforced_before_decoding() {
980 let file = minimal_avif(4, 4);
981 let mut decoder = AvifDecoder::new(&file[..], Limits::default()).unwrap();
982
983 let mut wrong = [0_u8; 3];
986 assert_eq!(
987 decoder.read_row(&mut wrong).unwrap_err().code(),
988 ErrorCode::InvalidArgument
989 );
990
991 let mut row = vec![0_u8; decoder.descriptor().row_bytes()];
995 assert!(decoder.read_row(&mut row).is_err());
996 }
997
998 #[test]
999 fn dimensions_are_limited_before_anything_is_allocated() {
1000 let file = minimal_avif(100_000, 100_000);
1001 let error = AvifDecoder::new(&file[..], Limits::default()).unwrap_err();
1002 assert_eq!(error.code(), ErrorCode::LimitExceeded);
1003 }
1004
1005 #[test]
1006 fn an_essential_property_we_do_not_understand_refuses_the_file() {
1007 let mut file = ftyp(b"avif", &[]);
1008 file.extend_from_slice(&meta_box(&[
1009 pitm(1),
1010 iinf(&[infe(1, b"av01", false)]),
1011 iloc(&[(1, 0, 1)]),
1012 iprp(
1014 &[ispe(8, 8), av1c(), boxed(b"zzzz", &[0; 4])],
1015 &[(1, &[1, 2, 0x83])],
1016 ),
1017 ]));
1018
1019 let error = AvifDecoder::new(&file[..], Limits::default()).unwrap_err();
1020 assert_eq!(error.code(), ErrorCode::Unsupported);
1021 assert!(error.to_string().contains("zzzz"), "{error}");
1022 }
1023
1024 #[test]
1025 fn a_file_without_the_boxes_an_image_needs_is_rejected() {
1026 let mut file = ftyp(b"avif", &[]);
1028 file.extend_from_slice(&boxed(b"mdat", &[0; 4]));
1029 let error = AvifDecoder::new(&file[..], Limits::default()).unwrap_err();
1030 assert_eq!(error.code(), ErrorCode::Malformed);
1031 assert!(error.to_string().contains("no meta box"), "{error}");
1032
1033 let mut file = ftyp(b"avif", &[]);
1035 file.extend_from_slice(&meta_box(&[pitm(1)]));
1036 let error = AvifDecoder::new(&file[..], Limits::default()).unwrap_err();
1037 assert_eq!(error.code(), ErrorCode::Malformed);
1038 assert!(
1039 error.to_string().contains("no primary image item"),
1040 "{error}"
1041 );
1042
1043 let mut file = ftyp(b"avif", &[]);
1045 file.extend_from_slice(&meta_box(&[
1046 pitm(1),
1047 iinf(&[infe(1, b"av01", false)]),
1048 iloc(&[(1, 0, 1)]),
1049 iprp(&[av1c()], &[(1, &[1])]),
1050 ]));
1051 let error = AvifDecoder::new(&file[..], Limits::default()).unwrap_err();
1052 assert_eq!(error.code(), ErrorCode::Malformed);
1053 assert!(error.to_string().contains("no ispe"), "{error}");
1054
1055 let mut file = ftyp(b"avif", &[]);
1057 file.extend_from_slice(&meta_box(&[
1058 pitm(1),
1059 iinf(&[infe(1, b"av01", false)]),
1060 iloc(&[(1, 0, 1)]),
1061 iprp(&[ispe(8, 8)], &[(1, &[1])]),
1062 ]));
1063 let error = AvifDecoder::new(&file[..], Limits::default()).unwrap_err();
1064 assert_eq!(error.code(), ErrorCode::Malformed);
1065 assert!(error.to_string().contains("no av1C"), "{error}");
1066 }
1067
1068 #[test]
1069 fn a_stream_that_is_not_an_avif_is_rejected() {
1070 for bytes in [
1071 &b""[..],
1072 &b"not an avif at all"[..],
1073 &b"\x89PNG\r\n\x1a\n"[..],
1074 ] {
1075 let error = AvifDecoder::new(bytes, Limits::default()).unwrap_err();
1076 assert_eq!(error.code(), ErrorCode::Malformed, "for {bytes:?}");
1077 }
1078 }
1079
1080 #[test]
1081 fn probe_needs_a_brand_not_just_an_isobmff_header() {
1082 assert!(probe(&ftyp(b"avif", &[])));
1083 assert!(probe(&ftyp(b"mif1", &[b"avif", b"miaf"])));
1085 assert!(probe(&ftyp(b"MA1B", &[])));
1086 assert!(probe(&ftyp(b"avis", &[b"avif"])));
1087
1088 assert!(!probe(&ftyp(b"isom", &[b"mp42"])));
1090 assert!(!probe(&ftyp(b"heic", &[b"heix"])));
1091 assert!(!probe(&ftyp(b"qt ", &[])));
1092
1093 assert!(!probe(b""));
1095 assert!(!probe(b"\0\0\0\x20ftyp"));
1096 assert!(!probe(b"\x89PNG\r\n\x1a\n"));
1097 }
1098
1099 #[test]
1103 fn the_minor_version_is_not_read_as_a_brand() {
1104 let mut payload = Vec::from(*b"isom");
1105 payload.extend_from_slice(b"avif"); payload.extend_from_slice(b"mp42");
1107 assert!(!probe(&boxed(b"ftyp", &payload)));
1108 }
1109
1110 #[test]
1111 fn the_codec_entry_reports_the_format() {
1112 assert_eq!(AvifCodec.format(), Format::Avif);
1113 assert!(AvifCodec.probe(&ftyp(b"avif", &[])));
1114 assert!(!AvifCodec.probe(b"short"));
1115 }
1116}