1extern crate alloc;
6
7use alloc::vec::Vec;
8use core::fmt;
9
10use crate::j2c::capabilities::{CapabilityMarkerError, CapabilityMarkerState, Htj2kCapabilities};
11use crate::{MAX_J2K_IMAGE_DIMENSION, MAX_J2K_SPEC_COMPONENTS, MAX_J2K_TILE_COUNT};
12
13const MARKER_SOC: u8 = 0x4F;
14const MARKER_CAP: u8 = 0x50;
15const MARKER_SIZ: u8 = 0x51;
16const MARKER_COD: u8 = 0x52;
17const MARKER_CPF: u8 = 0x59;
18const MARKER_SOT: u8 = 0x90;
19const MARKER_SOD: u8 = 0x93;
20const MARKER_EOC: u8 = 0xD9;
21
22#[derive(Debug, Clone, PartialEq, Eq)]
24pub struct J2kCodestreamHeaderMetadata {
25 pub dimensions: (u32, u32),
27 pub components: u16,
29 pub bit_depth: u8,
31 pub tile_size: (u32, u32),
33 pub tile_count: (u32, u32),
35 pub component_info: Vec<J2kCodestreamComponentHeader>,
37 pub resolution_levels: u8,
39 pub has_mct: bool,
41 pub reversible: bool,
43 pub high_throughput: bool,
45}
46
47#[derive(Debug, Clone, Copy, PartialEq, Eq)]
49pub struct J2kCodestreamComponentHeader {
50 pub bit_depth: u8,
52 pub signed: bool,
54 pub x_rsiz: u8,
56 pub y_rsiz: u8,
58}
59
60#[derive(Debug, Clone, Copy, PartialEq, Eq)]
62#[non_exhaustive]
63pub enum J2kCodestreamHeaderError {
64 TooShort {
66 need: usize,
68 have: usize,
70 },
71 TruncatedAt {
73 offset: usize,
75 segment: &'static str,
77 },
78 InvalidMarker {
80 offset: usize,
82 marker: u8,
84 },
85 MissingRequiredMarker {
87 marker: &'static str,
89 },
90 InvalidSegment {
92 offset: usize,
94 what: &'static str,
96 },
97 InvalidSiz {
99 what: &'static str,
101 },
102 InvalidCod {
104 what: &'static str,
106 },
107 InvalidCap {
109 what: &'static str,
111 },
112 InvalidCpf {
114 what: &'static str,
116 },
117 Unsupported {
119 what: &'static str,
121 },
122 HostAllocationFailed {
124 bytes: usize,
126 },
127}
128
129impl fmt::Display for J2kCodestreamHeaderError {
130 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
131 match self {
132 Self::TooShort { need, have } => {
133 write!(f, "input too short: need {need} bytes, have {have}")
134 }
135 Self::TruncatedAt { offset, segment } => {
136 write!(f, "truncated {segment} at offset {offset}")
137 }
138 Self::InvalidMarker { offset, marker } => {
139 write!(
140 f,
141 "invalid codestream marker FF{marker:02X} at offset {offset}"
142 )
143 }
144 Self::MissingRequiredMarker { marker } => {
145 write!(f, "missing required codestream marker {marker}")
146 }
147 Self::InvalidSegment { what, .. } => write!(f, "invalid marker segment: {what}"),
148 Self::InvalidSiz { what } => write!(f, "invalid SIZ segment: {what}"),
149 Self::InvalidCod { what } => write!(f, "invalid COD segment: {what}"),
150 Self::InvalidCap { what } => write!(f, "invalid CAP segment: {what}"),
151 Self::InvalidCpf { what } => write!(f, "invalid CPF segment: {what}"),
152 Self::Unsupported { what } => write!(f, "unsupported codestream header: {what}"),
153 Self::HostAllocationFailed { bytes } => {
154 write!(f, "codestream header allocation failed for {bytes} bytes")
155 }
156 }
157 }
158}
159
160impl core::error::Error for J2kCodestreamHeaderError {}
161
162pub fn inspect_j2k_codestream_header(
172 input: &[u8],
173) -> Result<J2kCodestreamHeaderMetadata, J2kCodestreamHeaderError> {
174 inspect_main_header(input).map(|inspected| inspected.metadata)
175}
176
177pub fn inspect_htj2k_capabilities(
188 input: &[u8],
189) -> Result<Option<Htj2kCapabilities>, J2kCodestreamHeaderError> {
190 inspect_main_header(input).map(|inspected| inspected.htj2k)
191}
192
193struct InspectedMainHeader {
194 metadata: J2kCodestreamHeaderMetadata,
195 htj2k: Option<Htj2kCapabilities>,
196}
197
198fn inspect_main_header(input: &[u8]) -> Result<InspectedMainHeader, J2kCodestreamHeaderError> {
199 if input.len() < 2 {
200 return Err(J2kCodestreamHeaderError::TooShort {
201 need: 2,
202 have: input.len(),
203 });
204 }
205 if !looks_like_j2k_codestream(input) {
206 return Err(J2kCodestreamHeaderError::InvalidMarker {
207 offset: 0,
208 marker: input[1],
209 });
210 }
211
212 let mut offset = 2usize;
213 let mut siz = None;
214 let mut cod = None;
215 let mut capabilities = CapabilityMarkerState::default();
216 let mut terminated = false;
217
218 while offset < input.len() {
219 let marker = read_marker(input, &mut offset)?;
220 match marker {
221 MARKER_SOT | MARKER_SOD | MARKER_EOC => {
222 terminated = true;
223 break;
224 }
225 MARKER_SIZ => {
226 let payload = read_segment_payload(input, &mut offset, "SIZ")?;
227 siz = Some(parse_siz(payload)?);
228 }
229 MARKER_COD => {
230 let payload = read_segment_payload(input, &mut offset, "COD")?;
231 cod = Some(parse_cod(payload)?);
232 }
233 MARKER_CAP => {
234 let payload = read_segment_payload(input, &mut offset, "CAP")?;
235 capabilities
236 .record_cap(payload)
237 .map_err(map_capability_error)?;
238 }
239 MARKER_CPF => {
240 let payload = read_segment_payload(input, &mut offset, "CPF")?;
241 capabilities
242 .record_cpf(payload)
243 .map_err(map_capability_error)?;
244 }
245 0x30..=0x3F => {}
246 _ => {
247 let _ = read_segment_payload(input, &mut offset, "segment")?;
248 }
249 }
250 }
251
252 if !terminated {
253 return Err(J2kCodestreamHeaderError::TruncatedAt {
254 offset,
255 segment: "main header terminator",
256 });
257 }
258
259 let siz = siz.ok_or(J2kCodestreamHeaderError::MissingRequiredMarker { marker: "SIZ" })?;
260 capabilities
261 .validate_rsiz(siz.rsiz)
262 .map_err(map_capability_error)?;
263 let high_throughput_cap = capabilities.high_throughput();
264 let cod = cod
265 .ok_or(J2kCodestreamHeaderError::MissingRequiredMarker { marker: "COD" })?
266 .with_high_throughput_cap(high_throughput_cap);
267 let htj2k = capabilities
268 .to_public(
269 cod.quality_layers,
270 cod.default_ht_block_coding,
271 cod.default_mixed_block_coding,
272 )
273 .map_err(map_capability_error)?;
274
275 Ok(InspectedMainHeader {
276 metadata: J2kCodestreamHeaderMetadata {
277 dimensions: siz.dimensions,
278 components: siz.components,
279 bit_depth: siz.bit_depth,
280 tile_size: siz.tile_size,
281 tile_count: siz.tile_count,
282 component_info: siz.component_info,
283 resolution_levels: cod.resolution_levels,
284 has_mct: cod.has_mct,
285 reversible: cod.reversible,
286 high_throughput: cod.high_throughput,
287 },
288 htj2k,
289 })
290}
291
292fn map_capability_error(error: CapabilityMarkerError) -> J2kCodestreamHeaderError {
293 match error {
294 CapabilityMarkerError::Cap(what) => J2kCodestreamHeaderError::InvalidCap { what },
295 CapabilityMarkerError::Cpf(what) => J2kCodestreamHeaderError::InvalidCpf { what },
296 CapabilityMarkerError::Allocation { bytes } => {
297 J2kCodestreamHeaderError::HostAllocationFailed { bytes }
298 }
299 }
300}
301
302#[must_use]
304pub fn looks_like_j2k_codestream(input: &[u8]) -> bool {
305 input.len() >= 2 && input[0] == 0xFF && input[1] == MARKER_SOC
306}
307
308#[derive(Debug, Clone)]
309struct ParsedSiz {
310 rsiz: u16,
311 dimensions: (u32, u32),
312 components: u16,
313 bit_depth: u8,
314 tile_size: (u32, u32),
315 tile_count: (u32, u32),
316 component_info: Vec<J2kCodestreamComponentHeader>,
317}
318
319#[derive(Debug, Clone, Copy)]
320#[expect(
321 clippy::struct_excessive_bools,
322 reason = "the flags preserve independent COD and CAP facts for header inspection"
323)]
324struct ParsedCod {
325 resolution_levels: u8,
326 quality_layers: u8,
327 has_mct: bool,
328 reversible: bool,
329 high_throughput: bool,
330 default_ht_block_coding: bool,
331 default_mixed_block_coding: bool,
332}
333
334impl ParsedCod {
335 const fn with_high_throughput_cap(mut self, high_throughput_cap: bool) -> Self {
336 self.high_throughput |= high_throughput_cap;
337 self
338 }
339}
340
341fn read_marker(input: &[u8], offset: &mut usize) -> Result<u8, J2kCodestreamHeaderError> {
342 if *offset + 2 > input.len() {
343 return Err(J2kCodestreamHeaderError::TruncatedAt {
344 offset: *offset,
345 segment: "marker",
346 });
347 }
348 if input[*offset] != 0xFF {
349 return Err(J2kCodestreamHeaderError::InvalidMarker {
350 offset: *offset,
351 marker: input[*offset],
352 });
353 }
354 let marker = input[*offset + 1];
355 *offset += 2;
356 Ok(marker)
357}
358
359fn read_segment_payload<'a>(
360 input: &'a [u8],
361 offset: &mut usize,
362 segment: &'static str,
363) -> Result<&'a [u8], J2kCodestreamHeaderError> {
364 if *offset + 2 > input.len() {
365 return Err(J2kCodestreamHeaderError::TruncatedAt {
366 offset: *offset,
367 segment,
368 });
369 }
370 let length = u16::from_be_bytes([input[*offset], input[*offset + 1]]) as usize;
371 if length < 2 {
372 return Err(J2kCodestreamHeaderError::InvalidSegment {
373 offset: *offset,
374 what: "segment length smaller than header",
375 });
376 }
377 let start = *offset + 2;
378 let end = *offset + length;
379 if end > input.len() {
380 return Err(J2kCodestreamHeaderError::TruncatedAt {
381 offset: *offset,
382 segment,
383 });
384 }
385 *offset = end;
386 Ok(&input[start..end])
387}
388
389struct SizGeometry {
390 x_size: u32,
391 y_size: u32,
392 x_origin: u32,
393 y_origin: u32,
394 tile_width: u32,
395 tile_height: u32,
396 tile_x_origin: u32,
397 tile_y_origin: u32,
398}
399
400type SizDimensionsAndTileCount = ((u32, u32), (u32, u32));
401
402fn parse_siz(payload: &[u8]) -> Result<ParsedSiz, J2kCodestreamHeaderError> {
403 if payload.len() < 36 {
404 return Err(J2kCodestreamHeaderError::InvalidSiz {
405 what: "payload shorter than fixed SIZ header",
406 });
407 }
408 let geometry = SizGeometry {
409 x_size: read_u32(payload, 2),
410 y_size: read_u32(payload, 6),
411 x_origin: read_u32(payload, 10),
412 y_origin: read_u32(payload, 14),
413 tile_width: read_u32(payload, 18),
414 tile_height: read_u32(payload, 22),
415 tile_x_origin: read_u32(payload, 26),
416 tile_y_origin: read_u32(payload, 30),
417 };
418 let component_count = read_u16(payload, 34);
419
420 let required_len = usize::from(component_count)
421 .checked_mul(3)
422 .and_then(|component_bytes| 36usize.checked_add(component_bytes))
423 .ok_or(J2kCodestreamHeaderError::InvalidSiz {
424 what: "component descriptor length overflows",
425 })?;
426 if payload.len() < required_len {
427 return Err(J2kCodestreamHeaderError::InvalidSiz {
428 what: "component descriptors truncated",
429 });
430 }
431 let ((width, height), (tiles_x, tiles_y)) = validate_siz_geometry(&geometry, component_count)?;
432
433 let mut bit_depth = 0u8;
434 let component_len = usize::from(component_count);
435 let component_bytes = component_len
436 .checked_mul(core::mem::size_of::<J2kCodestreamComponentHeader>())
437 .ok_or(J2kCodestreamHeaderError::HostAllocationFailed { bytes: usize::MAX })?;
438 let mut component_info = Vec::new();
439 component_info
440 .try_reserve_exact(component_len)
441 .map_err(|_| J2kCodestreamHeaderError::HostAllocationFailed {
442 bytes: component_bytes,
443 })?;
444 for idx in 0..component_len {
445 let ssiz = payload[36 + idx * 3];
446 let precision = (ssiz & 0x7F) + 1;
447 let x_rsiz = payload[36 + idx * 3 + 1];
448 let y_rsiz = payload[36 + idx * 3 + 2];
449 if x_rsiz == 0 || y_rsiz == 0 {
450 return Err(J2kCodestreamHeaderError::InvalidSiz {
451 what: "component sampling factors must be non-zero",
452 });
453 }
454 bit_depth = bit_depth.max(precision);
455 component_info.push(J2kCodestreamComponentHeader {
456 bit_depth: precision,
457 signed: ssiz & 0x80 != 0,
458 x_rsiz,
459 y_rsiz,
460 });
461 }
462
463 Ok(ParsedSiz {
464 rsiz: read_u16(payload, 0),
465 dimensions: (width, height),
466 components: component_count,
467 bit_depth,
468 tile_size: (geometry.tile_width, geometry.tile_height),
469 tile_count: (tiles_x, tiles_y),
470 component_info,
471 })
472}
473
474fn validate_siz_geometry(
475 geometry: &SizGeometry,
476 component_count: u16,
477) -> Result<SizDimensionsAndTileCount, J2kCodestreamHeaderError> {
478 if component_count == 0 {
479 return Err(J2kCodestreamHeaderError::InvalidSiz {
480 what: "component count must be non-zero",
481 });
482 }
483 if component_count > MAX_J2K_SPEC_COMPONENTS {
484 return Err(J2kCodestreamHeaderError::InvalidSiz {
485 what: "component count exceeds JPEG 2000 limit",
486 });
487 }
488 if geometry.x_size <= geometry.x_origin || geometry.y_size <= geometry.y_origin {
489 return Err(J2kCodestreamHeaderError::InvalidSiz {
490 what: "image origin must be smaller than image size",
491 });
492 }
493 if geometry.tile_width == 0 || geometry.tile_height == 0 {
494 return Err(J2kCodestreamHeaderError::InvalidSiz {
495 what: "tile size must be non-zero",
496 });
497 }
498 if geometry.tile_x_origin >= geometry.x_size || geometry.tile_y_origin >= geometry.y_size {
499 return Err(J2kCodestreamHeaderError::InvalidSiz {
500 what: "tile origin must be within image bounds",
501 });
502 }
503 if geometry.tile_x_origin > geometry.x_origin || geometry.tile_y_origin > geometry.y_origin {
504 return Err(J2kCodestreamHeaderError::InvalidSiz {
505 what: "tile origin must not exceed image origin",
506 });
507 }
508 if geometry
509 .tile_x_origin
510 .checked_add(geometry.tile_width)
511 .ok_or(J2kCodestreamHeaderError::InvalidSiz {
512 what: "tile extent overflows",
513 })?
514 <= geometry.x_origin
515 || geometry
516 .tile_y_origin
517 .checked_add(geometry.tile_height)
518 .ok_or(J2kCodestreamHeaderError::InvalidSiz {
519 what: "tile extent overflows",
520 })?
521 <= geometry.y_origin
522 {
523 return Err(J2kCodestreamHeaderError::InvalidSiz {
524 what: "first tile must overlap image area",
525 });
526 }
527
528 let width = geometry.x_size - geometry.x_origin;
529 let height = geometry.y_size - geometry.y_origin;
530 if width > MAX_J2K_IMAGE_DIMENSION || height > MAX_J2K_IMAGE_DIMENSION {
531 return Err(J2kCodestreamHeaderError::InvalidSiz {
532 what: "image dimensions exceed JPEG 2000 inspect limit",
533 });
534 }
535 let tiles_x = (geometry.x_size - geometry.tile_x_origin).div_ceil(geometry.tile_width);
536 let tiles_y = (geometry.y_size - geometry.tile_y_origin).div_ceil(geometry.tile_height);
537 let tile_count = u64::from(tiles_x) * u64::from(tiles_y);
538 if tile_count > MAX_J2K_TILE_COUNT {
539 return Err(J2kCodestreamHeaderError::InvalidSiz {
540 what: "image has too many tiles",
541 });
542 }
543 Ok(((width, height), (tiles_x, tiles_y)))
544}
545
546fn parse_cod(payload: &[u8]) -> Result<ParsedCod, J2kCodestreamHeaderError> {
547 if payload.len() < 10 {
548 return Err(J2kCodestreamHeaderError::InvalidCod {
549 what: "payload shorter than fixed COD header",
550 });
551 }
552 let default_ht_block_coding = payload[8] & 0x40 != 0;
553 let default_mixed_block_coding = payload[8] & 0x80 != 0;
554 let quality_layers = read_u16(payload, 2);
555 if quality_layers == 0 || quality_layers > u16::from(crate::j2c::codestream::MAX_LAYER_COUNT) {
556 return Err(J2kCodestreamHeaderError::InvalidCod {
557 what: "quality-layer count is outside the supported range",
558 });
559 }
560 Ok(ParsedCod {
561 resolution_levels: payload[5].saturating_add(1),
562 quality_layers: u8::try_from(quality_layers).map_err(|_| {
563 J2kCodestreamHeaderError::InvalidCod {
564 what: "quality-layer count does not fit the inspection model",
565 }
566 })?,
567 has_mct: payload[4] != 0,
568 reversible: payload[9] == 1,
569 high_throughput: default_ht_block_coding,
570 default_ht_block_coding,
571 default_mixed_block_coding,
572 })
573}
574
575fn read_u16(bytes: &[u8], offset: usize) -> u16 {
576 u16::from_be_bytes([bytes[offset], bytes[offset + 1]])
577}
578
579fn read_u32(bytes: &[u8], offset: usize) -> u32 {
580 u32::from_be_bytes([
581 bytes[offset],
582 bytes[offset + 1],
583 bytes[offset + 2],
584 bytes[offset + 3],
585 ])
586}
587
588#[cfg(test)]
589mod tests {
590 use super::{inspect_j2k_codestream_header, J2kCodestreamHeaderError};
591 use alloc::{vec, vec::Vec};
592
593 #[test]
594 fn inspect_j2k_codestream_header_accepts_minimal_main_header() {
595 let header = inspect_j2k_codestream_header(&minimal_codestream()).expect("header");
596
597 assert_eq!(header.dimensions, (128, 64));
598 assert_eq!(header.components, 3);
599 assert_eq!(header.bit_depth, 8);
600 assert_eq!(header.tile_size, (64, 64));
601 assert_eq!(header.tile_count, (2, 1));
602 assert_eq!(header.resolution_levels, 6);
603 assert!(header.reversible);
604 }
605
606 #[test]
607 fn inspect_skips_parameterless_reserved_main_header_markers() {
608 let mut bytes = minimal_codestream();
609 let sot = bytes
610 .windows(2)
611 .position(|marker| marker == [0xFF, 0x90])
612 .expect("SOT marker");
613 bytes.splice(sot..sot, [0xFF, 0x30]);
614
615 let header = inspect_j2k_codestream_header(&bytes).expect("header with reserved marker");
616
617 assert_eq!(header.dimensions, (128, 64));
618 }
619
620 #[test]
621 fn inspect_rejects_zero_component_sampling() {
622 let mut bytes = minimal_codestream();
623 rewrite_component_sampling(&mut bytes, 0, 0, 1);
624
625 let err = inspect_j2k_codestream_header(&bytes).expect_err("zero sampling must reject");
626
627 assert!(matches!(err, J2kCodestreamHeaderError::InvalidSiz { .. }));
628 }
629
630 #[test]
631 fn inspect_rejects_oversized_dimensions() {
632 let mut bytes = minimal_codestream();
633 rewrite_siz_u32(&mut bytes, 2, 60_001);
634
635 let err = inspect_j2k_codestream_header(&bytes).expect_err("oversized width must reject");
636
637 assert!(matches!(err, J2kCodestreamHeaderError::InvalidSiz { .. }));
638 }
639
640 #[test]
641 fn inspect_rejects_tile_origin_after_image_origin() {
642 let mut bytes = minimal_codestream();
643 rewrite_siz_u32(&mut bytes, 26, 1);
644
645 let err = inspect_j2k_codestream_header(&bytes).expect_err("bad tile origin must reject");
646
647 assert!(matches!(err, J2kCodestreamHeaderError::InvalidSiz { .. }));
648 }
649
650 #[test]
651 fn inspect_rejects_tile_extent_overflow() {
652 let mut bytes = minimal_codestream();
653 rewrite_siz_u32(&mut bytes, 2, u32::MAX);
654 rewrite_siz_u32(&mut bytes, 10, u32::MAX - 1);
655 rewrite_siz_u32(&mut bytes, 18, 10);
656 rewrite_siz_u32(&mut bytes, 26, u32::MAX - 2);
657
658 let err = inspect_j2k_codestream_header(&bytes).expect_err("overflow must reject");
659
660 assert!(matches!(err, J2kCodestreamHeaderError::InvalidSiz { .. }));
661 }
662
663 #[test]
664 fn inspect_rejects_excessive_tile_count() {
665 let mut bytes = minimal_codestream();
666 rewrite_siz_u32(&mut bytes, 2, 257);
667 rewrite_siz_u32(&mut bytes, 6, 257);
668 rewrite_siz_u32(&mut bytes, 18, 1);
669 rewrite_siz_u32(&mut bytes, 22, 1);
670
671 let err = inspect_j2k_codestream_header(&bytes).expect_err("tile count must reject");
672
673 assert!(matches!(err, J2kCodestreamHeaderError::InvalidSiz { .. }));
674 }
675
676 #[test]
677 fn inspect_accepts_legal_38_bit_component_metadata() {
678 let mut bytes = minimal_codestream();
679 rewrite_component_descriptor(&mut bytes, 0, 0x25);
680 rewrite_component_descriptor(&mut bytes, 1, 0x80 | 0x25);
681
682 let header = inspect_j2k_codestream_header(&bytes).expect("legal 38-bit SIZ inspect");
683
684 assert_eq!(header.bit_depth, 38);
685 assert_eq!(header.component_info[0].bit_depth, 38);
686 assert!(!header.component_info[0].signed);
687 assert_eq!(header.component_info[1].bit_depth, 38);
688 assert!(header.component_info[1].signed);
689 }
690
691 fn minimal_codestream() -> Vec<u8> {
692 let mut bytes = vec![0xFF, 0x4F];
693 let mut siz = Vec::new();
694 push_u16(&mut siz, 0);
695 push_u32(&mut siz, 128);
696 push_u32(&mut siz, 64);
697 push_u32(&mut siz, 0);
698 push_u32(&mut siz, 0);
699 push_u32(&mut siz, 64);
700 push_u32(&mut siz, 64);
701 push_u32(&mut siz, 0);
702 push_u32(&mut siz, 0);
703 push_u16(&mut siz, 3);
704 for _ in 0..3 {
705 siz.extend_from_slice(&[0x07, 0x01, 0x01]);
706 }
707 bytes.extend_from_slice(&[0xFF, 0x51]);
708 push_u16(
709 &mut bytes,
710 u16::try_from(siz.len() + 2).expect("test SIZ segment length fits u16"),
711 );
712 bytes.extend_from_slice(&siz);
713
714 let cod = [0x00, 0x00, 0x00, 0x01, 0x01, 0x05, 0x04, 0x04, 0x00, 0x01];
715 bytes.extend_from_slice(&[0xFF, 0x52]);
716 push_u16(
717 &mut bytes,
718 u16::try_from(cod.len() + 2).expect("test COD segment length fits u16"),
719 );
720 bytes.extend_from_slice(&cod);
721 bytes.extend_from_slice(&[0xFF, 0x90, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
722 bytes
723 }
724
725 fn push_u16(out: &mut Vec<u8>, value: u16) {
726 out.extend_from_slice(&value.to_be_bytes());
727 }
728
729 fn push_u32(out: &mut Vec<u8>, value: u32) {
730 out.extend_from_slice(&value.to_be_bytes());
731 }
732
733 fn rewrite_siz_u32(bytes: &mut [u8], payload_offset: usize, value: u32) {
734 let siz = bytes
735 .windows(2)
736 .position(|marker| marker == [0xFF, 0x51])
737 .expect("SIZ marker");
738 let offset = siz + 4 + payload_offset;
739 bytes[offset..offset + 4].copy_from_slice(&value.to_be_bytes());
740 }
741
742 fn rewrite_component_sampling(bytes: &mut [u8], component: usize, x_rsiz: u8, y_rsiz: u8) {
743 let siz = bytes
744 .windows(2)
745 .position(|marker| marker == [0xFF, 0x51])
746 .expect("SIZ marker");
747 let component_offset = siz + 40 + component * 3;
748 bytes[component_offset + 1] = x_rsiz;
749 bytes[component_offset + 2] = y_rsiz;
750 }
751
752 fn rewrite_component_descriptor(bytes: &mut [u8], component: usize, descriptor: u8) {
753 let siz = bytes
754 .windows(2)
755 .position(|marker| marker == [0xFF, 0x51])
756 .expect("SIZ marker");
757 bytes[siz + 40 + component * 3] = descriptor;
758 }
759}