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 min_seq_profile: u8,
26 chroma_subsampling: (bool, bool),
27 monochrome: bool,
28 width: u32,
29 height: u32,
30 bit_depth: u8,
31 exif: Option<Vec<u8>>,
32}
33
34pub 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<()> {
52 Aviffy::new()
53 .set_width(width)
54 .set_height(height)
55 .set_bit_depth(depth_bits)
56 .write_slice(into_output, color_av1_data, alpha_av1_data)
57}
58
59impl Aviffy {
60 #[inline]
64 #[must_use]
65 pub fn new() -> Self {
66 Self {
67 premultiplied_alpha: false,
68 min_seq_profile: 1,
69 chroma_subsampling: (false, false),
70 monochrome: false,
71 width: 0,
72 height: 0,
73 bit_depth: 0,
74 colr: ColrBox::default(),
75 exif: None,
76 }
77 }
78
79 #[inline]
83 pub fn set_matrix_coefficients(&mut self, matrix_coefficients: constants::MatrixCoefficients) -> &mut Self {
84 self.colr.matrix_coefficients = matrix_coefficients;
85 self
86 }
87
88 #[doc(hidden)]
89 pub fn matrix_coefficients(&mut self, matrix_coefficients: constants::MatrixCoefficients) -> &mut Self {
90 self.set_matrix_coefficients(matrix_coefficients)
91 }
92
93 #[inline]
96 pub fn set_transfer_characteristics(&mut self, transfer_characteristics: constants::TransferCharacteristics) -> &mut Self {
97 self.colr.transfer_characteristics = transfer_characteristics;
98 self
99 }
100
101 #[doc(hidden)]
102 pub fn transfer_characteristics(&mut self, transfer_characteristics: constants::TransferCharacteristics) -> &mut Self {
103 self.set_transfer_characteristics(transfer_characteristics)
104 }
105
106 #[inline]
109 pub fn set_color_primaries(&mut self, color_primaries: constants::ColorPrimaries) -> &mut Self {
110 self.colr.color_primaries = color_primaries;
111 self
112 }
113
114 #[doc(hidden)]
115 pub fn color_primaries(&mut self, color_primaries: constants::ColorPrimaries) -> &mut Self {
116 self.set_color_primaries(color_primaries)
117 }
118
119 #[inline]
122 pub fn set_full_color_range(&mut self, full_range: bool) -> &mut Self {
123 self.colr.full_range_flag = full_range;
124 self
125 }
126
127 #[doc(hidden)]
128 pub fn full_color_range(&mut self, full_range: bool) -> &mut Self {
129 self.set_full_color_range(full_range)
130 }
131
132 #[inline]
148 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<()> {
149 self.make_boxes(color_av1_data, alpha_av1_data, width, height, depth_bits)?.write(into_output)
150 }
151
152 #[inline]
154 pub fn write_slice<W: io::Write>(&self, into_output: W, color_av1_data: &[u8], alpha_av1_data: Option<&[u8]>) -> io::Result<()> {
155 self.make_boxes(color_av1_data, alpha_av1_data, self.width, self.height, self.bit_depth)?.write(into_output)
156 }
157
158 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>> {
159 if ![8, 10, 12].contains(&depth_bits) {
160 return Err(io::Error::new(io::ErrorKind::InvalidInput, "depth must be 8/10/12"));
161 }
162
163 let mut image_items = ArrayVec::new();
164 let mut iloc_items = ArrayVec::new();
165 let mut ipma_entries = ArrayVec::new();
166 let mut irefs = ArrayVec::new();
167 let mut ipco = IpcoBox::new();
168 let color_image_id = 1;
169 let alpha_image_id = 2;
170 let exif_id = 3;
171 const ESSENTIAL_BIT: u8 = 0x80;
172 let color_depth_bits = depth_bits;
173 let alpha_depth_bits = depth_bits; image_items.push(InfeBox {
176 id: color_image_id,
177 typ: FourCC(*b"av01"),
178 name: "",
179 });
180
181 let ispe_prop = ipco.push(IpcoProp::Ispe(IspeBox { width, height })).ok_or(io::ErrorKind::InvalidInput)?;
182
183 let av1c_color_prop = ipco.push(IpcoProp::Av1C(Av1CBox {
185 seq_profile: self.min_seq_profile.max(if color_depth_bits >= 12 { 2 } else { 0 }),
186 seq_level_idx_0: 31,
187 seq_tier_0: false,
188 high_bitdepth: color_depth_bits >= 10,
189 twelve_bit: color_depth_bits >= 12,
190 monochrome: self.monochrome,
191 chroma_subsampling_x: self.chroma_subsampling.0,
192 chroma_subsampling_y: self.chroma_subsampling.1,
193 chroma_sample_position: 0,
194 })).ok_or(io::ErrorKind::InvalidInput)?;
195
196 let pixi_3 = ipco.push(IpcoProp::Pixi(PixiBox {
198 channels: 3,
199 depth: color_depth_bits,
200 })).ok_or(io::ErrorKind::InvalidInput)?;
201
202 let mut ipma = IpmaEntry {
203 item_id: color_image_id,
204 prop_ids: from_array([ispe_prop, av1c_color_prop | ESSENTIAL_BIT, pixi_3]),
205 };
206
207 if self.colr != ColrBox::default() {
209 let colr_color_prop = ipco.push(IpcoProp::Colr(self.colr)).ok_or(io::ErrorKind::InvalidInput)?;
210 ipma.prop_ids.push(colr_color_prop);
211 }
212 ipma_entries.push(ipma);
213
214 if let Some(exif_data) = self.exif.as_deref() {
215 image_items.push(InfeBox {
216 id: exif_id,
217 typ: FourCC(*b"Exif"),
218 name: "",
219 });
220
221 iloc_items.push(IlocItem {
222 id: exif_id,
223 extents: [IlocExtent { data: exif_data }],
224 });
225
226 irefs.push(IrefEntryBox {
227 from_id: exif_id,
228 to_id: color_image_id,
229 typ: FourCC(*b"cdsc"),
230 });
231 }
232
233 if let Some(alpha_data) = alpha_av1_data {
234 image_items.push(InfeBox {
235 id: alpha_image_id,
236 typ: FourCC(*b"av01"),
237 name: "",
238 });
239
240 irefs.push(IrefEntryBox {
241 from_id: alpha_image_id,
242 to_id: color_image_id,
243 typ: FourCC(*b"auxl"),
244 });
245
246 if self.premultiplied_alpha {
247 irefs.push(IrefEntryBox {
248 from_id: color_image_id,
249 to_id: alpha_image_id,
250 typ: FourCC(*b"prem"),
251 });
252 }
253
254 let av1c_alpha_prop = ipco.push(boxes::IpcoProp::Av1C(Av1CBox {
255 seq_profile: if alpha_depth_bits >= 12 { 2 } else { 0 },
256 seq_level_idx_0: 31,
257 seq_tier_0: false,
258 high_bitdepth: alpha_depth_bits >= 10,
259 twelve_bit: alpha_depth_bits >= 12,
260 monochrome: true,
261 chroma_subsampling_x: true,
262 chroma_subsampling_y: true,
263 chroma_sample_position: 0,
264 })).ok_or(io::ErrorKind::InvalidInput)?;
265
266 let pixi_1 = ipco.push(IpcoProp::Pixi(PixiBox {
268 channels: 1,
269 depth: alpha_depth_bits,
270 })).ok_or(io::ErrorKind::InvalidInput)?;
271
272 let auxc_prop = ipco.push(IpcoProp::AuxC(AuxCBox {
274 urn: "urn:mpeg:mpegB:cicp:systems:auxiliary:alpha",
275 })).ok_or(io::ErrorKind::InvalidInput)?;
276
277 ipma_entries.push(IpmaEntry {
278 item_id: alpha_image_id,
279 prop_ids: from_array([ispe_prop, av1c_alpha_prop | ESSENTIAL_BIT, auxc_prop, pixi_1]),
280 });
281
282 iloc_items.push(IlocItem {
285 id: alpha_image_id,
286 extents: [IlocExtent { data: alpha_data }],
287 });
288 }
289 iloc_items.push(IlocItem {
290 id: color_image_id,
291 extents: [IlocExtent { data: color_av1_data }],
292 });
293
294 Ok(AvifFile {
295 ftyp: FtypBox {
296 major_brand: FourCC(*b"avif"),
297 minor_version: 0,
298 compatible_brands: [FourCC(*b"mif1"), FourCC(*b"miaf")].into(),
299 },
300 meta: MetaBox {
301 hdlr: HdlrBox {},
302 iinf: IinfBox { items: image_items },
303 pitm: PitmBox(color_image_id),
304 iloc: IlocBox {
305 absolute_offset_start: None,
306 items: iloc_items,
307 },
308 iprp: IprpBox {
309 ipco,
310 ipma: IpmaBox { entries: ipma_entries },
313 },
314 iref: IrefBox { entries: irefs },
315 },
316 mdat: MdatBox,
319 })
320 }
321
322 #[must_use]
324 #[track_caller]
325 pub fn to_vec(&self, color_av1_data: &[u8], alpha_av1_data: Option<&[u8]>, width: u32, height: u32, depth_bits: u8) -> Vec<u8> {
326 let mut file = self.make_boxes(color_av1_data, alpha_av1_data, width, height, depth_bits).unwrap();
327 let mut out = Vec::new();
328 file.write_to_vec(&mut out).unwrap();
329 out
330 }
331
332 #[inline]
337 pub fn set_chroma_subsampling(&mut self, subsampled_xy: (bool, bool)) -> &mut Self {
338 self.chroma_subsampling = subsampled_xy;
339 self
340 }
341
342 #[inline]
345 pub fn set_monochrome(&mut self, monochrome: bool) -> &mut Self {
346 self.monochrome = monochrome;
347 self
348 }
349
350 #[inline]
352 pub fn set_exif(&mut self, exif: Vec<u8>) -> &mut Self {
353 self.exif = Some(exif);
354 self
355 }
356
357 #[inline]
361 pub fn set_seq_profile(&mut self, seq_profile: u8) -> &mut Self {
362 self.min_seq_profile = seq_profile;
363 self
364 }
365
366 #[inline]
367 pub fn set_width(&mut self, width: u32) -> &mut Self {
368 self.width = width;
369 self
370 }
371
372 #[inline]
373 pub fn set_height(&mut self, height: u32) -> &mut Self {
374 self.height = height;
375 self
376 }
377
378 #[inline]
380 pub fn set_bit_depth(&mut self, bit_depth: u8) -> &mut Self {
381 self.bit_depth = bit_depth;
382 self
383 }
384
385 #[inline]
398 pub fn set_premultiplied_alpha(&mut self, is_premultiplied: bool) -> &mut Self {
399 self.premultiplied_alpha = is_premultiplied;
400 self
401 }
402
403 #[doc(hidden)]
404 pub fn premultiplied_alpha(&mut self, is_premultiplied: bool) -> &mut Self {
405 self.set_premultiplied_alpha(is_premultiplied)
406 }
407}
408
409#[inline(always)]
410fn from_array<const L1: usize, const L2: usize, T: Copy>(array: [T; L1]) -> ArrayVec<T, L2> {
411 assert!(L1 <= L2);
412 let mut tmp = ArrayVec::new_const();
413 let _ = tmp.try_extend_from_slice(&array);
414 tmp
415}
416
417#[must_use]
419#[track_caller]
420pub fn serialize_to_vec(color_av1_data: &[u8], alpha_av1_data: Option<&[u8]>, width: u32, height: u32, depth_bits: u8) -> Vec<u8> {
421 Aviffy::new().to_vec(color_av1_data, alpha_av1_data, width, height, depth_bits)
422}
423
424#[test]
425fn test_roundtrip_parse_mp4() {
426 let test_img = b"av12356abc";
427 let avif = serialize_to_vec(test_img, None, 10, 20, 8);
428
429 let ctx = mp4parse::read_avif(&mut avif.as_slice(), mp4parse::ParseStrictness::Normal).unwrap();
430
431 assert_eq!(&test_img[..], ctx.primary_item_coded_data().unwrap());
432}
433
434#[test]
435fn test_roundtrip_parse_mp4_alpha() {
436 let test_img = b"av12356abc";
437 let test_a = b"alpha";
438 let avif = serialize_to_vec(test_img, Some(test_a), 10, 20, 8);
439
440 let ctx = mp4parse::read_avif(&mut avif.as_slice(), mp4parse::ParseStrictness::Normal).unwrap();
441
442 assert_eq!(&test_img[..], ctx.primary_item_coded_data().unwrap());
443 assert_eq!(&test_a[..], ctx.alpha_item_coded_data().unwrap());
444}
445
446#[test]
447fn test_roundtrip_parse_exif() {
448 let test_img = b"av12356abc";
449 let test_a = b"alpha";
450 let avif = Aviffy::new()
451 .set_exif(b"lol".to_vec())
452 .to_vec(test_img, Some(test_a), 10, 20, 8);
453
454 let ctx = mp4parse::read_avif(&mut avif.as_slice(), mp4parse::ParseStrictness::Normal).unwrap();
455
456 assert_eq!(&test_img[..], ctx.primary_item_coded_data().unwrap());
457 assert_eq!(&test_a[..], ctx.alpha_item_coded_data().unwrap());
458}
459
460#[test]
461fn test_roundtrip_parse_avif() {
462 let test_img = [1, 2, 3, 4, 5, 6];
463 let test_alpha = [77, 88, 99];
464 let avif = serialize_to_vec(&test_img, Some(&test_alpha), 10, 20, 8);
465
466 let ctx = avif_parse::read_avif(&mut avif.as_slice()).unwrap();
467
468 assert_eq!(&test_img[..], ctx.primary_item.as_slice());
469 assert_eq!(&test_alpha[..], ctx.alpha_item.as_deref().unwrap());
470}
471
472#[test]
473fn test_roundtrip_parse_avif_colr() {
474 let test_img = [1, 2, 3, 4, 5, 6];
475 let test_alpha = [77, 88, 99];
476 let avif = Aviffy::new()
477 .matrix_coefficients(constants::MatrixCoefficients::Bt709)
478 .to_vec(&test_img, Some(&test_alpha), 10, 20, 8);
479
480 let ctx = avif_parse::read_avif(&mut avif.as_slice()).unwrap();
481
482 assert_eq!(&test_img[..], ctx.primary_item.as_slice());
483 assert_eq!(&test_alpha[..], ctx.alpha_item.as_deref().unwrap());
484}
485
486#[test]
487fn premultiplied_flag() {
488 let test_img = [1,2,3,4];
489 let test_alpha = [55,66,77,88,99];
490 let avif = Aviffy::new().premultiplied_alpha(true).to_vec(&test_img, Some(&test_alpha), 5, 5, 8);
491
492 let ctx = avif_parse::read_avif(&mut avif.as_slice()).unwrap();
493
494 assert!(ctx.premultiplied_alpha);
495 assert_eq!(&test_img[..], ctx.primary_item.as_slice());
496 assert_eq!(&test_alpha[..], ctx.alpha_item.as_deref().unwrap());
497}
498
499#[test]
500fn size_required() {
501 assert!(Aviffy::new().set_bit_depth(10).write_slice(&mut vec![], &[], None).is_err());
502}
503
504#[test]
505fn depth_required() {
506 assert!(Aviffy::new().set_width(1).set_height(1).write_slice(&mut vec![], &[], None).is_err());
507}