1mod boxes;
12pub mod constants;
13mod writer;
14
15use crate::boxes::*;
16use arrayvec::ArrayVec;
17use std::io;
18
19const EXIF_TIFF_OFFSET_ZERO: [u8; 4] = 0_u32.to_be_bytes();
20
21pub struct Aviffy {
25 premultiplied_alpha: bool,
26 colr: ColrBox,
27 clli: Option<ClliBox>,
28 mdcv: Option<MdcvBox>,
29 min_seq_profile: u8,
30 chroma_subsampling: (bool, bool),
31 monochrome: bool,
32 width: u32,
33 height: u32,
34 bit_depth: u8,
35 exif: Option<Vec<u8>>,
36}
37
38pub 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<()> {
56 Aviffy::new()
57 .set_width(width)
58 .set_height(height)
59 .set_bit_depth(depth_bits)
60 .write_slice(into_output, color_av1_data, alpha_av1_data)
61}
62
63impl Aviffy {
64 #[inline]
68 #[must_use]
69 pub fn new() -> Self {
70 Self {
71 premultiplied_alpha: false,
72 min_seq_profile: 1,
73 chroma_subsampling: (false, false),
74 monochrome: false,
75 width: 0,
76 height: 0,
77 bit_depth: 0,
78 colr: ColrBox::default(),
79 clli: None,
80 mdcv: None,
81 exif: None,
82 }
83 }
84
85 #[inline]
89 pub fn set_matrix_coefficients(&mut self, matrix_coefficients: constants::MatrixCoefficients) -> &mut Self {
90 self.colr.matrix_coefficients = matrix_coefficients;
91 self
92 }
93
94 #[doc(hidden)]
95 pub fn matrix_coefficients(&mut self, matrix_coefficients: constants::MatrixCoefficients) -> &mut Self {
96 self.set_matrix_coefficients(matrix_coefficients)
97 }
98
99 #[inline]
102 pub fn set_transfer_characteristics(&mut self, transfer_characteristics: constants::TransferCharacteristics) -> &mut Self {
103 self.colr.transfer_characteristics = transfer_characteristics;
104 self
105 }
106
107 #[doc(hidden)]
108 pub fn transfer_characteristics(&mut self, transfer_characteristics: constants::TransferCharacteristics) -> &mut Self {
109 self.set_transfer_characteristics(transfer_characteristics)
110 }
111
112 #[inline]
115 pub fn set_color_primaries(&mut self, color_primaries: constants::ColorPrimaries) -> &mut Self {
116 self.colr.color_primaries = color_primaries;
117 self
118 }
119
120 #[doc(hidden)]
121 pub fn color_primaries(&mut self, color_primaries: constants::ColorPrimaries) -> &mut Self {
122 self.set_color_primaries(color_primaries)
123 }
124
125 #[inline]
128 pub fn set_full_color_range(&mut self, full_range: bool) -> &mut Self {
129 self.colr.full_range_flag = full_range;
130 self
131 }
132
133 #[doc(hidden)]
134 pub fn full_color_range(&mut self, full_range: bool) -> &mut Self {
135 self.set_full_color_range(full_range)
136 }
137
138 #[inline]
145 pub fn set_content_light_level(&mut self, max_content_light_level: u16, max_pic_average_light_level: u16) -> &mut Self {
146 self.clli = Some(ClliBox {
147 max_content_light_level,
148 max_pic_average_light_level,
149 });
150 self
151 }
152
153 #[inline]
164 pub fn set_mastering_display(&mut self, primaries: [(u16, u16); 3], white_point: (u16, u16), max_luminance: u32, min_luminance: u32) -> &mut Self {
165 self.mdcv = Some(MdcvBox {
166 primaries,
167 white_point,
168 max_luminance,
169 min_luminance,
170 });
171 self
172 }
173
174 #[inline]
190 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<()> {
191 self.make_boxes(color_av1_data, alpha_av1_data, width, height, depth_bits)?.write(into_output)
192 }
193
194 #[inline]
196 pub fn write_slice<W: io::Write>(&self, into_output: W, color_av1_data: &[u8], alpha_av1_data: Option<&[u8]>) -> io::Result<()> {
197 self.make_boxes(color_av1_data, alpha_av1_data, self.width, self.height, self.bit_depth)?.write(into_output)
198 }
199
200 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>> {
201 if ![8, 10, 12].contains(&depth_bits) {
202 return Err(io::Error::new(io::ErrorKind::InvalidInput, "depth must be 8/10/12"));
203 }
204
205 let mut image_items = ArrayVec::new();
206 let mut iloc_items = ArrayVec::new();
207 let mut ipma_entries = ArrayVec::new();
208 let mut irefs = ArrayVec::new();
209 let mut ipco = IpcoBox::new();
210 let color_image_id = 1;
211 let alpha_image_id = 2;
212 let exif_id = 3;
213 const ESSENTIAL_BIT: u8 = 0x80;
214 let color_depth_bits = depth_bits;
215 let alpha_depth_bits = depth_bits; image_items.push(InfeBox {
218 id: color_image_id,
219 typ: FourCC(*b"av01"),
220 name: "",
221 });
222
223 let ispe_prop = ipco.push(IpcoProp::Ispe(IspeBox { width, height })).ok_or(io::ErrorKind::InvalidInput)?;
224
225 let av1c_color_prop = ipco.push(IpcoProp::Av1C(Av1CBox {
227 seq_profile: self.min_seq_profile.max(if color_depth_bits >= 12 { 2 } else { 0 }),
228 seq_level_idx_0: 31,
229 seq_tier_0: false,
230 high_bitdepth: color_depth_bits >= 10,
231 twelve_bit: color_depth_bits >= 12,
232 monochrome: self.monochrome,
233 chroma_subsampling_x: self.chroma_subsampling.0,
234 chroma_subsampling_y: self.chroma_subsampling.1,
235 chroma_sample_position: 0,
236 })).ok_or(io::ErrorKind::InvalidInput)?;
237
238 let pixi_3 = ipco.push(IpcoProp::Pixi(PixiBox {
240 channels: 3,
241 depth: color_depth_bits,
242 })).ok_or(io::ErrorKind::InvalidInput)?;
243
244 let mut ipma = IpmaEntry {
245 item_id: color_image_id,
246 prop_ids: from_array([ispe_prop, av1c_color_prop | ESSENTIAL_BIT, pixi_3]),
247 };
248
249 if self.colr != ColrBox::default() {
251 let colr_color_prop = ipco.push(IpcoProp::Colr(self.colr)).ok_or(io::ErrorKind::InvalidInput)?;
252 ipma.prop_ids.push(colr_color_prop);
253 }
254
255 if let Some(clli) = self.clli {
256 let clli_prop = ipco.push(IpcoProp::Clli(clli)).ok_or(io::ErrorKind::InvalidInput)?;
257 ipma.prop_ids.push(clli_prop);
258 }
259
260 if let Some(mdcv) = self.mdcv {
261 let mdcv_prop = ipco.push(IpcoProp::Mdcv(mdcv)).ok_or(io::ErrorKind::InvalidInput)?;
262 ipma.prop_ids.push(mdcv_prop);
263 }
264
265 ipma_entries.push(ipma);
266
267 if let Some(exif_data) = self.exif.as_deref() {
268 image_items.push(InfeBox {
269 id: exif_id,
270 typ: FourCC(*b"Exif"),
271 name: "",
272 });
273
274 iloc_items.push(IlocItem {
275 id: exif_id,
276 extents: exif_extents(exif_data),
277 });
278
279 irefs.push(IrefEntryBox {
280 from_id: exif_id,
281 to_id: color_image_id,
282 typ: FourCC(*b"cdsc"),
283 });
284 }
285
286 if let Some(alpha_data) = alpha_av1_data {
287 image_items.push(InfeBox {
288 id: alpha_image_id,
289 typ: FourCC(*b"av01"),
290 name: "",
291 });
292
293 irefs.push(IrefEntryBox {
294 from_id: alpha_image_id,
295 to_id: color_image_id,
296 typ: FourCC(*b"auxl"),
297 });
298
299 if self.premultiplied_alpha {
300 irefs.push(IrefEntryBox {
301 from_id: color_image_id,
302 to_id: alpha_image_id,
303 typ: FourCC(*b"prem"),
304 });
305 }
306
307 let av1c_alpha_prop = ipco.push(boxes::IpcoProp::Av1C(Av1CBox {
308 seq_profile: if alpha_depth_bits >= 12 { 2 } else { 0 },
309 seq_level_idx_0: 31,
310 seq_tier_0: false,
311 high_bitdepth: alpha_depth_bits >= 10,
312 twelve_bit: alpha_depth_bits >= 12,
313 monochrome: true,
314 chroma_subsampling_x: true,
315 chroma_subsampling_y: true,
316 chroma_sample_position: 0,
317 })).ok_or(io::ErrorKind::InvalidInput)?;
318
319 let pixi_1 = ipco.push(IpcoProp::Pixi(PixiBox {
321 channels: 1,
322 depth: alpha_depth_bits,
323 })).ok_or(io::ErrorKind::InvalidInput)?;
324
325 let auxc_prop = ipco.push(IpcoProp::AuxC(AuxCBox {
327 urn: "urn:mpeg:mpegB:cicp:systems:auxiliary:alpha",
328 })).ok_or(io::ErrorKind::InvalidInput)?;
329
330 ipma_entries.push(IpmaEntry {
331 item_id: alpha_image_id,
332 prop_ids: from_array([ispe_prop, av1c_alpha_prop | ESSENTIAL_BIT, auxc_prop, pixi_1]),
333 });
334
335 iloc_items.push(IlocItem {
338 id: alpha_image_id,
339 extents: from_array([IlocExtent { data: alpha_data }]),
340 });
341 }
342 iloc_items.push(IlocItem {
343 id: color_image_id,
344 extents: from_array([IlocExtent { data: color_av1_data }]),
345 });
346
347 Ok(AvifFile {
348 ftyp: FtypBox {
349 major_brand: FourCC(*b"avif"),
350 minor_version: 0,
351 compatible_brands: [FourCC(*b"mif1"), FourCC(*b"miaf")].into(),
352 },
353 meta: MetaBox {
354 hdlr: HdlrBox {},
355 iinf: IinfBox { items: image_items },
356 pitm: PitmBox(color_image_id),
357 iloc: IlocBox {
358 absolute_offset_start: None,
359 items: iloc_items,
360 },
361 iprp: IprpBox {
362 ipco,
363 ipma: IpmaBox { entries: ipma_entries },
366 },
367 iref: IrefBox { entries: irefs },
368 },
369 mdat: MdatBox,
372 })
373 }
374
375 #[must_use]
377 #[track_caller]
378 pub fn to_vec(&self, color_av1_data: &[u8], alpha_av1_data: Option<&[u8]>, width: u32, height: u32, depth_bits: u8) -> Vec<u8> {
379 let mut file = self.make_boxes(color_av1_data, alpha_av1_data, width, height, depth_bits).unwrap();
380 let mut out = Vec::new();
381 file.write_to_vec(&mut out).unwrap();
382 out
383 }
384
385 #[inline]
390 pub fn set_chroma_subsampling(&mut self, subsampled_xy: (bool, bool)) -> &mut Self {
391 self.chroma_subsampling = subsampled_xy;
392 self
393 }
394
395 #[inline]
398 pub fn set_monochrome(&mut self, monochrome: bool) -> &mut Self {
399 self.monochrome = monochrome;
400 self
401 }
402
403 #[inline]
408 pub fn set_exif(&mut self, exif: Vec<u8>) -> &mut Self {
409 self.exif = Some(exif);
410 self
411 }
412
413 #[inline]
417 pub fn set_seq_profile(&mut self, seq_profile: u8) -> &mut Self {
418 self.min_seq_profile = seq_profile;
419 self
420 }
421
422 #[inline]
423 pub fn set_width(&mut self, width: u32) -> &mut Self {
424 self.width = width;
425 self
426 }
427
428 #[inline]
429 pub fn set_height(&mut self, height: u32) -> &mut Self {
430 self.height = height;
431 self
432 }
433
434 #[inline]
436 pub fn set_bit_depth(&mut self, bit_depth: u8) -> &mut Self {
437 self.bit_depth = bit_depth;
438 self
439 }
440
441 #[inline]
454 pub fn set_premultiplied_alpha(&mut self, is_premultiplied: bool) -> &mut Self {
455 self.premultiplied_alpha = is_premultiplied;
456 self
457 }
458
459 #[doc(hidden)]
460 pub fn premultiplied_alpha(&mut self, is_premultiplied: bool) -> &mut Self {
461 self.set_premultiplied_alpha(is_premultiplied)
462 }
463}
464
465fn exif_extents(exif: &[u8]) -> ArrayVec<IlocExtent<'_>, 2> {
466 if looks_like_heif_exif_item(exif) {
467 return from_array([IlocExtent { data: exif }]);
468 }
469
470 from_array([
471 IlocExtent {
472 data: &EXIF_TIFF_OFFSET_ZERO,
473 },
474 IlocExtent { data: exif },
475 ])
476}
477
478fn looks_like_heif_exif_item(exif: &[u8]) -> bool {
479 let Some(offset_bytes) = exif.get(..4) else {
480 return false;
481 };
482 let tiff_offset = u32::from_be_bytes(offset_bytes.try_into().unwrap()) as usize;
483 let Some(tiff_start) = 4_usize.checked_add(tiff_offset) else {
484 return false;
485 };
486
487 exif.get(tiff_start..).map_or(false, looks_like_tiff_header)
488}
489
490fn looks_like_tiff_header(data: &[u8]) -> bool {
491 data.starts_with(b"II\x2a\0") || data.starts_with(b"MM\0\x2a")
492}
493
494#[inline(always)]
495fn from_array<const L1: usize, const L2: usize, T: Copy>(array: [T; L1]) -> ArrayVec<T, L2> {
496 assert!(L1 <= L2);
497 let mut tmp = ArrayVec::new_const();
498 let _ = tmp.try_extend_from_slice(&array);
499 tmp
500}
501
502#[must_use]
504#[track_caller]
505pub fn serialize_to_vec(color_av1_data: &[u8], alpha_av1_data: Option<&[u8]>, width: u32, height: u32, depth_bits: u8) -> Vec<u8> {
506 Aviffy::new().to_vec(color_av1_data, alpha_av1_data, width, height, depth_bits)
507}
508
509#[test]
510fn test_roundtrip_parse_mp4() {
511 let test_img = b"av12356abc";
512 let avif = serialize_to_vec(test_img, None, 10, 20, 8);
513
514 let ctx = mp4parse::read_avif(&mut avif.as_slice(), mp4parse::ParseStrictness::Normal).unwrap();
515
516 assert_eq!(&test_img[..], ctx.primary_item_coded_data().unwrap());
517}
518
519#[test]
520fn test_roundtrip_parse_mp4_alpha() {
521 let test_img = b"av12356abc";
522 let test_a = b"alpha";
523 let avif = serialize_to_vec(test_img, Some(test_a), 10, 20, 8);
524
525 let ctx = mp4parse::read_avif(&mut avif.as_slice(), mp4parse::ParseStrictness::Normal).unwrap();
526
527 assert_eq!(&test_img[..], ctx.primary_item_coded_data().unwrap());
528 assert_eq!(&test_a[..], ctx.alpha_item_coded_data().unwrap());
529}
530
531#[test]
532fn test_roundtrip_parse_exif() {
533 let test_img = b"av12356abc";
534 let test_a = b"alpha";
535 let avif = Aviffy::new()
536 .set_exif(test_tiff_exif())
537 .to_vec(test_img, Some(test_a), 10, 20, 8);
538
539 let ctx = mp4parse::read_avif(&mut avif.as_slice(), mp4parse::ParseStrictness::Normal).unwrap();
540
541 assert_eq!(&test_img[..], ctx.primary_item_coded_data().unwrap());
542 assert_eq!(&test_a[..], ctx.alpha_item_coded_data().unwrap());
543}
544
545#[test]
546fn set_exif_stores_input_bytes_unchanged() {
547 let tiff_exif = test_tiff_exif();
548 let mut aviffy = Aviffy::new();
549
550 aviffy.set_exif(tiff_exif.clone());
551
552 assert_eq!(Some(tiff_exif), aviffy.exif);
553}
554
555#[test]
556fn raw_tiff_exif_uses_header_extent() {
557 let tiff_exif = test_tiff_exif();
558 let extents = exif_extents(&tiff_exif);
559
560 assert_eq!(2, extents.len());
561 assert_eq!(&EXIF_TIFF_OFFSET_ZERO[..], extents[0].data);
562 assert_eq!(tiff_exif.as_slice(), extents[1].data);
563}
564
565#[test]
566fn heif_exif_item_uses_single_extent() {
567 let expected = test_heif_exif(&test_tiff_exif());
568 let extents = exif_extents(&expected);
569
570 assert_eq!(1, extents.len());
571 assert_eq!(expected.as_slice(), extents[0].data);
572}
573
574#[test]
575fn heif_exif_item_with_nonzero_offset_uses_single_extent() {
576 let mut expected = 2_u32.to_be_bytes().to_vec();
577 expected.extend_from_slice(&[0, 0]);
578 expected.extend_from_slice(&test_tiff_exif());
579 let extents = exif_extents(&expected);
580
581 assert_eq!(1, extents.len());
582 assert_eq!(expected.as_slice(), extents[0].data);
583}
584
585#[test]
586fn writes_heif_exif_header_before_raw_tiff_exif() {
587 let tiff_exif = test_tiff_exif();
588 let expected = test_heif_exif(&tiff_exif);
589 let avif = Aviffy::new().set_exif(tiff_exif).to_vec(b"av12356abc", None, 10, 20, 8);
590
591 assert!(avif.windows(expected.len()).any(|window| window == expected));
592}
593
594#[test]
595fn test_roundtrip_parse_avif() {
596 let test_img = [1, 2, 3, 4, 5, 6];
597 let test_alpha = [77, 88, 99];
598 let avif = serialize_to_vec(&test_img, Some(&test_alpha), 10, 20, 8);
599
600 let ctx = avif_parse::read_avif(&mut avif.as_slice()).unwrap();
601
602 assert_eq!(&test_img[..], ctx.primary_item.as_slice());
603 assert_eq!(&test_alpha[..], ctx.alpha_item.as_deref().unwrap());
604}
605
606#[test]
607fn test_roundtrip_parse_avif_colr() {
608 let test_img = [1, 2, 3, 4, 5, 6];
609 let test_alpha = [77, 88, 99];
610 let avif = Aviffy::new()
611 .matrix_coefficients(constants::MatrixCoefficients::Bt709)
612 .to_vec(&test_img, Some(&test_alpha), 10, 20, 8);
613
614 let ctx = avif_parse::read_avif(&mut avif.as_slice()).unwrap();
615
616 assert_eq!(&test_img[..], ctx.primary_item.as_slice());
617 assert_eq!(&test_alpha[..], ctx.alpha_item.as_deref().unwrap());
618}
619
620#[test]
621fn premultiplied_flag() {
622 let test_img = [1,2,3,4];
623 let test_alpha = [55,66,77,88,99];
624 let avif = Aviffy::new().premultiplied_alpha(true).to_vec(&test_img, Some(&test_alpha), 5, 5, 8);
625
626 let ctx = avif_parse::read_avif(&mut avif.as_slice()).unwrap();
627
628 assert!(ctx.premultiplied_alpha);
629 assert_eq!(&test_img[..], ctx.primary_item.as_slice());
630 assert_eq!(&test_alpha[..], ctx.alpha_item.as_deref().unwrap());
631}
632
633#[test]
634fn size_required() {
635 assert!(Aviffy::new().set_bit_depth(10).write_slice(&mut vec![], &[], None).is_err());
636}
637
638#[test]
639fn depth_required() {
640 assert!(Aviffy::new().set_width(1).set_height(1).write_slice(&mut vec![], &[], None).is_err());
641}
642
643#[test]
644fn clli_roundtrip() {
645 let test_img = [1, 2, 3, 4, 5, 6];
646 let avif = Aviffy::new()
647 .set_content_light_level(1000, 400)
648 .to_vec(&test_img, None, 10, 20, 8);
649
650 let parser = avif_parse::read_avif(&mut avif.as_slice()).unwrap();
651 let cll = parser.content_light_level.expect("clli box should be present");
652 assert_eq!(cll.max_content_light_level, 1000);
653 assert_eq!(cll.max_pic_average_light_level, 400);
654}
655
656#[test]
657fn mdcv_roundtrip() {
658 let test_img = [1, 2, 3, 4, 5, 6];
659 let primaries = [
661 (8500, 39850), (6550, 2300), (35400, 14600), ];
665 let white_point = (15635, 16450); let max_luminance = 10_000_000; let min_luminance = 1; let avif = Aviffy::new()
670 .set_mastering_display(primaries, white_point, max_luminance, min_luminance)
671 .to_vec(&test_img, None, 10, 20, 8);
672
673 let parser = avif_parse::read_avif(&mut avif.as_slice()).unwrap();
674 let mdcv = parser.mastering_display.expect("mdcv box should be present");
675 assert_eq!(mdcv.primaries, primaries);
676 assert_eq!(mdcv.white_point, white_point);
677 assert_eq!(mdcv.max_luminance, max_luminance);
678 assert_eq!(mdcv.min_luminance, min_luminance);
679}
680
681#[test]
682fn hdr10_full_metadata() {
683 let test_img = [1, 2, 3, 4, 5, 6];
684 let test_alpha = [77, 88, 99];
685 let primaries = [
686 (8500, 39850),
687 (6550, 2300),
688 (35400, 14600),
689 ];
690 let white_point = (15635, 16450);
691
692 let avif = Aviffy::new()
693 .set_transfer_characteristics(constants::TransferCharacteristics::Smpte2084)
694 .set_color_primaries(constants::ColorPrimaries::Bt2020)
695 .set_matrix_coefficients(constants::MatrixCoefficients::Bt2020Ncl)
696 .set_content_light_level(4000, 1000)
697 .set_mastering_display(primaries, white_point, 40_000_000, 50)
698 .to_vec(&test_img, Some(&test_alpha), 10, 20, 10);
699
700 let parser = avif_parse::read_avif(&mut avif.as_slice()).unwrap();
701
702 let cll = parser.content_light_level.expect("clli box should be present");
704 assert_eq!(cll.max_content_light_level, 4000);
705 assert_eq!(cll.max_pic_average_light_level, 1000);
706
707 let mdcv = parser.mastering_display.expect("mdcv box should be present");
709 assert_eq!(mdcv.primaries, primaries);
710 assert_eq!(mdcv.white_point, white_point);
711 assert_eq!(mdcv.max_luminance, 40_000_000);
712 assert_eq!(mdcv.min_luminance, 50);
713
714 let ctx = avif_parse::read_avif(&mut avif.as_slice()).unwrap();
716 assert_eq!(ctx.primary_item.as_slice(), &test_img[..]);
717 assert_eq!(ctx.alpha_item.as_deref().unwrap(), &test_alpha[..]);
718}
719
720#[test]
721fn no_hdr_metadata_by_default() {
722 let test_img = [1, 2, 3, 4, 5, 6];
723 let avif = serialize_to_vec(&test_img, None, 10, 20, 8);
724
725 let parser = avif_parse::read_avif(&mut avif.as_slice()).unwrap();
726 assert!(parser.content_light_level.is_none());
727 assert!(parser.mastering_display.is_none());
728}
729
730#[cfg(test)]
731fn test_heif_exif(tiff_exif: &[u8]) -> Vec<u8> {
732 let mut heif_exif = 0_u32.to_be_bytes().to_vec();
733 heif_exif.extend_from_slice(tiff_exif);
734 heif_exif
735}
736
737#[cfg(test)]
738fn test_tiff_exif() -> Vec<u8> {
739 let make = b"avif-serialize\0";
740 let ifd0_offset = 8_u32;
741 let ifd0_entry_count = 1_u16;
742 let make_value_offset = 8 + 2 + 12 + 4;
743
744 let mut exif = Vec::new();
745 exif.extend_from_slice(b"II");
746 exif.extend_from_slice(&42_u16.to_le_bytes());
747 exif.extend_from_slice(&ifd0_offset.to_le_bytes());
748
749 exif.extend_from_slice(&ifd0_entry_count.to_le_bytes());
750 exif.extend_from_slice(&0x010f_u16.to_le_bytes());
751 exif.extend_from_slice(&2_u16.to_le_bytes());
752 exif.extend_from_slice(&(make.len() as u32).to_le_bytes());
753 exif.extend_from_slice(&(make_value_offset as u32).to_le_bytes());
754 exif.extend_from_slice(&0_u32.to_le_bytes());
755 exif.extend_from_slice(make);
756
757 exif
758}