1use crate::boxes::*;
7use arrayvec::ArrayVec;
8use std::io;
9
10pub struct GridImage {
15 color_config: Av1CBox,
16 alpha_config: Option<Av1CBox>,
17 depth_bits: u8,
18 colr: Option<ColrBox>,
19 premultiplied_alpha: bool,
20}
21
22impl Default for GridImage {
23 fn default() -> Self { Self::new() }
24}
25
26impl GridImage {
27 pub fn new() -> Self {
29 Self {
30 color_config: Av1CBox::default(),
31 alpha_config: None,
32 depth_bits: 8,
33 colr: None,
34 premultiplied_alpha: false,
35 }
36 }
37
38 pub fn set_color_config(&mut self, config: Av1CBox) -> &mut Self { self.color_config = config; self }
40 pub fn set_alpha_config(&mut self, config: Av1CBox) -> &mut Self { self.alpha_config = Some(config); self }
42 pub fn set_depth_bits(&mut self, depth: u8) -> &mut Self { self.depth_bits = depth; self }
44 pub fn set_colr(&mut self, colr: ColrBox) -> &mut Self { self.colr = Some(colr); self }
46 pub fn set_premultiplied_alpha(&mut self, premultiplied: bool) -> &mut Self { self.premultiplied_alpha = premultiplied; self }
48
49 #[allow(clippy::too_many_arguments)]
57 pub fn serialize(&self, rows: u8, columns: u8,
58 output_width: u32, output_height: u32,
59 tile_width: u32, tile_height: u32,
60 tile_data: &[&[u8]], alpha_data: Option<&[&[u8]]>) -> io::Result<Vec<u8>> {
61 let image = self;
62 let tile_count = rows as usize * columns as usize;
63 if tile_data.len() != tile_count {
64 return Err(io::Error::new(io::ErrorKind::InvalidInput,
65 format!("tile_data.len() ({}) != rows*columns ({})", tile_data.len(), tile_count)));
66 }
67 if let Some(alpha) = alpha_data
68 && alpha.len() != tile_count {
69 return Err(io::Error::new(io::ErrorKind::InvalidInput,
70 format!("alpha_data.len() ({}) != rows*columns ({})", alpha.len(), tile_count)));
71 }
72
73 let has_alpha = alpha_data.is_some() && image.alpha_config.is_some();
74
75 let color_grid_id: u16 = 1;
81 let alpha_grid_id: u16 = 2;
82 let color_tile_base: u16 = if has_alpha { 3 } else { 2 };
83 let alpha_tile_base: u16 = color_tile_base + tile_count as u16;
84
85 let grid_descriptor = make_grid_descriptor(
87 rows, columns,
88 output_width, output_height,
89 );
90
91 let alpha_grid_descriptor = if has_alpha {
92 Some(make_grid_descriptor(
93 rows, columns,
94 output_width, output_height,
95 ))
96 } else {
97 None
98 };
99
100 let mut image_items: Vec<InfeBox> = Vec::new();
102 let mut ipma_entries: Vec<IpmaEntry> = Vec::new();
103 let mut irefs: Vec<IrefEntryBox> = Vec::new();
104 let mut ipco = IpcoBox::new();
105 const ESSENTIAL_BIT: u8 = 0x80;
106
107 let ispe_output = ipco.push(IpcoProp::Ispe(IspeBox {
109 width: output_width,
110 height: output_height,
111 })).ok_or(io::ErrorKind::InvalidInput)?;
112
113 let ispe_tile = ipco.push(IpcoProp::Ispe(IspeBox {
114 width: tile_width,
115 height: tile_height,
116 })).ok_or(io::ErrorKind::InvalidInput)?;
117
118 let av1c_color = ipco.push(IpcoProp::Av1C(image.color_config)).ok_or(io::ErrorKind::InvalidInput)?;
119
120 let pixi_color = ipco.push(IpcoProp::Pixi(PixiBox {
121 channels: if image.color_config.monochrome { 1 } else { 3 },
122 depth: image.depth_bits,
123 })).ok_or(io::ErrorKind::InvalidInput)?;
124
125 let colr_prop = if let Some(ref colr) = image.colr {
127 if *colr != ColrBox::default() {
128 Some(ipco.push(IpcoProp::Colr(*colr)).ok_or(io::ErrorKind::InvalidInput)?)
129 } else {
130 None
131 }
132 } else {
133 None
134 };
135
136 let (av1c_alpha, pixi_alpha, auxc_alpha) = if has_alpha {
138 let ac = ipco.push(IpcoProp::Av1C(*image.alpha_config.as_ref().unwrap())).ok_or(io::ErrorKind::InvalidInput)?;
139 let pa = ipco.push(IpcoProp::Pixi(PixiBox {
140 channels: 1,
141 depth: image.depth_bits,
142 })).ok_or(io::ErrorKind::InvalidInput)?;
143 let auxc = ipco.push(IpcoProp::AuxC(AuxCBox {
144 urn: "urn:mpeg:mpegB:cicp:systems:auxiliary:alpha",
145 })).ok_or(io::ErrorKind::InvalidInput)?;
146 (Some(ac), Some(pa), Some(auxc))
147 } else {
148 (None, None, None)
149 };
150
151 image_items.push(InfeBox {
153 id: color_grid_id,
154 typ: FourCC(*b"grid"),
155 name: "",
156 content_type: "",
157 });
158
159 let mut grid_ipma = IpmaEntry {
161 item_id: color_grid_id,
162 prop_ids: {
163 let mut v = ArrayVec::new();
164 v.push(ispe_output);
165 v.push(pixi_color);
166 if let Some(colr_p) = colr_prop {
167 v.push(colr_p);
168 }
169 v
170 },
171 };
172 let _ = &mut grid_ipma; ipma_entries.push(grid_ipma);
174
175 if has_alpha {
177 image_items.push(InfeBox {
178 id: alpha_grid_id,
179 typ: FourCC(*b"grid"),
180 name: "",
181 content_type: "",
182 });
183
184 irefs.push(IrefEntryBox {
185 from_id: alpha_grid_id,
186 to_id: color_grid_id,
187 typ: FourCC(*b"auxl"),
188 });
189
190 if image.premultiplied_alpha {
191 irefs.push(IrefEntryBox {
192 from_id: color_grid_id,
193 to_id: alpha_grid_id,
194 typ: FourCC(*b"prem"),
195 });
196 }
197
198 ipma_entries.push(IpmaEntry {
199 item_id: alpha_grid_id,
200 prop_ids: {
201 let mut v = ArrayVec::new();
202 v.push(ispe_output);
203 v.push(pixi_alpha.unwrap());
204 v.push(auxc_alpha.unwrap());
205 v
206 },
207 });
208 }
209
210 for i in 0..tile_count {
212 let tile_id = color_tile_base + i as u16;
213
214 image_items.push(InfeBox {
215 id: tile_id,
216 typ: FourCC(*b"av01"),
217 name: "",
218 content_type: "",
219 });
220
221 irefs.push(IrefEntryBox {
222 from_id: color_grid_id,
223 to_id: tile_id,
224 typ: FourCC(*b"dimg"),
225 });
226
227 ipma_entries.push(IpmaEntry {
228 item_id: tile_id,
229 prop_ids: {
230 let mut v = ArrayVec::new();
231 v.push(ispe_tile);
232 v.push(av1c_color | ESSENTIAL_BIT);
233 v
234 },
235 });
236 }
237
238 if has_alpha {
240 for i in 0..tile_count {
241 let tile_id = alpha_tile_base + i as u16;
242
243 image_items.push(InfeBox {
244 id: tile_id,
245 typ: FourCC(*b"av01"),
246 name: "",
247 content_type: "",
248 });
249
250 irefs.push(IrefEntryBox {
251 from_id: alpha_grid_id,
252 to_id: tile_id,
253 typ: FourCC(*b"dimg"),
254 });
255
256 ipma_entries.push(IpmaEntry {
257 item_id: tile_id,
258 prop_ids: {
259 let mut v = ArrayVec::new();
260 v.push(ispe_tile);
261 v.push(av1c_alpha.unwrap() | ESSENTIAL_BIT);
262 v
263 },
264 });
265 }
266 }
267
268 let mut out = Vec::new();
273
274 write_ftyp(&mut out);
276
277 write_meta_grid(
279 &mut out,
280 &image_items,
281 &ipma_entries,
282 &ipco,
283 &irefs,
284 color_grid_id,
285 &grid_descriptor,
286 alpha_grid_descriptor.as_deref(),
287 alpha_grid_id,
288 tile_data,
289 alpha_data,
290 color_tile_base,
291 alpha_tile_base,
292 tile_count,
293 has_alpha,
294 );
295
296 let mdat_pos = begin_box(&mut out, b"mdat");
298 let mdat_data_start = out.len() as u32;
299
300 out.extend_from_slice(&grid_descriptor);
306 if let Some(ref agd) = alpha_grid_descriptor {
307 out.extend_from_slice(agd);
308 }
309
310 for tile in tile_data {
312 out.extend_from_slice(tile);
313 }
314
315 if let Some(alpha) = alpha_data {
317 for tile in alpha {
318 out.extend_from_slice(tile);
319 }
320 }
321
322 end_box(&mut out, mdat_pos);
323
324 patch_iloc_offsets(&mut out, mdat_data_start);
326
327 Ok(out)
328 }
329}
330
331const ILOC_PLACEHOLDER: u32 = 0xBAAD_F00D;
334
335fn make_grid_descriptor(rows: u8, columns: u8, width: u32, height: u32) -> Vec<u8> {
336 let mut desc = Vec::new();
337 desc.push(0); if width > u16::MAX as u32 || height > u16::MAX as u32 {
339 desc.push(1); } else {
341 desc.push(0); }
343 desc.push(rows.saturating_sub(1)); desc.push(columns.saturating_sub(1)); if width > u16::MAX as u32 || height > u16::MAX as u32 {
346 desc.extend_from_slice(&width.to_be_bytes());
347 desc.extend_from_slice(&height.to_be_bytes());
348 } else {
349 desc.extend_from_slice(&(width as u16).to_be_bytes());
350 desc.extend_from_slice(&(height as u16).to_be_bytes());
351 }
352 desc
353}
354
355fn write_u16(out: &mut Vec<u8>, v: u16) {
356 out.extend_from_slice(&v.to_be_bytes());
357}
358
359fn write_u32(out: &mut Vec<u8>, v: u32) {
360 out.extend_from_slice(&v.to_be_bytes());
361}
362
363fn begin_box(out: &mut Vec<u8>, box_type: &[u8; 4]) -> usize {
364 let pos = out.len();
365 write_u32(out, 0);
366 out.extend_from_slice(box_type);
367 pos
368}
369
370fn end_box(out: &mut [u8], pos: usize) {
371 let size = (out.len() - pos) as u32;
372 out[pos..pos + 4].copy_from_slice(&size.to_be_bytes());
373}
374
375fn write_fullbox(out: &mut Vec<u8>, version: u8, flags: u32) {
376 out.push(version);
377 out.push((flags >> 16) as u8);
378 out.push((flags >> 8) as u8);
379 out.push(flags as u8);
380}
381
382fn write_ftyp(out: &mut Vec<u8>) {
383 let pos = begin_box(out, b"ftyp");
384 out.extend_from_slice(b"avif");
385 write_u32(out, 0);
386 out.extend_from_slice(b"avif");
387 out.extend_from_slice(b"mif1");
388 out.extend_from_slice(b"miaf");
389 end_box(out, pos);
390}
391
392#[allow(clippy::too_many_arguments)]
393fn write_meta_grid(
394 out: &mut Vec<u8>,
395 image_items: &[InfeBox],
396 ipma_entries: &[IpmaEntry],
397 ipco: &IpcoBox,
398 irefs: &[IrefEntryBox],
399 primary_id: u16,
400 grid_descriptor: &[u8],
401 alpha_grid_descriptor: Option<&[u8]>,
402 alpha_grid_id: u16,
403 tile_data: &[&[u8]],
404 alpha_data: Option<&[&[u8]]>,
405 color_tile_base: u16,
406 alpha_tile_base: u16,
407 tile_count: usize,
408 has_alpha: bool,
409) {
410 let meta_pos = begin_box(out, b"meta");
411 write_fullbox(out, 0, 0);
412
413 {
415 let pos = begin_box(out, b"hdlr");
416 write_fullbox(out, 0, 0);
417 write_u32(out, 0);
418 out.extend_from_slice(b"pict");
419 out.extend_from_slice(&[0u8; 12]);
420 out.push(0);
421 end_box(out, pos);
422 }
423
424 {
426 let pos = begin_box(out, b"pitm");
427 write_fullbox(out, 0, 0);
428 write_u16(out, primary_id);
429 end_box(out, pos);
430 }
431
432 {
434 let pos = begin_box(out, b"iloc");
435 write_fullbox(out, 0, 0);
436 out.push(0x44); out.push(0x00);
438
439 let mut item_count: u16 = 1 + tile_count as u16; if has_alpha {
442 item_count += 1 + tile_count as u16; }
444 write_u16(out, item_count);
445
446 write_u16(out, primary_id);
448 write_u16(out, 0); write_u16(out, 1); write_u32(out, ILOC_PLACEHOLDER);
451 write_u32(out, grid_descriptor.len() as u32);
452
453 if has_alpha {
455 write_u16(out, alpha_grid_id);
456 write_u16(out, 0);
457 write_u16(out, 1);
458 write_u32(out, ILOC_PLACEHOLDER);
459 write_u32(out, alpha_grid_descriptor.map_or(0, |d| d.len() as u32));
460 }
461
462 for (i, tile) in tile_data.iter().enumerate() {
464 write_u16(out, color_tile_base + i as u16);
465 write_u16(out, 0);
466 write_u16(out, 1);
467 write_u32(out, ILOC_PLACEHOLDER);
468 write_u32(out, tile.len() as u32);
469 }
470
471 if let Some(alpha) = alpha_data {
473 for (i, tile) in alpha.iter().enumerate() {
474 write_u16(out, alpha_tile_base + i as u16);
475 write_u16(out, 0);
476 write_u16(out, 1);
477 write_u32(out, ILOC_PLACEHOLDER);
478 write_u32(out, tile.len() as u32);
479 }
480 }
481
482 end_box(out, pos);
483 }
484
485 {
487 let iinf_pos = begin_box(out, b"iinf");
488 write_fullbox(out, 0, 0);
489 write_u16(out, image_items.len() as u16);
490
491 for item in image_items {
492 let infe_pos = begin_box(out, b"infe");
493 write_fullbox(out, 2, 0);
494 write_u16(out, item.id);
495 write_u16(out, 0); out.extend_from_slice(&item.typ.0);
497 out.push(0); if !item.content_type.is_empty() {
499 out.extend_from_slice(item.content_type.as_bytes());
500 out.push(0);
501 }
502 end_box(out, infe_pos);
503 }
504
505 end_box(out, iinf_pos);
506 }
507
508 if !irefs.is_empty() {
510 let iref_pos = begin_box(out, b"iref");
511 write_fullbox(out, 0, 0);
512 for entry in irefs {
513 let entry_pos = begin_box(out, &entry.typ.0);
514 write_u16(out, entry.from_id);
515 write_u16(out, 1); write_u16(out, entry.to_id);
517 end_box(out, entry_pos);
518 }
519 end_box(out, iref_pos);
520 }
521
522 {
524 let iprp_pos = begin_box(out, b"iprp");
525
526 {
528 let mut tmp = Vec::new();
529 let mut w = crate::writer::Writer::new(&mut tmp);
530 let _ = ipco.write(&mut w);
531 drop(w);
532 out.extend_from_slice(&tmp);
533 }
534
535 {
537 let pos = begin_box(out, b"ipma");
538 write_fullbox(out, 0, 0);
539 write_u32(out, ipma_entries.len() as u32);
540 for entry in ipma_entries {
541 write_u16(out, entry.item_id);
542 out.push(entry.prop_ids.len() as u8);
543 for &p in &entry.prop_ids {
544 out.push(p);
545 }
546 }
547 end_box(out, pos);
548 }
549
550 end_box(out, iprp_pos);
551 }
552
553 end_box(out, meta_pos);
554}
555
556fn patch_iloc_offsets(out: &mut [u8], mdat_data_start: u32) {
559 let placeholder = ILOC_PLACEHOLDER.to_be_bytes();
560 let mut current_offset = mdat_data_start;
561 let mut i = 0;
562
563 while i + 4 <= out.len() {
564 if out[i..i + 4] == placeholder {
565 let len = if i + 8 <= out.len() {
567 u32::from_be_bytes([out[i + 4], out[i + 5], out[i + 6], out[i + 7]])
568 } else {
569 0
570 };
571
572 out[i..i + 4].copy_from_slice(¤t_offset.to_be_bytes());
573 current_offset += len;
574 i += 8; } else {
576 i += 1;
577 }
578 }
579}
580
581#[cfg(test)]
582mod tests {
583 use super::*;
584
585 fn basic_av1c() -> Av1CBox {
586 Av1CBox {
587 seq_profile: 0,
588 seq_level_idx_0: 4,
589 seq_tier_0: false,
590 high_bitdepth: false,
591 twelve_bit: false,
592 monochrome: false,
593 chroma_subsampling_x: true,
594 chroma_subsampling_y: true,
595 chroma_sample_position: 0,
596 }
597 }
598
599 fn mono_av1c() -> Av1CBox {
600 Av1CBox {
601 seq_profile: 0,
602 seq_level_idx_0: 4,
603 seq_tier_0: false,
604 high_bitdepth: false,
605 twelve_bit: false,
606 monochrome: true,
607 chroma_subsampling_x: true,
608 chroma_subsampling_y: true,
609 chroma_sample_position: 0,
610 }
611 }
612
613 #[test]
614 fn grid_2x2_roundtrip() {
615 let tiles: Vec<Vec<u8>> = (0..4).map(|i| vec![i as u8; 100]).collect();
616 let tile_refs: Vec<&[u8]> = tiles.iter().map(|t| t.as_slice()).collect();
617
618 let mut image = GridImage::new();
619 image.set_color_config(basic_av1c());
620
621 let avif = image.serialize(2, 2, 200, 200, 100, 100, &tile_refs, None).unwrap();
622
623 assert_eq!(&avif[4..8], b"ftyp");
625 assert_eq!(&avif[8..12], b"avif");
626
627 for tile in &tiles {
629 assert!(avif.windows(tile.len()).any(|w| w == tile.as_slice()),
630 "tile data should be in output");
631 }
632
633 let parser = zenavif_parse::AvifParser::from_bytes(&avif).unwrap();
635 let grid = parser.grid_config().expect("should have grid config");
636 assert_eq!(grid.rows, 2);
637 assert_eq!(grid.columns, 2);
638 assert_eq!(grid.output_width, 200);
639 assert_eq!(grid.output_height, 200);
640 assert_eq!(parser.grid_tile_count(), 4);
641 }
642
643 #[test]
644 fn grid_1x3_roundtrip() {
645 let tiles: Vec<Vec<u8>> = (0..3).map(|i| vec![(i + 10) as u8; 50]).collect();
646 let tile_refs: Vec<&[u8]> = tiles.iter().map(|t| t.as_slice()).collect();
647
648 let mut image = GridImage::new();
649 image.set_color_config(basic_av1c());
650
651 let avif = image.serialize(1, 3, 300, 100, 100, 100, &tile_refs, None).unwrap();
652 let parser = zenavif_parse::AvifParser::from_bytes(&avif).unwrap();
653 let grid = parser.grid_config().expect("grid config");
654 assert_eq!(grid.rows, 1);
655 assert_eq!(grid.columns, 3);
656 assert_eq!(parser.grid_tile_count(), 3);
657 }
658
659 #[test]
660 fn grid_with_alpha() {
661 let color_tiles: Vec<Vec<u8>> = (0..4).map(|i| vec![i as u8; 80]).collect();
662 let alpha_tiles: Vec<Vec<u8>> = (0..4).map(|i| vec![(i + 100) as u8; 40]).collect();
663 let color_refs: Vec<&[u8]> = color_tiles.iter().map(|t| t.as_slice()).collect();
664 let alpha_refs: Vec<&[u8]> = alpha_tiles.iter().map(|t| t.as_slice()).collect();
665
666 let mut image = GridImage::new();
667 image.set_color_config(basic_av1c());
668 image.set_alpha_config(mono_av1c());
669
670 let avif = image.serialize(2, 2, 128, 128, 64, 64, &color_refs, Some(&alpha_refs)).unwrap();
671
672 for tile in &color_tiles {
674 assert!(avif.windows(tile.len()).any(|w| w == tile.as_slice()));
675 }
676 for tile in &alpha_tiles {
677 assert!(avif.windows(tile.len()).any(|w| w == tile.as_slice()));
678 }
679
680 let parser = zenavif_parse::AvifParser::from_bytes(&avif).unwrap();
681 let grid = parser.grid_config().expect("grid config");
682 assert_eq!(grid.rows, 2);
683 assert_eq!(grid.columns, 2);
684 }
685
686 #[test]
687 fn grid_wrong_tile_count_errors() {
688 let tiles = [vec![0u8; 10]];
689 let tile_refs: Vec<&[u8]> = tiles.iter().map(|t| t.as_slice()).collect();
690
691 let image = GridImage::new();
692 assert!(image.serialize(2, 2, 200, 200, 100, 100, &tile_refs, None).is_err());
694 }
695}