1mod boxes;
12pub mod constants;
13mod writer;
14
15use crate::boxes::*;
16use arrayvec::ArrayVec;
17use std::io;
18
19pub struct Aviffy {
23 premultiplied_alpha: bool,
24 colr: ColrBox,
25 clli: Option<ClliBox>,
26 mdcv: Option<MdcvBox>,
27 min_seq_profile: u8,
28 chroma_subsampling: (bool, bool),
29 monochrome: bool,
30 width: u32,
31 height: u32,
32 bit_depth: u8,
33 exif: Option<Vec<u8>>,
34}
35
36pub fn serialize<W: io::Write>(into_output: W, color_av1_data: &[u8], alpha_av1_data: Option<&[u8]>, width: u32, height: u32, depth_bits: u8) -> io::Result<()> {
54 Aviffy::new()
55 .set_width(width)
56 .set_height(height)
57 .set_bit_depth(depth_bits)
58 .write_slice(into_output, color_av1_data, alpha_av1_data)
59}
60
61impl Aviffy {
62 #[inline]
66 #[must_use]
67 pub fn new() -> Self {
68 Self {
69 premultiplied_alpha: false,
70 min_seq_profile: 1,
71 chroma_subsampling: (false, false),
72 monochrome: false,
73 width: 0,
74 height: 0,
75 bit_depth: 0,
76 colr: ColrBox::default(),
77 clli: None,
78 mdcv: None,
79 exif: None,
80 }
81 }
82
83 #[inline]
87 pub fn set_matrix_coefficients(&mut self, matrix_coefficients: constants::MatrixCoefficients) -> &mut Self {
88 self.colr.matrix_coefficients = matrix_coefficients;
89 self
90 }
91
92 #[doc(hidden)]
93 pub fn matrix_coefficients(&mut self, matrix_coefficients: constants::MatrixCoefficients) -> &mut Self {
94 self.set_matrix_coefficients(matrix_coefficients)
95 }
96
97 #[inline]
100 pub fn set_transfer_characteristics(&mut self, transfer_characteristics: constants::TransferCharacteristics) -> &mut Self {
101 self.colr.transfer_characteristics = transfer_characteristics;
102 self
103 }
104
105 #[doc(hidden)]
106 pub fn transfer_characteristics(&mut self, transfer_characteristics: constants::TransferCharacteristics) -> &mut Self {
107 self.set_transfer_characteristics(transfer_characteristics)
108 }
109
110 #[inline]
113 pub fn set_color_primaries(&mut self, color_primaries: constants::ColorPrimaries) -> &mut Self {
114 self.colr.color_primaries = color_primaries;
115 self
116 }
117
118 #[doc(hidden)]
119 pub fn color_primaries(&mut self, color_primaries: constants::ColorPrimaries) -> &mut Self {
120 self.set_color_primaries(color_primaries)
121 }
122
123 #[inline]
126 pub fn set_full_color_range(&mut self, full_range: bool) -> &mut Self {
127 self.colr.full_range_flag = full_range;
128 self
129 }
130
131 #[doc(hidden)]
132 pub fn full_color_range(&mut self, full_range: bool) -> &mut Self {
133 self.set_full_color_range(full_range)
134 }
135
136 #[inline]
143 pub fn set_content_light_level(&mut self, max_content_light_level: u16, max_pic_average_light_level: u16) -> &mut Self {
144 self.clli = Some(ClliBox {
145 max_content_light_level,
146 max_pic_average_light_level,
147 });
148 self
149 }
150
151 #[inline]
162 pub fn set_mastering_display(&mut self, primaries: [(u16, u16); 3], white_point: (u16, u16), max_luminance: u32, min_luminance: u32) -> &mut Self {
163 self.mdcv = Some(MdcvBox {
164 primaries,
165 white_point,
166 max_luminance,
167 min_luminance,
168 });
169 self
170 }
171
172 #[inline]
188 pub fn write<W: io::Write>(&self, into_output: W, color_av1_data: &[u8], alpha_av1_data: Option<&[u8]>, width: u32, height: u32, depth_bits: u8) -> io::Result<()> {
189 self.make_boxes(color_av1_data, alpha_av1_data, width, height, depth_bits)?.write(into_output)
190 }
191
192 #[inline]
194 pub fn write_slice<W: io::Write>(&self, into_output: W, color_av1_data: &[u8], alpha_av1_data: Option<&[u8]>) -> io::Result<()> {
195 self.make_boxes(color_av1_data, alpha_av1_data, self.width, self.height, self.bit_depth)?.write(into_output)
196 }
197
198 fn make_boxes<'data>(&'data self, color_av1_data: &'data [u8], alpha_av1_data: Option<&'data [u8]>, width: u32, height: u32, depth_bits: u8) -> io::Result<AvifFile<'data>> {
199 if ![8, 10, 12].contains(&depth_bits) {
200 return Err(io::Error::new(io::ErrorKind::InvalidInput, "depth must be 8/10/12"));
201 }
202
203 let mut image_items = ArrayVec::new();
204 let mut iloc_items = ArrayVec::new();
205 let mut ipma_entries = ArrayVec::new();
206 let mut irefs = ArrayVec::new();
207 let mut ipco = IpcoBox::new();
208 let color_image_id = 1;
209 let alpha_image_id = 2;
210 let exif_id = 3;
211 const ESSENTIAL_BIT: u8 = 0x80;
212 let color_depth_bits = depth_bits;
213 let alpha_depth_bits = depth_bits; image_items.push(InfeBox {
216 id: color_image_id,
217 typ: FourCC(*b"av01"),
218 name: "",
219 });
220
221 let ispe_prop = ipco.push(IpcoProp::Ispe(IspeBox { width, height })).ok_or(io::ErrorKind::InvalidInput)?;
222
223 let av1c_color_prop = ipco.push(IpcoProp::Av1C(Av1CBox {
225 seq_profile: self.min_seq_profile.max(if color_depth_bits >= 12 { 2 } else { 0 }),
226 seq_level_idx_0: 31,
227 seq_tier_0: false,
228 high_bitdepth: color_depth_bits >= 10,
229 twelve_bit: color_depth_bits >= 12,
230 monochrome: self.monochrome,
231 chroma_subsampling_x: self.chroma_subsampling.0,
232 chroma_subsampling_y: self.chroma_subsampling.1,
233 chroma_sample_position: 0,
234 })).ok_or(io::ErrorKind::InvalidInput)?;
235
236 let pixi_3 = ipco.push(IpcoProp::Pixi(PixiBox {
238 channels: 3,
239 depth: color_depth_bits,
240 })).ok_or(io::ErrorKind::InvalidInput)?;
241
242 let mut ipma = IpmaEntry {
243 item_id: color_image_id,
244 prop_ids: from_array([ispe_prop, av1c_color_prop | ESSENTIAL_BIT, pixi_3]),
245 };
246
247 if self.colr != ColrBox::default() {
249 let colr_color_prop = ipco.push(IpcoProp::Colr(self.colr)).ok_or(io::ErrorKind::InvalidInput)?;
250 ipma.prop_ids.push(colr_color_prop);
251 }
252
253 if let Some(clli) = self.clli {
254 let clli_prop = ipco.push(IpcoProp::Clli(clli)).ok_or(io::ErrorKind::InvalidInput)?;
255 ipma.prop_ids.push(clli_prop);
256 }
257
258 if let Some(mdcv) = self.mdcv {
259 let mdcv_prop = ipco.push(IpcoProp::Mdcv(mdcv)).ok_or(io::ErrorKind::InvalidInput)?;
260 ipma.prop_ids.push(mdcv_prop);
261 }
262
263 ipma_entries.push(ipma);
264
265 if let Some(exif_data) = self.exif.as_deref() {
266 image_items.push(InfeBox {
267 id: exif_id,
268 typ: FourCC(*b"Exif"),
269 name: "",
270 });
271
272 iloc_items.push(IlocItem {
273 id: exif_id,
274 extents: [IlocExtent { data: exif_data }],
275 });
276
277 irefs.push(IrefEntryBox {
278 from_id: exif_id,
279 to_id: color_image_id,
280 typ: FourCC(*b"cdsc"),
281 });
282 }
283
284 if let Some(alpha_data) = alpha_av1_data {
285 image_items.push(InfeBox {
286 id: alpha_image_id,
287 typ: FourCC(*b"av01"),
288 name: "",
289 });
290
291 irefs.push(IrefEntryBox {
292 from_id: alpha_image_id,
293 to_id: color_image_id,
294 typ: FourCC(*b"auxl"),
295 });
296
297 if self.premultiplied_alpha {
298 irefs.push(IrefEntryBox {
299 from_id: color_image_id,
300 to_id: alpha_image_id,
301 typ: FourCC(*b"prem"),
302 });
303 }
304
305 let av1c_alpha_prop = ipco.push(boxes::IpcoProp::Av1C(Av1CBox {
306 seq_profile: if alpha_depth_bits >= 12 { 2 } else { 0 },
307 seq_level_idx_0: 31,
308 seq_tier_0: false,
309 high_bitdepth: alpha_depth_bits >= 10,
310 twelve_bit: alpha_depth_bits >= 12,
311 monochrome: true,
312 chroma_subsampling_x: true,
313 chroma_subsampling_y: true,
314 chroma_sample_position: 0,
315 })).ok_or(io::ErrorKind::InvalidInput)?;
316
317 let pixi_1 = ipco.push(IpcoProp::Pixi(PixiBox {
319 channels: 1,
320 depth: alpha_depth_bits,
321 })).ok_or(io::ErrorKind::InvalidInput)?;
322
323 let auxc_prop = ipco.push(IpcoProp::AuxC(AuxCBox {
325 urn: "urn:mpeg:mpegB:cicp:systems:auxiliary:alpha",
326 })).ok_or(io::ErrorKind::InvalidInput)?;
327
328 ipma_entries.push(IpmaEntry {
329 item_id: alpha_image_id,
330 prop_ids: from_array([ispe_prop, av1c_alpha_prop | ESSENTIAL_BIT, auxc_prop, pixi_1]),
331 });
332
333 iloc_items.push(IlocItem {
336 id: alpha_image_id,
337 extents: [IlocExtent { data: alpha_data }],
338 });
339 }
340 iloc_items.push(IlocItem {
341 id: color_image_id,
342 extents: [IlocExtent { data: color_av1_data }],
343 });
344
345 Ok(AvifFile {
346 ftyp: FtypBox {
347 major_brand: FourCC(*b"avif"),
348 minor_version: 0,
349 compatible_brands: [FourCC(*b"mif1"), FourCC(*b"miaf")].into(),
350 },
351 meta: MetaBox {
352 hdlr: HdlrBox {},
353 iinf: IinfBox { items: image_items },
354 pitm: PitmBox(color_image_id),
355 iloc: IlocBox {
356 absolute_offset_start: None,
357 items: iloc_items,
358 },
359 iprp: IprpBox {
360 ipco,
361 ipma: IpmaBox { entries: ipma_entries },
364 },
365 iref: IrefBox { entries: irefs },
366 },
367 mdat: MdatBox,
370 })
371 }
372
373 #[must_use]
375 #[track_caller]
376 pub fn to_vec(&self, color_av1_data: &[u8], alpha_av1_data: Option<&[u8]>, width: u32, height: u32, depth_bits: u8) -> Vec<u8> {
377 let mut file = self.make_boxes(color_av1_data, alpha_av1_data, width, height, depth_bits).unwrap();
378 let mut out = Vec::new();
379 file.write_to_vec(&mut out).unwrap();
380 out
381 }
382
383 #[inline]
388 pub fn set_chroma_subsampling(&mut self, subsampled_xy: (bool, bool)) -> &mut Self {
389 self.chroma_subsampling = subsampled_xy;
390 self
391 }
392
393 #[inline]
396 pub fn set_monochrome(&mut self, monochrome: bool) -> &mut Self {
397 self.monochrome = monochrome;
398 self
399 }
400
401 #[inline]
403 pub fn set_exif(&mut self, exif: Vec<u8>) -> &mut Self {
404 self.exif = Some(exif);
405 self
406 }
407
408 #[inline]
412 pub fn set_seq_profile(&mut self, seq_profile: u8) -> &mut Self {
413 self.min_seq_profile = seq_profile;
414 self
415 }
416
417 #[inline]
418 pub fn set_width(&mut self, width: u32) -> &mut Self {
419 self.width = width;
420 self
421 }
422
423 #[inline]
424 pub fn set_height(&mut self, height: u32) -> &mut Self {
425 self.height = height;
426 self
427 }
428
429 #[inline]
431 pub fn set_bit_depth(&mut self, bit_depth: u8) -> &mut Self {
432 self.bit_depth = bit_depth;
433 self
434 }
435
436 #[inline]
449 pub fn set_premultiplied_alpha(&mut self, is_premultiplied: bool) -> &mut Self {
450 self.premultiplied_alpha = is_premultiplied;
451 self
452 }
453
454 #[doc(hidden)]
455 pub fn premultiplied_alpha(&mut self, is_premultiplied: bool) -> &mut Self {
456 self.set_premultiplied_alpha(is_premultiplied)
457 }
458}
459
460#[inline(always)]
461fn from_array<const L1: usize, const L2: usize, T: Copy>(array: [T; L1]) -> ArrayVec<T, L2> {
462 assert!(L1 <= L2);
463 let mut tmp = ArrayVec::new_const();
464 let _ = tmp.try_extend_from_slice(&array);
465 tmp
466}
467
468#[must_use]
470#[track_caller]
471pub fn serialize_to_vec(color_av1_data: &[u8], alpha_av1_data: Option<&[u8]>, width: u32, height: u32, depth_bits: u8) -> Vec<u8> {
472 Aviffy::new().to_vec(color_av1_data, alpha_av1_data, width, height, depth_bits)
473}
474
475#[test]
476fn test_roundtrip_parse_mp4() {
477 let test_img = b"av12356abc";
478 let avif = serialize_to_vec(test_img, None, 10, 20, 8);
479
480 let ctx = mp4parse::read_avif(&mut avif.as_slice(), mp4parse::ParseStrictness::Normal).unwrap();
481
482 assert_eq!(&test_img[..], ctx.primary_item_coded_data().unwrap());
483}
484
485#[test]
486fn test_roundtrip_parse_mp4_alpha() {
487 let test_img = b"av12356abc";
488 let test_a = b"alpha";
489 let avif = serialize_to_vec(test_img, Some(test_a), 10, 20, 8);
490
491 let ctx = mp4parse::read_avif(&mut avif.as_slice(), mp4parse::ParseStrictness::Normal).unwrap();
492
493 assert_eq!(&test_img[..], ctx.primary_item_coded_data().unwrap());
494 assert_eq!(&test_a[..], ctx.alpha_item_coded_data().unwrap());
495}
496
497#[test]
498fn test_roundtrip_parse_exif() {
499 let test_img = b"av12356abc";
500 let test_a = b"alpha";
501 let avif = Aviffy::new()
502 .set_exif(b"lol".to_vec())
503 .to_vec(test_img, Some(test_a), 10, 20, 8);
504
505 let ctx = mp4parse::read_avif(&mut avif.as_slice(), mp4parse::ParseStrictness::Normal).unwrap();
506
507 assert_eq!(&test_img[..], ctx.primary_item_coded_data().unwrap());
508 assert_eq!(&test_a[..], ctx.alpha_item_coded_data().unwrap());
509}
510
511#[test]
512fn test_roundtrip_parse_avif() {
513 let test_img = [1, 2, 3, 4, 5, 6];
514 let test_alpha = [77, 88, 99];
515 let avif = serialize_to_vec(&test_img, Some(&test_alpha), 10, 20, 8);
516
517 let ctx = avif_parse::read_avif(&mut avif.as_slice()).unwrap();
518
519 assert_eq!(&test_img[..], ctx.primary_item.as_slice());
520 assert_eq!(&test_alpha[..], ctx.alpha_item.as_deref().unwrap());
521}
522
523#[test]
524fn test_roundtrip_parse_avif_colr() {
525 let test_img = [1, 2, 3, 4, 5, 6];
526 let test_alpha = [77, 88, 99];
527 let avif = Aviffy::new()
528 .matrix_coefficients(constants::MatrixCoefficients::Bt709)
529 .to_vec(&test_img, Some(&test_alpha), 10, 20, 8);
530
531 let ctx = avif_parse::read_avif(&mut avif.as_slice()).unwrap();
532
533 assert_eq!(&test_img[..], ctx.primary_item.as_slice());
534 assert_eq!(&test_alpha[..], ctx.alpha_item.as_deref().unwrap());
535}
536
537#[test]
538fn premultiplied_flag() {
539 let test_img = [1,2,3,4];
540 let test_alpha = [55,66,77,88,99];
541 let avif = Aviffy::new().premultiplied_alpha(true).to_vec(&test_img, Some(&test_alpha), 5, 5, 8);
542
543 let ctx = avif_parse::read_avif(&mut avif.as_slice()).unwrap();
544
545 assert!(ctx.premultiplied_alpha);
546 assert_eq!(&test_img[..], ctx.primary_item.as_slice());
547 assert_eq!(&test_alpha[..], ctx.alpha_item.as_deref().unwrap());
548}
549
550#[test]
551fn size_required() {
552 assert!(Aviffy::new().set_bit_depth(10).write_slice(&mut vec![], &[], None).is_err());
553}
554
555#[test]
556fn depth_required() {
557 assert!(Aviffy::new().set_width(1).set_height(1).write_slice(&mut vec![], &[], None).is_err());
558}
559
560#[test]
561fn clli_roundtrip() {
562 let test_img = [1, 2, 3, 4, 5, 6];
563 let avif = Aviffy::new()
564 .set_content_light_level(1000, 400)
565 .to_vec(&test_img, None, 10, 20, 8);
566
567 let parser = avif_parse::read_avif(&mut avif.as_slice()).unwrap();
568 let cll = parser.content_light_level.expect("clli box should be present");
569 assert_eq!(cll.max_content_light_level, 1000);
570 assert_eq!(cll.max_pic_average_light_level, 400);
571}
572
573#[test]
574fn mdcv_roundtrip() {
575 let test_img = [1, 2, 3, 4, 5, 6];
576 let primaries = [
578 (8500, 39850), (6550, 2300), (35400, 14600), ];
582 let white_point = (15635, 16450); let max_luminance = 10_000_000; let min_luminance = 1; let avif = Aviffy::new()
587 .set_mastering_display(primaries, white_point, max_luminance, min_luminance)
588 .to_vec(&test_img, None, 10, 20, 8);
589
590 let parser = avif_parse::read_avif(&mut avif.as_slice()).unwrap();
591 let mdcv = parser.mastering_display.expect("mdcv box should be present");
592 assert_eq!(mdcv.primaries, primaries);
593 assert_eq!(mdcv.white_point, white_point);
594 assert_eq!(mdcv.max_luminance, max_luminance);
595 assert_eq!(mdcv.min_luminance, min_luminance);
596}
597
598#[test]
599fn hdr10_full_metadata() {
600 let test_img = [1, 2, 3, 4, 5, 6];
601 let test_alpha = [77, 88, 99];
602 let primaries = [
603 (8500, 39850),
604 (6550, 2300),
605 (35400, 14600),
606 ];
607 let white_point = (15635, 16450);
608
609 let avif = Aviffy::new()
610 .set_transfer_characteristics(constants::TransferCharacteristics::Smpte2084)
611 .set_color_primaries(constants::ColorPrimaries::Bt2020)
612 .set_matrix_coefficients(constants::MatrixCoefficients::Bt2020Ncl)
613 .set_content_light_level(4000, 1000)
614 .set_mastering_display(primaries, white_point, 40_000_000, 50)
615 .to_vec(&test_img, Some(&test_alpha), 10, 20, 10);
616
617 let parser = avif_parse::read_avif(&mut avif.as_slice()).unwrap();
618
619 let cll = parser.content_light_level.expect("clli box should be present");
621 assert_eq!(cll.max_content_light_level, 4000);
622 assert_eq!(cll.max_pic_average_light_level, 1000);
623
624 let mdcv = parser.mastering_display.expect("mdcv box should be present");
626 assert_eq!(mdcv.primaries, primaries);
627 assert_eq!(mdcv.white_point, white_point);
628 assert_eq!(mdcv.max_luminance, 40_000_000);
629 assert_eq!(mdcv.min_luminance, 50);
630
631 let ctx = avif_parse::read_avif(&mut avif.as_slice()).unwrap();
633 assert_eq!(ctx.primary_item.as_slice(), &test_img[..]);
634 assert_eq!(ctx.alpha_item.as_deref().unwrap(), &test_alpha[..]);
635}
636
637#[test]
638fn no_hdr_metadata_by_default() {
639 let test_img = [1, 2, 3, 4, 5, 6];
640 let avif = serialize_to_vec(&test_img, None, 10, 20, 8);
641
642 let parser = avif_parse::read_avif(&mut avif.as_slice()).unwrap();
643 assert!(parser.content_light_level.is_none());
644 assert!(parser.mastering_display.is_none());
645}