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