1use crate::error::JpegError;
4use crate::info::{ColorSpace, DecodeOptions, Info, RestartIndex, SofKind};
5use crate::parse::header::{parse_header, parse_header_with_external_live, ParsedHeader};
6use j2k_core::{CompressedPayloadKind, PassthroughCandidate};
7
8use super::core_traits::jpeg_passthrough_syntax;
9use super::{find_component_index, restart_index_for_stream};
10
11#[derive(Debug)]
13pub struct JpegView<'a> {
14 pub(super) bytes: &'a [u8],
15 pub(super) header: ParsedHeader,
16 pub(super) info: Info,
17 pub(super) options: DecodeOptions,
18}
19
20impl<'a> JpegView<'a> {
21 pub fn parse(input: &'a [u8]) -> Result<Self, JpegError> {
27 Self::parse_with_options(input, DecodeOptions::default())
28 }
29
30 pub(crate) fn parse_with_external_live(
37 input: &'a [u8],
38 external_live_bytes: usize,
39 ) -> Result<Self, JpegError> {
40 Self::parse_with_options_and_external_live(
41 input,
42 DecodeOptions::default(),
43 external_live_bytes,
44 )
45 }
46
47 pub fn parse_with_options(input: &'a [u8], options: DecodeOptions) -> Result<Self, JpegError> {
53 let header = parse_header(input)?;
54 Ok(Self::from_header(input, header, options))
55 }
56
57 fn parse_with_options_and_external_live(
58 input: &'a [u8],
59 options: DecodeOptions,
60 external_live_bytes: usize,
61 ) -> Result<Self, JpegError> {
62 let header = parse_header_with_external_live(input, external_live_bytes)?;
63 Ok(Self::from_header(input, header, options))
64 }
65
66 fn from_header(input: &'a [u8], header: ParsedHeader, options: DecodeOptions) -> Self {
67 let mut info = header.info();
68 options.apply_to_info(&mut info);
69 Self {
70 bytes: input,
71 header,
72 info,
73 options,
74 }
75 }
76
77 #[must_use]
79 pub fn info(&self) -> &Info {
80 &self.info
81 }
82
83 #[must_use]
85 pub fn bytes(&self) -> &'a [u8] {
86 self.bytes
87 }
88
89 pub(crate) fn parsed_header(&self) -> &ParsedHeader {
90 &self.header
91 }
92
93 #[must_use]
100 pub fn passthrough_candidate(&self) -> Option<PassthroughCandidate<'a>> {
101 jpeg_passthrough_syntax(&self.info).map(|transfer_syntax| {
102 PassthroughCandidate::new(
103 self.bytes,
104 transfer_syntax,
105 CompressedPayloadKind::JpegInterchange,
106 self.info.to_core_info(),
107 )
108 })
109 }
110
111 pub fn restart_index(&self) -> Result<Option<RestartIndex>, JpegError> {
120 restart_index_for_stream(
121 self.bytes,
122 self.header.sos_offset,
123 &self.info,
124 self.info.restart_interval,
125 )
126 }
127
128 pub(crate) fn has_lossless_subsampled_color_capability_shape(&self) -> bool {
129 if self.info.sof_kind != SofKind::Lossless
130 || !matches!(self.info.color_space, ColorSpace::Rgb | ColorSpace::YCbCr)
131 || !matches!(self.info.bit_depth, 8 | 16)
132 || self.info.sampling.len() != 3
133 || !self
134 .info
135 .sampling
136 .components()
137 .iter()
138 .any(|&(h, v)| h != 1 || v != 1)
139 || self.header.scan_count != 1
140 {
141 return false;
142 }
143
144 let Some(scan) = self.header.scan.as_ref() else {
145 return false;
146 };
147 if !(1..=7).contains(&scan.ss)
148 || scan.se != 0
149 || scan.ah != 0
150 || scan.al != 0
151 || scan.components.len() != 3
152 {
153 return false;
154 }
155
156 scan.components.iter().all(|scan_component| {
157 find_component_index(&self.header.component_ids, scan_component.id).is_some()
158 && self.header.huffman_tables.dc[scan_component.dc_table as usize].is_some()
159 })
160 }
161}