1use std::collections::HashMap;
7use std::sync::{Arc, LazyLock, RwLock};
8
9use dicom_toolkit_core::error::{DcmError, DcmResult};
10use dicom_toolkit_data::value::PixelData;
11use dicom_toolkit_dict::ts::transfer_syntaxes;
12
13pub trait ImageCodec: Send + Sync {
20 fn transfer_syntax_uids(&self) -> &[&str];
22
23 fn decode(
27 &self,
28 encapsulated: &PixelData,
29 rows: u16,
30 columns: u16,
31 samples_per_pixel: u8,
32 bits_allocated: u8,
33 ) -> DcmResult<Vec<u8>>;
34
35 fn encode(
41 &self,
42 pixels: &[u8],
43 rows: u16,
44 columns: u16,
45 samples_per_pixel: u8,
46 bits_allocated: u8,
47 bits_stored: u8,
48 ) -> DcmResult<PixelData>;
49}
50
51fn validate_stored_bits(codec_name: &str, bits_allocated: u8, bits_stored: u8) -> DcmResult<()> {
52 if bits_stored == 0 {
53 return Err(DcmError::CompressionError {
54 reason: format!("{codec_name}: BitsStored must be at least 1"),
55 });
56 }
57 if bits_stored > bits_allocated {
58 return Err(DcmError::CompressionError {
59 reason: format!(
60 "{codec_name}: BitsStored ({bits_stored}) exceeds BitsAllocated ({bits_allocated})"
61 ),
62 });
63 }
64 Ok(())
65}
66
67struct RleCodec;
70
71impl ImageCodec for RleCodec {
72 fn transfer_syntax_uids(&self) -> &[&str] {
73 &[transfer_syntaxes::RLE_LOSSLESS.uid]
74 }
75
76 fn decode(
77 &self,
78 pixel_data: &PixelData,
79 rows: u16,
80 columns: u16,
81 samples_per_pixel: u8,
82 bits_allocated: u8,
83 ) -> DcmResult<Vec<u8>> {
84 let fragments = match pixel_data {
85 PixelData::Encapsulated { fragments, .. } => fragments,
86 PixelData::Native { bytes } => return Ok(bytes.clone()),
87 };
88
89 let mut all_frames = Vec::new();
90 for fragment in fragments {
91 let frame = crate::rle::rle_decode_frame(
92 fragment,
93 rows,
94 columns,
95 samples_per_pixel,
96 bits_allocated,
97 )?;
98 all_frames.extend_from_slice(&frame);
99 }
100 Ok(all_frames)
101 }
102
103 fn encode(
104 &self,
105 pixels: &[u8],
106 rows: u16,
107 columns: u16,
108 samples_per_pixel: u8,
109 bits_allocated: u8,
110 _bits_stored: u8,
111 ) -> DcmResult<PixelData> {
112 let encoded =
113 crate::rle::rle_encode_frame(pixels, rows, columns, samples_per_pixel, bits_allocated)?;
114 Ok(PixelData::Encapsulated {
115 offset_table: vec![0],
116 fragments: vec![encoded],
117 })
118 }
119}
120
121struct JpegCodec {
124 uids: Vec<&'static str>,
125}
126
127impl JpegCodec {
128 fn baseline() -> Self {
129 Self {
130 uids: vec![
131 transfer_syntaxes::JPEG_BASELINE.uid,
132 transfer_syntaxes::JPEG_EXTENDED.uid,
133 ],
134 }
135 }
136}
137
138impl ImageCodec for JpegCodec {
139 fn transfer_syntax_uids(&self) -> &[&str] {
140 &self.uids
141 }
142
143 fn decode(
144 &self,
145 pixel_data: &PixelData,
146 _rows: u16,
147 _columns: u16,
148 _samples_per_pixel: u8,
149 _bits_allocated: u8,
150 ) -> DcmResult<Vec<u8>> {
151 let fragments = match pixel_data {
152 PixelData::Encapsulated { fragments, .. } => fragments,
153 PixelData::Native { bytes } => return Ok(bytes.clone()),
154 };
155
156 let mut all_frames = Vec::new();
157 for fragment in fragments {
158 let frame = crate::jpeg::decoder::decode_jpeg(fragment)?;
159 all_frames.extend_from_slice(&frame.data);
160 }
161 Ok(all_frames)
162 }
163
164 fn encode(
165 &self,
166 pixels: &[u8],
167 rows: u16,
168 columns: u16,
169 samples_per_pixel: u8,
170 _bits_allocated: u8,
171 _bits_stored: u8,
172 ) -> DcmResult<PixelData> {
173 use crate::jpeg::params::JpegParams;
174 let encoded = crate::jpeg::encoder::encode_jpeg(
175 pixels,
176 columns,
177 rows,
178 samples_per_pixel,
179 &JpegParams::default(),
180 )?;
181 Ok(PixelData::Encapsulated {
182 offset_table: vec![0],
183 fragments: vec![encoded],
184 })
185 }
186}
187
188struct JpegLsCodec;
191
192impl ImageCodec for JpegLsCodec {
193 fn transfer_syntax_uids(&self) -> &[&str] {
194 &[
195 transfer_syntaxes::JPEG_LS_LOSSLESS.uid,
196 transfer_syntaxes::JPEG_LS_LOSSY.uid,
197 ]
198 }
199
200 fn decode(
201 &self,
202 pixel_data: &PixelData,
203 _rows: u16,
204 _columns: u16,
205 _samples_per_pixel: u8,
206 _bits_allocated: u8,
207 ) -> DcmResult<Vec<u8>> {
208 let fragments = match pixel_data {
209 PixelData::Encapsulated { fragments, .. } => fragments,
210 PixelData::Native { bytes } => return Ok(bytes.clone()),
211 };
212 let empty = vec![];
213 let data = fragments.first().unwrap_or(&empty);
214 let decoded = crate::jpeg_ls::decoder::decode_jpeg_ls(data)?;
215 Ok(decoded.pixels)
216 }
217
218 fn encode(
219 &self,
220 pixels: &[u8],
221 rows: u16,
222 columns: u16,
223 samples_per_pixel: u8,
224 bits_allocated: u8,
225 bits_stored: u8,
226 ) -> DcmResult<PixelData> {
227 validate_stored_bits("JPEG-LS", bits_allocated, bits_stored)?;
228 let near = 0; let encoded = crate::jpeg_ls::encoder::encode_jpeg_ls(
230 pixels,
231 columns as u32,
232 rows as u32,
233 bits_stored,
234 samples_per_pixel,
235 near,
236 )?;
237 Ok(PixelData::Encapsulated {
238 offset_table: vec![],
239 fragments: vec![encoded],
240 })
241 }
242}
243
244struct Jp2kRegistryCodec;
247
248impl ImageCodec for Jp2kRegistryCodec {
249 fn transfer_syntax_uids(&self) -> &[&str] {
250 &[
251 transfer_syntaxes::JPEG_2000_LOSSLESS.uid,
252 transfer_syntaxes::JPEG_2000.uid,
253 ]
254 }
255
256 fn decode(
257 &self,
258 pixel_data: &PixelData,
259 _rows: u16,
260 _columns: u16,
261 _samples_per_pixel: u8,
262 _bits_allocated: u8,
263 ) -> DcmResult<Vec<u8>> {
264 let fragments = match pixel_data {
265 PixelData::Encapsulated { fragments, .. } => fragments,
266 PixelData::Native { bytes } => return Ok(bytes.clone()),
267 };
268 let mut all_pixels = Vec::new();
269 for fragment in fragments {
270 if fragment.is_empty() {
271 continue;
272 }
273 let decoded = crate::jp2k::decoder::decode_jp2k(fragment)?;
274 all_pixels.extend_from_slice(&decoded.pixels);
275 }
276 Ok(all_pixels)
277 }
278
279 fn encode(
280 &self,
281 pixels: &[u8],
282 rows: u16,
283 columns: u16,
284 samples_per_pixel: u8,
285 bits_allocated: u8,
286 bits_stored: u8,
287 ) -> DcmResult<PixelData> {
288 validate_stored_bits("JPEG 2000", bits_allocated, bits_stored)?;
289 let lossless = true;
291 let encoded = crate::jp2k::encoder::encode_jp2k(
292 pixels,
293 columns as u32,
294 rows as u32,
295 bits_stored,
296 samples_per_pixel,
297 lossless,
298 )?;
299 Ok(PixelData::Encapsulated {
300 offset_table: vec![],
301 fragments: vec![encoded],
302 })
303 }
304}
305
306pub struct CodecRegistry {
310 codecs: RwLock<HashMap<String, Arc<dyn ImageCodec>>>,
311}
312
313impl CodecRegistry {
314 pub fn new() -> Self {
316 Self {
317 codecs: RwLock::new(HashMap::new()),
318 }
319 }
320
321 pub fn register(&self, codec: Arc<dyn ImageCodec>) {
323 let mut map = self.codecs.write().unwrap();
324 for uid in codec.transfer_syntax_uids() {
325 map.insert(uid.to_string(), Arc::clone(&codec));
326 }
327 }
328
329 pub fn find(&self, transfer_syntax_uid: &str) -> Option<Arc<dyn ImageCodec>> {
331 self.codecs
332 .read()
333 .unwrap()
334 .get(transfer_syntax_uid)
335 .cloned()
336 }
337
338 pub fn find_required(&self, transfer_syntax_uid: &str) -> DcmResult<Arc<dyn ImageCodec>> {
340 self.find(transfer_syntax_uid)
341 .ok_or_else(|| DcmError::NoCodec {
342 uid: transfer_syntax_uid.to_string(),
343 })
344 }
345}
346
347impl Default for CodecRegistry {
348 fn default() -> Self {
349 Self::new()
350 }
351}
352
353pub static GLOBAL_REGISTRY: LazyLock<CodecRegistry> = LazyLock::new(|| {
357 let reg = CodecRegistry::new();
358 reg.register(Arc::new(RleCodec));
359 reg.register(Arc::new(JpegCodec::baseline()));
360 reg.register(Arc::new(JpegLsCodec));
361 reg.register(Arc::new(Jp2kRegistryCodec));
362 reg
363});
364
365const SUPPORTED_TS: &[&str] = &[
369 transfer_syntaxes::RLE_LOSSLESS.uid,
370 transfer_syntaxes::JPEG_BASELINE.uid,
371 transfer_syntaxes::JPEG_EXTENDED.uid,
372 transfer_syntaxes::JPEG_LOSSLESS.uid,
373 transfer_syntaxes::JPEG_LOSSLESS_SV1.uid,
374 transfer_syntaxes::JPEG_LS_LOSSLESS.uid,
375 transfer_syntaxes::JPEG_LS_LOSSY.uid,
376 transfer_syntaxes::JPEG_2000_LOSSLESS.uid,
377 transfer_syntaxes::JPEG_2000.uid,
378];
379
380#[derive(Debug, Clone, Copy)]
382pub struct CodecInfo {
383 pub transfer_syntax_uid: &'static str,
385 pub name: &'static str,
387}
388
389pub fn can_decode(ts_uid: &str) -> bool {
391 SUPPORTED_TS.contains(&ts_uid)
392}
393
394pub fn supported_transfer_syntaxes() -> &'static [&'static str] {
396 SUPPORTED_TS
397}
398
399pub fn decode_pixel_data(
404 ts_uid: &str,
405 data: &[u8],
406 rows: u16,
407 cols: u16,
408 bits_allocated: u16,
409 samples: u16,
410) -> DcmResult<Vec<u8>> {
411 match ts_uid {
412 uid if uid == transfer_syntaxes::RLE_LOSSLESS.uid => {
413 crate::rle::RleCodec::decode(data, rows, cols, bits_allocated, samples)
414 }
415 uid if uid == transfer_syntaxes::JPEG_BASELINE.uid
416 || uid == transfer_syntaxes::JPEG_EXTENDED.uid
417 || uid == transfer_syntaxes::JPEG_LOSSLESS.uid
418 || uid == transfer_syntaxes::JPEG_LOSSLESS_SV1.uid =>
419 {
420 crate::jpeg::JpegDecoder::decode_frame(data).map(|f| f.pixels)
421 }
422 uid if uid == transfer_syntaxes::JPEG_LS_LOSSLESS.uid
423 || uid == transfer_syntaxes::JPEG_LS_LOSSY.uid =>
424 {
425 crate::jpeg_ls::JpegLsCodec::decode_frame(data).map(|f| f.pixels)
426 }
427 uid if uid == transfer_syntaxes::JPEG_2000_LOSSLESS.uid
428 || uid == transfer_syntaxes::JPEG_2000.uid =>
429 {
430 crate::jp2k::Jp2kCodec::decode_frame(data).map(|f| f.pixels)
431 }
432 uid => Err(DcmError::NoCodec {
433 uid: uid.to_string(),
434 }),
435 }
436}
437
438#[cfg(test)]
441mod tests {
442 use super::*;
443 use dicom_toolkit_dict::ts::transfer_syntaxes;
444
445 #[test]
446 fn global_registry_has_rle() {
447 let codec = GLOBAL_REGISTRY.find(transfer_syntaxes::RLE_LOSSLESS.uid);
448 assert!(codec.is_some());
449 }
450
451 #[test]
452 fn global_registry_has_jpeg_baseline() {
453 let codec = GLOBAL_REGISTRY.find(transfer_syntaxes::JPEG_BASELINE.uid);
454 assert!(codec.is_some());
455 }
456
457 #[test]
458 fn global_registry_has_jpeg_extended() {
459 let codec = GLOBAL_REGISTRY.find(transfer_syntaxes::JPEG_EXTENDED.uid);
460 assert!(codec.is_some());
461 }
462
463 #[test]
464 fn global_registry_has_jpeg_ls() {
465 let codec = GLOBAL_REGISTRY.find(transfer_syntaxes::JPEG_LS_LOSSLESS.uid);
466 assert!(codec.is_some());
467 }
468
469 #[test]
470 fn unknown_uid_returns_none() {
471 let codec = GLOBAL_REGISTRY.find("1.2.3.4.5.999");
472 assert!(codec.is_none());
473 }
474
475 #[test]
476 fn find_required_returns_error_for_unknown() {
477 let result = GLOBAL_REGISTRY.find_required("1.9.9.9.9");
478 assert!(matches!(result, Err(DcmError::NoCodec { .. })));
479 }
480
481 #[test]
484 fn codec_registry_can_decode_rle() {
485 assert!(can_decode(transfer_syntaxes::RLE_LOSSLESS.uid));
486 }
487
488 #[test]
489 fn codec_registry_can_decode_jpeg_baseline() {
490 assert!(can_decode(transfer_syntaxes::JPEG_BASELINE.uid));
491 }
492
493 #[test]
494 fn codec_registry_cannot_decode_unknown() {
495 assert!(!can_decode("1.2.3.4.5.999"));
496 }
497
498 #[test]
499 fn supported_transfer_syntaxes_is_non_empty() {
500 let list = supported_transfer_syntaxes();
501 assert!(!list.is_empty());
502 assert!(list.contains(&transfer_syntaxes::RLE_LOSSLESS.uid));
503 assert!(list.contains(&transfer_syntaxes::JPEG_BASELINE.uid));
504 }
505
506 #[test]
507 fn rle_codec_roundtrip_via_registry() {
508 use crate::rle::rle_encode_frame;
509
510 let rows = 4u16;
511 let cols = 4u16;
512 let samples = 1u8;
513 let bits = 8u8;
514 let pixels: Vec<u8> = (0u8..16).collect();
515
516 let encoded_frame = rle_encode_frame(&pixels, rows, cols, samples, bits).unwrap();
517 let pixel_data = PixelData::Encapsulated {
518 offset_table: vec![0],
519 fragments: vec![encoded_frame],
520 };
521
522 let codec = GLOBAL_REGISTRY
523 .find(transfer_syntaxes::RLE_LOSSLESS.uid)
524 .unwrap();
525 let decoded = codec
526 .decode(&pixel_data, rows, cols, samples, bits)
527 .unwrap();
528 assert_eq!(&decoded[..16], &pixels[..]);
529 }
530
531 #[test]
532 fn jp2k_codec_multiframe_decode_via_registry() {
533 let rows = 4u16;
534 let cols = 4u16;
535 let samples = 1u8;
536 let bits = 8u8;
537 let frame_a: Vec<u8> = (0u8..16).collect();
538 let frame_b: Vec<u8> = (16u8..32).collect();
539
540 let encoded_a = crate::jp2k::encoder::encode_jp2k(
541 &frame_a,
542 cols as u32,
543 rows as u32,
544 bits,
545 samples,
546 true,
547 )
548 .unwrap();
549 let encoded_b = crate::jp2k::encoder::encode_jp2k(
550 &frame_b,
551 cols as u32,
552 rows as u32,
553 bits,
554 samples,
555 true,
556 )
557 .unwrap();
558
559 let pixel_data = PixelData::Encapsulated {
560 offset_table: vec![],
561 fragments: vec![encoded_a, encoded_b],
562 };
563
564 let codec = GLOBAL_REGISTRY
565 .find(transfer_syntaxes::JPEG_2000_LOSSLESS.uid)
566 .unwrap();
567 let decoded = codec
568 .decode(&pixel_data, rows, cols, samples, bits)
569 .unwrap();
570
571 let mut expected = frame_a;
572 expected.extend_from_slice(&frame_b);
573 assert_eq!(decoded, expected);
574 }
575
576 #[test]
577 fn jpeg_ls_codec_encode_uses_bits_stored_precision() {
578 let rows = 4u16;
579 let cols = 4u16;
580 let samples = 1u8;
581 let bits_allocated = 16u8;
582 let bits_stored = 12u8;
583 let mut pixels = Vec::with_capacity(32);
584 for i in 0u16..16 {
585 pixels.extend_from_slice(&((i * 257) & 0x0FFF).to_le_bytes());
586 }
587
588 let codec = GLOBAL_REGISTRY
589 .find(transfer_syntaxes::JPEG_LS_LOSSLESS.uid)
590 .unwrap();
591 let encoded = codec
592 .encode(&pixels, rows, cols, samples, bits_allocated, bits_stored)
593 .unwrap();
594 let fragment = match encoded {
595 PixelData::Encapsulated { fragments, .. } => fragments.into_iter().next().unwrap(),
596 PixelData::Native { .. } => panic!("expected encapsulated pixel data"),
597 };
598
599 let decoded = crate::jpeg_ls::decoder::decode_jpeg_ls(&fragment).unwrap();
600 assert_eq!(decoded.bits_per_sample, bits_stored);
601 assert_eq!(decoded.pixels, pixels);
602 }
603
604 #[test]
605 fn jp2k_codec_encode_uses_bits_stored_precision() {
606 let rows = 4u16;
607 let cols = 4u16;
608 let samples = 1u8;
609 let bits_allocated = 16u8;
610 let bits_stored = 12u8;
611 let mut pixels = Vec::with_capacity(32);
612 for i in 0u16..16 {
613 pixels.extend_from_slice(&((i * 257) & 0x0FFF).to_le_bytes());
614 }
615
616 let codec = GLOBAL_REGISTRY
617 .find(transfer_syntaxes::JPEG_2000_LOSSLESS.uid)
618 .unwrap();
619 let encoded = codec
620 .encode(&pixels, rows, cols, samples, bits_allocated, bits_stored)
621 .unwrap();
622 let fragment = match encoded {
623 PixelData::Encapsulated { fragments, .. } => fragments.into_iter().next().unwrap(),
624 PixelData::Native { .. } => panic!("expected encapsulated pixel data"),
625 };
626
627 let decoded = crate::jp2k::decoder::decode_jp2k(&fragment).unwrap();
628 assert_eq!(decoded.bits_per_sample, bits_stored);
629 assert_eq!(decoded.pixels, pixels);
630 }
631}