1extern crate alloc;
6
7use alloc::vec::Vec;
8use core::fmt;
9
10use crate::j2c::capabilities::{CapabilityMarkerError, CapabilityMarkerState, Htj2kCapabilities};
11use crate::{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 u64::from(geometry.tile_x_origin) + u64::from(geometry.tile_width)
509 <= u64::from(geometry.x_origin)
510 || u64::from(geometry.tile_y_origin) + u64::from(geometry.tile_height)
511 <= u64::from(geometry.y_origin)
512 {
513 return Err(J2kCodestreamHeaderError::InvalidSiz {
514 what: "first tile must overlap image area",
515 });
516 }
517
518 let width = geometry.x_size - geometry.x_origin;
519 let height = geometry.y_size - geometry.y_origin;
520 let tiles_x = (geometry.x_size - geometry.tile_x_origin).div_ceil(geometry.tile_width);
521 let tiles_y = (geometry.y_size - geometry.tile_y_origin).div_ceil(geometry.tile_height);
522 let tile_count = u64::from(tiles_x) * u64::from(tiles_y);
523 if tile_count > MAX_J2K_TILE_COUNT {
524 return Err(J2kCodestreamHeaderError::InvalidSiz {
525 what: "image has too many tiles",
526 });
527 }
528 Ok(((width, height), (tiles_x, tiles_y)))
529}
530
531fn parse_cod(payload: &[u8]) -> Result<ParsedCod, J2kCodestreamHeaderError> {
532 if payload.len() < 10 {
533 return Err(J2kCodestreamHeaderError::InvalidCod {
534 what: "payload shorter than fixed COD header",
535 });
536 }
537 let default_ht_block_coding = payload[8] & 0x40 != 0;
538 let default_mixed_block_coding = payload[8] & 0x80 != 0;
539 let quality_layers = read_u16(payload, 2);
540 if quality_layers == 0 || quality_layers > u16::from(crate::j2c::codestream::MAX_LAYER_COUNT) {
541 return Err(J2kCodestreamHeaderError::InvalidCod {
542 what: "quality-layer count is outside the supported range",
543 });
544 }
545 Ok(ParsedCod {
546 resolution_levels: payload[5].saturating_add(1),
547 quality_layers: u8::try_from(quality_layers).map_err(|_| {
548 J2kCodestreamHeaderError::InvalidCod {
549 what: "quality-layer count does not fit the inspection model",
550 }
551 })?,
552 has_mct: payload[4] != 0,
553 reversible: payload[9] == 1,
554 high_throughput: default_ht_block_coding,
555 default_ht_block_coding,
556 default_mixed_block_coding,
557 })
558}
559
560fn read_u16(bytes: &[u8], offset: usize) -> u16 {
561 u16::from_be_bytes([bytes[offset], bytes[offset + 1]])
562}
563
564fn read_u32(bytes: &[u8], offset: usize) -> u32 {
565 u32::from_be_bytes([
566 bytes[offset],
567 bytes[offset + 1],
568 bytes[offset + 2],
569 bytes[offset + 3],
570 ])
571}
572
573#[cfg(test)]
574mod tests {
575 use super::{inspect_j2k_codestream_header, J2kCodestreamHeaderError};
576 use alloc::{vec, vec::Vec};
577
578 #[test]
579 fn inspect_j2k_codestream_header_accepts_minimal_main_header() {
580 let header = inspect_j2k_codestream_header(&minimal_codestream()).expect("header");
581
582 assert_eq!(header.dimensions, (128, 64));
583 assert_eq!(header.components, 3);
584 assert_eq!(header.bit_depth, 8);
585 assert_eq!(header.tile_size, (64, 64));
586 assert_eq!(header.tile_count, (2, 1));
587 assert_eq!(header.resolution_levels, 6);
588 assert!(header.reversible);
589 }
590
591 #[test]
592 fn inspect_skips_parameterless_reserved_main_header_markers() {
593 let mut bytes = minimal_codestream();
594 let sot = bytes
595 .windows(2)
596 .position(|marker| marker == [0xFF, 0x90])
597 .expect("SOT marker");
598 bytes.splice(sot..sot, [0xFF, 0x30]);
599
600 let header = inspect_j2k_codestream_header(&bytes).expect("header with reserved marker");
601
602 assert_eq!(header.dimensions, (128, 64));
603 }
604
605 #[test]
606 fn inspect_rejects_zero_component_sampling() {
607 let mut bytes = minimal_codestream();
608 rewrite_component_sampling(&mut bytes, 0, 0, 1);
609
610 let err = inspect_j2k_codestream_header(&bytes).expect_err("zero sampling must reject");
611
612 assert!(matches!(err, J2kCodestreamHeaderError::InvalidSiz { .. }));
613 }
614
615 #[test]
616 fn inspect_accepts_large_dimensions() {
617 let mut bytes = minimal_codestream();
618 rewrite_siz_u32(&mut bytes, 2, 60_001);
619
620 let header = inspect_j2k_codestream_header(&bytes).expect("large width is valid");
621 assert_eq!(header.dimensions, (60_001, 64));
622 }
623
624 #[test]
625 fn inspect_rejects_tile_origin_after_image_origin() {
626 let mut bytes = minimal_codestream();
627 rewrite_siz_u32(&mut bytes, 26, 1);
628
629 let err = inspect_j2k_codestream_header(&bytes).expect_err("bad tile origin must reject");
630
631 assert!(matches!(err, J2kCodestreamHeaderError::InvalidSiz { .. }));
632 }
633
634 #[test]
635 fn inspect_accepts_tile_extending_beyond_reference_grid() {
636 let mut bytes = minimal_codestream();
637 rewrite_siz_u32(&mut bytes, 2, u32::MAX);
638 rewrite_siz_u32(&mut bytes, 10, u32::MAX - 1);
639 rewrite_siz_u32(&mut bytes, 18, 10);
640 rewrite_siz_u32(&mut bytes, 26, u32::MAX - 2);
641
642 let header = inspect_j2k_codestream_header(&bytes).expect("tile is clipped to image");
643 assert_eq!(header.dimensions, (1, 64));
644 assert_eq!(header.tile_count, (1, 1));
645 }
646
647 #[test]
648 fn inspect_rejects_excessive_tile_count() {
649 let mut bytes = minimal_codestream();
650 rewrite_siz_u32(&mut bytes, 2, 257);
651 rewrite_siz_u32(&mut bytes, 6, 257);
652 rewrite_siz_u32(&mut bytes, 18, 1);
653 rewrite_siz_u32(&mut bytes, 22, 1);
654
655 let err = inspect_j2k_codestream_header(&bytes).expect_err("tile count must reject");
656
657 assert!(matches!(err, J2kCodestreamHeaderError::InvalidSiz { .. }));
658 }
659
660 #[test]
661 fn inspect_accepts_legal_38_bit_component_metadata() {
662 let mut bytes = minimal_codestream();
663 rewrite_component_descriptor(&mut bytes, 0, 0x25);
664 rewrite_component_descriptor(&mut bytes, 1, 0x80 | 0x25);
665
666 let header = inspect_j2k_codestream_header(&bytes).expect("legal 38-bit SIZ inspect");
667
668 assert_eq!(header.bit_depth, 38);
669 assert_eq!(header.component_info[0].bit_depth, 38);
670 assert!(!header.component_info[0].signed);
671 assert_eq!(header.component_info[1].bit_depth, 38);
672 assert!(header.component_info[1].signed);
673 }
674
675 fn minimal_codestream() -> Vec<u8> {
676 let mut bytes = vec![0xFF, 0x4F];
677 let mut siz = Vec::new();
678 push_u16(&mut siz, 0);
679 push_u32(&mut siz, 128);
680 push_u32(&mut siz, 64);
681 push_u32(&mut siz, 0);
682 push_u32(&mut siz, 0);
683 push_u32(&mut siz, 64);
684 push_u32(&mut siz, 64);
685 push_u32(&mut siz, 0);
686 push_u32(&mut siz, 0);
687 push_u16(&mut siz, 3);
688 for _ in 0..3 {
689 siz.extend_from_slice(&[0x07, 0x01, 0x01]);
690 }
691 bytes.extend_from_slice(&[0xFF, 0x51]);
692 push_u16(
693 &mut bytes,
694 u16::try_from(siz.len() + 2).expect("test SIZ segment length fits u16"),
695 );
696 bytes.extend_from_slice(&siz);
697
698 let cod = [0x00, 0x00, 0x00, 0x01, 0x01, 0x05, 0x04, 0x04, 0x00, 0x01];
699 bytes.extend_from_slice(&[0xFF, 0x52]);
700 push_u16(
701 &mut bytes,
702 u16::try_from(cod.len() + 2).expect("test COD segment length fits u16"),
703 );
704 bytes.extend_from_slice(&cod);
705 bytes.extend_from_slice(&[0xFF, 0x90, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
706 bytes
707 }
708
709 fn push_u16(out: &mut Vec<u8>, value: u16) {
710 out.extend_from_slice(&value.to_be_bytes());
711 }
712
713 fn push_u32(out: &mut Vec<u8>, value: u32) {
714 out.extend_from_slice(&value.to_be_bytes());
715 }
716
717 fn rewrite_siz_u32(bytes: &mut [u8], payload_offset: usize, value: u32) {
718 let siz = bytes
719 .windows(2)
720 .position(|marker| marker == [0xFF, 0x51])
721 .expect("SIZ marker");
722 let offset = siz + 4 + payload_offset;
723 bytes[offset..offset + 4].copy_from_slice(&value.to_be_bytes());
724 }
725
726 fn rewrite_component_sampling(bytes: &mut [u8], component: usize, x_rsiz: u8, y_rsiz: u8) {
727 let siz = bytes
728 .windows(2)
729 .position(|marker| marker == [0xFF, 0x51])
730 .expect("SIZ marker");
731 let component_offset = siz + 40 + component * 3;
732 bytes[component_offset + 1] = x_rsiz;
733 bytes[component_offset + 2] = y_rsiz;
734 }
735
736 fn rewrite_component_descriptor(bytes: &mut [u8], component: usize, descriptor: u8) {
737 let siz = bytes
738 .windows(2)
739 .position(|marker| marker == [0xFF, 0x51])
740 .expect("SIZ marker");
741 bytes[siz + 40 + component * 3] = descriptor;
742 }
743}