dicom_toolkit_codec/jpeg_ls/
marker.rs1use dicom_toolkit_core::error::{DcmError, DcmResult};
6
7use super::params::{ColorTransform, InterleaveMode, JlsCustomParameters, JlsParameters};
8
9const JPEG_SOI: u8 = 0xD8;
11const JPEG_EOI: u8 = 0xD9;
12const JPEG_SOS: u8 = 0xDA;
13const JPEG_SOF55: u8 = 0xF7;
14const JPEG_LSE: u8 = 0xF8;
15const JPEG_APP8: u8 = 0xE8;
16const JPEG_COM: u8 = 0xFE;
17const JPEG_DRI: u8 = 0xDD;
18
19const COLOR_XFORM_TAG: &[u8; 4] = b"mrfx";
21
22#[derive(Debug, Clone)]
24pub struct FrameInfo {
25 pub params: JlsParameters,
26 pub scan_offsets: Vec<usize>,
28}
29
30pub fn parse_markers(data: &[u8]) -> DcmResult<FrameInfo> {
32 let len = data.len();
33 if len < 4 {
34 return Err(DcmError::DecompressionError {
35 reason: "JPEG-LS: data too short".into(),
36 });
37 }
38
39 if data[0] != 0xFF || data[1] != JPEG_SOI {
41 return Err(DcmError::DecompressionError {
42 reason: "JPEG-LS: missing SOI marker".into(),
43 });
44 }
45
46 let mut pos = 2;
47 let mut params = JlsParameters::default();
48 let mut scan_offsets = Vec::new();
49 let mut got_sof = false;
50
51 while pos + 1 < len {
52 if data[pos] != 0xFF {
53 return Err(DcmError::DecompressionError {
54 reason: format!("JPEG-LS: expected marker at offset {pos}"),
55 });
56 }
57 let marker = data[pos + 1];
58 pos += 2;
59
60 match marker {
61 JPEG_EOI => break,
62
63 JPEG_SOF55 => {
64 if pos + 2 > len {
66 return Err(short_data_err());
67 }
68 let seg_len = read_u16_be(data, pos) as usize;
69 if pos + seg_len > len || seg_len < 8 {
70 return Err(short_data_err());
71 }
72 params.bits_per_sample = data[pos + 2];
73 params.height = read_u16_be(data, pos + 3) as u32;
74 params.width = read_u16_be(data, pos + 5) as u32;
75 params.components = data[pos + 7];
76
77 if params.bits_per_sample < 2 || params.bits_per_sample > 16 {
79 return Err(DcmError::DecompressionError {
80 reason: format!(
81 "JPEG-LS: unsupported bit depth {}",
82 params.bits_per_sample
83 ),
84 });
85 }
86 if params.components == 0 || params.components > 4 {
87 return Err(DcmError::DecompressionError {
88 reason: format!(
89 "JPEG-LS: unsupported component count {}",
90 params.components
91 ),
92 });
93 }
94
95 got_sof = true;
96 pos += seg_len;
97 }
98
99 JPEG_SOS => {
100 if pos + 2 > len {
102 return Err(short_data_err());
103 }
104 let seg_len = read_u16_be(data, pos) as usize;
105 if pos + seg_len > len || seg_len < 6 {
106 return Err(short_data_err());
107 }
108
109 let ns = data[pos + 2]; let near_offset = pos + 3 + (ns as usize * 2);
112 if near_offset + 2 > pos + seg_len {
113 return Err(short_data_err());
114 }
115 params.near = data[near_offset] as i32;
116 let ilv = data[near_offset + 1];
117 params.interleave = InterleaveMode::from_u8(ilv).unwrap_or(InterleaveMode::None);
118
119 let scan_start = pos + seg_len;
121 scan_offsets.push(scan_start);
122
123 pos = scan_start;
127 while pos < len {
128 if data[pos] == 0xFF && pos + 1 < len && data[pos + 1] >= 0x80 {
129 break;
130 }
131 pos += 1;
132 }
133 }
134
135 JPEG_LSE => {
136 if pos + 2 > len {
138 return Err(short_data_err());
139 }
140 let seg_len = read_u16_be(data, pos) as usize;
141 if pos + seg_len > len {
142 return Err(short_data_err());
143 }
144 if seg_len >= 13 {
145 let id = data[pos + 2];
146 if id == 1 {
147 params.custom = JlsCustomParameters {
149 max_val: read_u16_be(data, pos + 3) as i32,
150 t1: read_u16_be(data, pos + 5) as i32,
151 t2: read_u16_be(data, pos + 7) as i32,
152 t3: read_u16_be(data, pos + 9) as i32,
153 reset: read_u16_be(data, pos + 11) as i32,
154 };
155 }
156 }
157 pos += seg_len;
158 }
159
160 JPEG_APP8 => {
161 if pos + 2 > len {
163 return Err(short_data_err());
164 }
165 let seg_len = read_u16_be(data, pos) as usize;
166 if pos + seg_len > len {
167 return Err(short_data_err());
168 }
169 if seg_len >= 7 && &data[pos + 2..pos + 6] == COLOR_XFORM_TAG {
170 let xform = data[pos + 6];
171 params.color_transform =
172 ColorTransform::from_u8(xform).unwrap_or(ColorTransform::None);
173 }
174 pos += seg_len;
175 }
176
177 JPEG_COM | JPEG_DRI => {
178 if pos + 2 > len {
180 return Err(short_data_err());
181 }
182 let seg_len = read_u16_be(data, pos) as usize;
183 pos += seg_len;
184 }
185
186 m if (0xE0..=0xEF).contains(&m) => {
187 if pos + 2 > len {
189 return Err(short_data_err());
190 }
191 let seg_len = read_u16_be(data, pos) as usize;
192 pos += seg_len;
193 }
194
195 _ => {
196 if pos + 2 <= len {
198 let seg_len = read_u16_be(data, pos) as usize;
199 pos += seg_len;
200 }
201 }
202 }
203 }
204
205 if !got_sof {
206 return Err(DcmError::DecompressionError {
207 reason: "JPEG-LS: no SOF-55 marker found".into(),
208 });
209 }
210
211 Ok(FrameInfo {
212 params,
213 scan_offsets,
214 })
215}
216
217pub fn write_header(params: &JlsParameters) -> Vec<u8> {
219 let mut buf = Vec::with_capacity(64);
220
221 buf.push(0xFF);
223 buf.push(JPEG_SOI);
224
225 if params.color_transform != ColorTransform::None {
227 buf.push(0xFF);
228 buf.push(JPEG_APP8);
229 let seg_len: u16 = 7; buf.extend_from_slice(&seg_len.to_be_bytes());
231 buf.extend_from_slice(COLOR_XFORM_TAG);
232 buf.push(params.color_transform as u8);
233 }
234
235 buf.push(0xFF);
237 buf.push(JPEG_SOF55);
238 let sof_len: u16 = 8 + 3 * params.components as u16;
239 buf.extend_from_slice(&sof_len.to_be_bytes());
240 buf.push(params.bits_per_sample);
241 buf.extend_from_slice(&(params.height as u16).to_be_bytes());
242 buf.extend_from_slice(&(params.width as u16).to_be_bytes());
243 buf.push(params.components);
244 for c in 0..params.components {
245 buf.push(c + 1); buf.push(0x11); buf.push(0); }
249
250 if params.custom.max_val > 0 {
252 write_lse_params(&mut buf, ¶ms.custom);
253 }
254
255 let ns = if params.interleave == InterleaveMode::None {
257 1
258 } else {
259 params.components
260 };
261 buf.push(0xFF);
262 buf.push(JPEG_SOS);
263 let sos_len: u16 = 6 + 2 * ns as u16;
264 buf.extend_from_slice(&sos_len.to_be_bytes());
265 buf.push(ns);
266 for c in 0..ns {
267 buf.push(c + 1); buf.push(0); }
270 buf.push(params.near as u8);
271 buf.push(params.interleave as u8);
272 buf.push(0); buf
275}
276
277fn write_lse_params(buf: &mut Vec<u8>, custom: &JlsCustomParameters) {
279 buf.push(0xFF);
280 buf.push(JPEG_LSE);
281 let seg_len: u16 = 13;
282 buf.extend_from_slice(&seg_len.to_be_bytes());
283 buf.push(1); buf.extend_from_slice(&(custom.max_val as u16).to_be_bytes());
285 buf.extend_from_slice(&(custom.t1 as u16).to_be_bytes());
286 buf.extend_from_slice(&(custom.t2 as u16).to_be_bytes());
287 buf.extend_from_slice(&(custom.t3 as u16).to_be_bytes());
288 buf.extend_from_slice(&(custom.reset as u16).to_be_bytes());
289}
290
291pub fn write_eoi(buf: &mut Vec<u8>) {
293 buf.push(0xFF);
294 buf.push(JPEG_EOI);
295}
296
297fn read_u16_be(data: &[u8], offset: usize) -> u16 {
298 ((data[offset] as u16) << 8) | data[offset + 1] as u16
299}
300
301fn short_data_err() -> DcmError {
302 DcmError::DecompressionError {
303 reason: "JPEG-LS: truncated marker segment".into(),
304 }
305}
306
307#[cfg(test)]
310mod tests {
311 use super::*;
312
313 #[test]
314 fn parse_minimal_header() {
315 let params = JlsParameters {
316 width: 64,
317 height: 48,
318 bits_per_sample: 8,
319 components: 1,
320 near: 0,
321 interleave: InterleaveMode::None,
322 ..Default::default()
323 };
324 let mut header = write_header(¶ms);
325 header.push(0x00);
327 write_eoi(&mut header);
328
329 let info = parse_markers(&header).unwrap();
330 assert_eq!(info.params.width, 64);
331 assert_eq!(info.params.height, 48);
332 assert_eq!(info.params.bits_per_sample, 8);
333 assert_eq!(info.params.components, 1);
334 assert_eq!(info.params.near, 0);
335 assert_eq!(info.params.interleave, InterleaveMode::None);
336 assert_eq!(info.scan_offsets.len(), 1);
337 }
338
339 #[test]
340 fn parse_with_color_transform() {
341 let params = JlsParameters {
342 width: 256,
343 height: 256,
344 bits_per_sample: 8,
345 components: 3,
346 near: 0,
347 interleave: InterleaveMode::Line,
348 color_transform: ColorTransform::Hp1,
349 ..Default::default()
350 };
351 let mut header = write_header(¶ms);
352 header.push(0x00);
353 write_eoi(&mut header);
354
355 let info = parse_markers(&header).unwrap();
356 assert_eq!(info.params.color_transform, ColorTransform::Hp1);
357 assert_eq!(info.params.interleave, InterleaveMode::Line);
358 assert_eq!(info.params.components, 3);
359 }
360
361 #[test]
362 fn parse_with_custom_params() {
363 let params = JlsParameters {
364 width: 32,
365 height: 32,
366 bits_per_sample: 12,
367 components: 1,
368 near: 0,
369 interleave: InterleaveMode::None,
370 custom: JlsCustomParameters {
371 max_val: 4095,
372 t1: 10,
373 t2: 20,
374 t3: 30,
375 reset: 64,
376 },
377 ..Default::default()
378 };
379 let mut header = write_header(¶ms);
380 header.push(0x00);
381 write_eoi(&mut header);
382
383 let info = parse_markers(&header).unwrap();
384 assert_eq!(info.params.custom.max_val, 4095);
385 assert_eq!(info.params.custom.t1, 10);
386 assert_eq!(info.params.custom.t2, 20);
387 assert_eq!(info.params.custom.t3, 30);
388 assert_eq!(info.params.custom.reset, 64);
389 }
390
391 #[test]
392 fn reject_missing_soi() {
393 let data = [0x00, 0x00, 0xFF, 0xD9];
394 assert!(parse_markers(&data).is_err());
395 }
396
397 #[test]
398 fn reject_too_short() {
399 let data = [0xFF, 0xD8];
400 assert!(parse_markers(&data).is_err());
401 }
402}