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