1use std::collections::HashMap;
7use std::sync::{Arc, LazyLock, RwLock};
8
9use dicom_toolkit_core::error::{DcmError, DcmResult};
10use dicom_toolkit_data::{encapsulated_pixel_data_from_frames, 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_transfer_syntax_uids(&self) -> &[&str] {
25 self.transfer_syntax_uids()
26 }
27
28 fn encode_transfer_syntax_uids(&self) -> &[&str] {
30 self.transfer_syntax_uids()
31 }
32
33 fn decode(
37 &self,
38 encapsulated: &PixelData,
39 rows: u16,
40 columns: u16,
41 samples_per_pixel: u8,
42 bits_allocated: u8,
43 ) -> DcmResult<Vec<u8>>;
44
45 fn encode(
51 &self,
52 pixels: &[u8],
53 rows: u16,
54 columns: u16,
55 samples_per_pixel: u8,
56 bits_allocated: u8,
57 bits_stored: u8,
58 ) -> DcmResult<PixelData>;
59}
60
61fn validate_stored_bits(codec_name: &str, bits_allocated: u8, bits_stored: u8) -> DcmResult<()> {
62 if bits_stored == 0 {
63 return Err(DcmError::CompressionError {
64 reason: format!("{codec_name}: BitsStored must be at least 1"),
65 });
66 }
67 if bits_stored > bits_allocated {
68 return Err(DcmError::CompressionError {
69 reason: format!(
70 "{codec_name}: BitsStored ({bits_stored}) exceeds BitsAllocated ({bits_allocated})"
71 ),
72 });
73 }
74 Ok(())
75}
76
77struct RleCodec;
80
81impl ImageCodec for RleCodec {
82 fn transfer_syntax_uids(&self) -> &[&str] {
83 &[transfer_syntaxes::RLE_LOSSLESS.uid]
84 }
85
86 fn decode(
87 &self,
88 pixel_data: &PixelData,
89 rows: u16,
90 columns: u16,
91 samples_per_pixel: u8,
92 bits_allocated: u8,
93 ) -> DcmResult<Vec<u8>> {
94 let fragments = match pixel_data {
95 PixelData::Encapsulated { fragments, .. } => fragments,
96 PixelData::Native { bytes } => return Ok(bytes.clone()),
97 };
98
99 let mut all_frames = Vec::new();
100 for fragment in fragments {
101 let frame = crate::rle::rle_decode_frame(
102 fragment,
103 rows,
104 columns,
105 samples_per_pixel,
106 bits_allocated,
107 )?;
108 all_frames.extend_from_slice(&frame);
109 }
110 Ok(all_frames)
111 }
112
113 fn encode(
114 &self,
115 pixels: &[u8],
116 rows: u16,
117 columns: u16,
118 samples_per_pixel: u8,
119 bits_allocated: u8,
120 _bits_stored: u8,
121 ) -> DcmResult<PixelData> {
122 let encoded =
123 crate::rle::rle_encode_frame(pixels, rows, columns, samples_per_pixel, bits_allocated)?;
124 encapsulated_pixel_data_from_frames(&[encoded])
125 }
126}
127
128struct JpegCodec {
131 uids: Vec<&'static str>,
132}
133
134impl JpegCodec {
135 fn baseline() -> Self {
136 Self {
137 uids: vec![
138 transfer_syntaxes::JPEG_BASELINE.uid,
139 transfer_syntaxes::JPEG_EXTENDED.uid,
140 ],
141 }
142 }
143}
144
145struct JpegLosslessCodec;
146
147impl ImageCodec for JpegCodec {
148 fn transfer_syntax_uids(&self) -> &[&str] {
149 &self.uids
150 }
151
152 fn decode(
153 &self,
154 pixel_data: &PixelData,
155 _rows: u16,
156 _columns: u16,
157 _samples_per_pixel: u8,
158 _bits_allocated: u8,
159 ) -> DcmResult<Vec<u8>> {
160 let fragments = match pixel_data {
161 PixelData::Encapsulated { fragments, .. } => fragments,
162 PixelData::Native { bytes } => return Ok(bytes.clone()),
163 };
164
165 let mut all_frames = Vec::new();
166 for fragment in fragments {
167 let frame = crate::jpeg::decoder::decode_jpeg(fragment)?;
168 all_frames.extend_from_slice(&frame.data);
169 }
170 Ok(all_frames)
171 }
172
173 fn encode(
174 &self,
175 pixels: &[u8],
176 rows: u16,
177 columns: u16,
178 samples_per_pixel: u8,
179 _bits_allocated: u8,
180 _bits_stored: u8,
181 ) -> DcmResult<PixelData> {
182 use crate::jpeg::params::JpegParams;
183 let encoded = crate::jpeg::encoder::encode_jpeg(
184 pixels,
185 columns,
186 rows,
187 samples_per_pixel,
188 &JpegParams::default(),
189 )?;
190 encapsulated_pixel_data_from_frames(&[encoded])
191 }
192}
193
194impl ImageCodec for JpegLosslessCodec {
195 fn transfer_syntax_uids(&self) -> &[&str] {
196 &[
197 transfer_syntaxes::JPEG_LOSSLESS.uid,
198 transfer_syntaxes::JPEG_LOSSLESS_SV1.uid,
199 ]
200 }
201
202 fn decode(
203 &self,
204 pixel_data: &PixelData,
205 _rows: u16,
206 _columns: u16,
207 _samples_per_pixel: u8,
208 _bits_allocated: u8,
209 ) -> DcmResult<Vec<u8>> {
210 let fragments = match pixel_data {
211 PixelData::Encapsulated { fragments, .. } => fragments,
212 PixelData::Native { bytes } => return Ok(bytes.clone()),
213 };
214
215 let mut all_frames = Vec::new();
216 for fragment in fragments {
217 let frame = crate::jpeg::decoder::decode_jpeg(fragment)?;
218 all_frames.extend_from_slice(&frame.data);
219 }
220 Ok(all_frames)
221 }
222
223 fn encode(
224 &self,
225 pixels: &[u8],
226 rows: u16,
227 columns: u16,
228 samples_per_pixel: u8,
229 bits_allocated: u8,
230 bits_stored: u8,
231 ) -> DcmResult<PixelData> {
232 let encoded = crate::jpeg::lossless_encoder::encode_jpeg_lossless(
233 pixels,
234 columns,
235 rows,
236 samples_per_pixel,
237 bits_allocated,
238 bits_stored,
239 1,
240 )?;
241 encapsulated_pixel_data_from_frames(&[encoded])
242 }
243}
244
245struct JpegLsCodec;
248
249impl ImageCodec for JpegLsCodec {
250 fn transfer_syntax_uids(&self) -> &[&str] {
251 &[
252 transfer_syntaxes::JPEG_LS_LOSSLESS.uid,
253 transfer_syntaxes::JPEG_LS_LOSSY.uid,
254 ]
255 }
256
257 fn decode(
258 &self,
259 pixel_data: &PixelData,
260 _rows: u16,
261 _columns: u16,
262 _samples_per_pixel: u8,
263 _bits_allocated: u8,
264 ) -> DcmResult<Vec<u8>> {
265 let fragments = match pixel_data {
266 PixelData::Encapsulated { fragments, .. } => fragments,
267 PixelData::Native { bytes } => return Ok(bytes.clone()),
268 };
269 let empty = vec![];
270 let data = fragments.first().unwrap_or(&empty);
271 let decoded = crate::jpeg_ls::decoder::decode_jpeg_ls(data)?;
272 Ok(decoded.pixels)
273 }
274
275 fn encode(
276 &self,
277 pixels: &[u8],
278 rows: u16,
279 columns: u16,
280 samples_per_pixel: u8,
281 bits_allocated: u8,
282 bits_stored: u8,
283 ) -> DcmResult<PixelData> {
284 validate_stored_bits("JPEG-LS", bits_allocated, bits_stored)?;
285 let near = 0; let encoded = crate::jpeg_ls::encoder::encode_jpeg_ls(
287 pixels,
288 columns as u32,
289 rows as u32,
290 bits_stored,
291 samples_per_pixel,
292 near,
293 )?;
294 encapsulated_pixel_data_from_frames(&[encoded])
295 }
296}
297
298struct Jp2kDecodeCodec;
301
302struct Jp2kEncodeCodec {
303 transfer_syntax_uid: &'static str,
304 high_throughput: bool,
305 lossless: bool,
306}
307
308const JP2K_DECODE_TRANSFER_SYNTAXES: &[&str] = &[
309 transfer_syntaxes::JPEG_2000_LOSSLESS.uid,
310 transfer_syntaxes::JPEG_2000.uid,
311 transfer_syntaxes::HIGH_THROUGHPUT_JPEG_2000_LOSSLESS_ONLY.uid,
312 transfer_syntaxes::HIGH_THROUGHPUT_JPEG_2000_RPCL_LOSSLESS_ONLY.uid,
313 transfer_syntaxes::HIGH_THROUGHPUT_JPEG_2000.uid,
314];
315
316const NO_TRANSFER_SYNTAXES: &[&str] = &[];
317
318impl Jp2kEncodeCodec {
319 const fn new(transfer_syntax_uid: &'static str, high_throughput: bool, lossless: bool) -> Self {
320 Self {
321 transfer_syntax_uid,
322 high_throughput,
323 lossless,
324 }
325 }
326
327 fn codec_name(&self) -> &'static str {
328 if self.high_throughput {
329 "HTJ2K"
330 } else {
331 "JPEG 2000"
332 }
333 }
334}
335
336fn decode_jp2k_pixel_data(pixel_data: &PixelData) -> DcmResult<Vec<u8>> {
337 let fragments = match pixel_data {
338 PixelData::Encapsulated { fragments, .. } => fragments,
339 PixelData::Native { bytes } => return Ok(bytes.clone()),
340 };
341
342 let mut all_pixels = Vec::new();
343 for fragment in fragments {
344 if fragment.is_empty() {
345 continue;
346 }
347 let decoded = crate::jp2k::decoder::decode_jp2k(fragment)?;
348 all_pixels.extend_from_slice(&decoded.pixels);
349 }
350
351 Ok(all_pixels)
352}
353
354fn encode_jp2k_pixel_data(
355 codec: &Jp2kEncodeCodec,
356 pixels: &[u8],
357 rows: u16,
358 columns: u16,
359 samples_per_pixel: u8,
360 bits_allocated: u8,
361 bits_stored: u8,
362) -> DcmResult<PixelData> {
363 validate_stored_bits(codec.codec_name(), bits_allocated, bits_stored)?;
364 let encoded = if codec.high_throughput {
365 crate::jp2k::encoder::encode_htj2k(
366 pixels,
367 columns as u32,
368 rows as u32,
369 bits_stored,
370 samples_per_pixel,
371 codec.lossless,
372 )?
373 } else {
374 crate::jp2k::encoder::encode_jp2k(
375 pixels,
376 columns as u32,
377 rows as u32,
378 bits_stored,
379 samples_per_pixel,
380 codec.lossless,
381 )?
382 };
383
384 encapsulated_pixel_data_from_frames(&[encoded])
385}
386
387impl ImageCodec for Jp2kDecodeCodec {
388 fn transfer_syntax_uids(&self) -> &[&str] {
389 JP2K_DECODE_TRANSFER_SYNTAXES
390 }
391
392 fn encode_transfer_syntax_uids(&self) -> &[&str] {
393 NO_TRANSFER_SYNTAXES
394 }
395
396 fn decode(
397 &self,
398 pixel_data: &PixelData,
399 _rows: u16,
400 _columns: u16,
401 _samples_per_pixel: u8,
402 _bits_allocated: u8,
403 ) -> DcmResult<Vec<u8>> {
404 decode_jp2k_pixel_data(pixel_data)
405 }
406
407 fn encode(
408 &self,
409 _pixels: &[u8],
410 _rows: u16,
411 _columns: u16,
412 _samples_per_pixel: u8,
413 _bits_allocated: u8,
414 _bits_stored: u8,
415 ) -> DcmResult<PixelData> {
416 Err(DcmError::CompressionError {
417 reason: "JPEG 2000 encoding is not available for this transfer syntax".to_string(),
418 })
419 }
420}
421
422impl ImageCodec for Jp2kEncodeCodec {
423 fn transfer_syntax_uids(&self) -> &[&str] {
424 std::slice::from_ref(&self.transfer_syntax_uid)
425 }
426
427 fn decode(
428 &self,
429 pixel_data: &PixelData,
430 _rows: u16,
431 _columns: u16,
432 _samples_per_pixel: u8,
433 _bits_allocated: u8,
434 ) -> DcmResult<Vec<u8>> {
435 decode_jp2k_pixel_data(pixel_data)
436 }
437
438 fn encode(
439 &self,
440 pixels: &[u8],
441 rows: u16,
442 columns: u16,
443 samples_per_pixel: u8,
444 bits_allocated: u8,
445 bits_stored: u8,
446 ) -> DcmResult<PixelData> {
447 encode_jp2k_pixel_data(
448 self,
449 pixels,
450 rows,
451 columns,
452 samples_per_pixel,
453 bits_allocated,
454 bits_stored,
455 )
456 }
457}
458
459pub struct CodecRegistry {
463 codecs: RwLock<HashMap<String, Arc<dyn ImageCodec>>>,
464 decoder_codecs: RwLock<HashMap<String, Arc<dyn ImageCodec>>>,
465 encoder_codecs: RwLock<HashMap<String, Arc<dyn ImageCodec>>>,
466}
467
468impl CodecRegistry {
469 pub fn new() -> Self {
471 Self {
472 codecs: RwLock::new(HashMap::new()),
473 decoder_codecs: RwLock::new(HashMap::new()),
474 encoder_codecs: RwLock::new(HashMap::new()),
475 }
476 }
477
478 pub fn register(&self, codec: Arc<dyn ImageCodec>) {
480 let decode_uids = codec.decode_transfer_syntax_uids();
481 let encode_uids = codec.encode_transfer_syntax_uids();
482
483 let mut codecs = self.codecs.write().unwrap();
484 let mut decoder_codecs = self.decoder_codecs.write().unwrap();
485 let mut encoder_codecs = self.encoder_codecs.write().unwrap();
486
487 for uid in decode_uids {
488 decoder_codecs.insert(uid.to_string(), Arc::clone(&codec));
489 if encode_uids.contains(uid) {
490 codecs.insert(uid.to_string(), Arc::clone(&codec));
491 }
492 }
493
494 for uid in encode_uids {
495 encoder_codecs.insert(uid.to_string(), Arc::clone(&codec));
496 }
497 }
498
499 pub fn find(&self, transfer_syntax_uid: &str) -> Option<Arc<dyn ImageCodec>> {
502 self.codecs
503 .read()
504 .unwrap()
505 .get(transfer_syntax_uid)
506 .cloned()
507 }
508
509 pub fn find_decoder(&self, transfer_syntax_uid: &str) -> Option<Arc<dyn ImageCodec>> {
511 self.decoder_codecs
512 .read()
513 .unwrap()
514 .get(transfer_syntax_uid)
515 .cloned()
516 }
517
518 pub fn find_encoder(&self, transfer_syntax_uid: &str) -> Option<Arc<dyn ImageCodec>> {
520 self.encoder_codecs
521 .read()
522 .unwrap()
523 .get(transfer_syntax_uid)
524 .cloned()
525 }
526
527 pub fn find_required(&self, transfer_syntax_uid: &str) -> DcmResult<Arc<dyn ImageCodec>> {
530 self.find(transfer_syntax_uid)
531 .ok_or_else(|| DcmError::NoCodec {
532 uid: transfer_syntax_uid.to_string(),
533 })
534 }
535
536 pub fn find_decoder_required(
538 &self,
539 transfer_syntax_uid: &str,
540 ) -> DcmResult<Arc<dyn ImageCodec>> {
541 self.find_decoder(transfer_syntax_uid)
542 .ok_or_else(|| DcmError::NoCodec {
543 uid: transfer_syntax_uid.to_string(),
544 })
545 }
546
547 pub fn find_encoder_required(
549 &self,
550 transfer_syntax_uid: &str,
551 ) -> DcmResult<Arc<dyn ImageCodec>> {
552 self.find_encoder(transfer_syntax_uid)
553 .ok_or_else(|| DcmError::NoCodec {
554 uid: transfer_syntax_uid.to_string(),
555 })
556 }
557}
558
559impl Default for CodecRegistry {
560 fn default() -> Self {
561 Self::new()
562 }
563}
564
565pub static GLOBAL_REGISTRY: LazyLock<CodecRegistry> = LazyLock::new(|| {
569 let reg = CodecRegistry::new();
570 reg.register(Arc::new(RleCodec));
571 reg.register(Arc::new(JpegCodec::baseline()));
572 reg.register(Arc::new(JpegLosslessCodec));
573 reg.register(Arc::new(JpegLsCodec));
574 reg.register(Arc::new(Jp2kDecodeCodec));
575 reg.register(Arc::new(Jp2kEncodeCodec::new(
576 transfer_syntaxes::JPEG_2000_LOSSLESS.uid,
577 false,
578 true,
579 )));
580 reg.register(Arc::new(Jp2kEncodeCodec::new(
581 transfer_syntaxes::JPEG_2000.uid,
582 false,
583 true,
584 )));
585 reg.register(Arc::new(Jp2kEncodeCodec::new(
586 transfer_syntaxes::HIGH_THROUGHPUT_JPEG_2000_LOSSLESS_ONLY.uid,
587 true,
588 true,
589 )));
590 reg.register(Arc::new(Jp2kEncodeCodec::new(
593 transfer_syntaxes::HIGH_THROUGHPUT_JPEG_2000.uid,
594 true,
595 true,
596 )));
597 reg
598});
599
600const SUPPORTED_DECODE_TS: &[&str] = &[
604 transfer_syntaxes::RLE_LOSSLESS.uid,
605 transfer_syntaxes::JPEG_BASELINE.uid,
606 transfer_syntaxes::JPEG_EXTENDED.uid,
607 transfer_syntaxes::JPEG_LOSSLESS.uid,
608 transfer_syntaxes::JPEG_LOSSLESS_SV1.uid,
609 transfer_syntaxes::JPEG_LS_LOSSLESS.uid,
610 transfer_syntaxes::JPEG_LS_LOSSY.uid,
611 transfer_syntaxes::JPEG_2000_LOSSLESS.uid,
612 transfer_syntaxes::JPEG_2000.uid,
613 transfer_syntaxes::HIGH_THROUGHPUT_JPEG_2000_LOSSLESS_ONLY.uid,
614 transfer_syntaxes::HIGH_THROUGHPUT_JPEG_2000_RPCL_LOSSLESS_ONLY.uid,
615 transfer_syntaxes::HIGH_THROUGHPUT_JPEG_2000.uid,
616];
617
618const SUPPORTED_ENCODE_TS: &[&str] = &[
620 transfer_syntaxes::RLE_LOSSLESS.uid,
621 transfer_syntaxes::JPEG_BASELINE.uid,
622 transfer_syntaxes::JPEG_EXTENDED.uid,
623 transfer_syntaxes::JPEG_LOSSLESS.uid,
624 transfer_syntaxes::JPEG_LOSSLESS_SV1.uid,
625 transfer_syntaxes::JPEG_LS_LOSSLESS.uid,
626 transfer_syntaxes::JPEG_LS_LOSSY.uid,
627 transfer_syntaxes::JPEG_2000_LOSSLESS.uid,
628 transfer_syntaxes::JPEG_2000.uid,
629 transfer_syntaxes::HIGH_THROUGHPUT_JPEG_2000_LOSSLESS_ONLY.uid,
630 transfer_syntaxes::HIGH_THROUGHPUT_JPEG_2000.uid,
631];
632
633#[derive(Debug, Clone, Copy)]
635pub struct CodecInfo {
636 pub transfer_syntax_uid: &'static str,
638 pub name: &'static str,
640}
641
642pub fn can_decode(ts_uid: &str) -> bool {
644 SUPPORTED_DECODE_TS.contains(&ts_uid)
645}
646
647pub fn supported_decode_transfer_syntaxes() -> &'static [&'static str] {
649 SUPPORTED_DECODE_TS
650}
651
652pub fn can_encode(ts_uid: &str) -> bool {
654 SUPPORTED_ENCODE_TS.contains(&ts_uid)
655}
656
657pub fn supported_encode_transfer_syntaxes() -> &'static [&'static str] {
659 SUPPORTED_ENCODE_TS
660}
661
662pub fn supported_transfer_syntaxes() -> &'static [&'static str] {
664 supported_decode_transfer_syntaxes()
665}
666
667pub fn decode_pixel_data(
672 ts_uid: &str,
673 data: &[u8],
674 rows: u16,
675 cols: u16,
676 bits_allocated: u16,
677 samples: u16,
678) -> DcmResult<Vec<u8>> {
679 match ts_uid {
680 uid if uid == transfer_syntaxes::RLE_LOSSLESS.uid => {
681 crate::rle::RleCodec::decode(data, rows, cols, bits_allocated, samples)
682 }
683 uid if uid == transfer_syntaxes::JPEG_BASELINE.uid
684 || uid == transfer_syntaxes::JPEG_EXTENDED.uid
685 || uid == transfer_syntaxes::JPEG_LOSSLESS.uid
686 || uid == transfer_syntaxes::JPEG_LOSSLESS_SV1.uid =>
687 {
688 crate::jpeg::JpegDecoder::decode_frame(data).map(|f| f.pixels)
689 }
690 uid if uid == transfer_syntaxes::JPEG_LS_LOSSLESS.uid
691 || uid == transfer_syntaxes::JPEG_LS_LOSSY.uid =>
692 {
693 crate::jpeg_ls::JpegLsCodec::decode_frame(data).map(|f| f.pixels)
694 }
695 uid if uid == transfer_syntaxes::JPEG_2000_LOSSLESS.uid
696 || uid == transfer_syntaxes::JPEG_2000.uid
697 || uid == transfer_syntaxes::HIGH_THROUGHPUT_JPEG_2000_LOSSLESS_ONLY.uid
698 || uid == transfer_syntaxes::HIGH_THROUGHPUT_JPEG_2000_RPCL_LOSSLESS_ONLY.uid
699 || uid == transfer_syntaxes::HIGH_THROUGHPUT_JPEG_2000.uid =>
700 {
701 crate::jp2k::Jp2kCodec::decode_frame(data).map(|f| f.pixels)
702 }
703 uid => Err(DcmError::NoCodec {
704 uid: uid.to_string(),
705 }),
706 }
707}
708
709#[cfg(test)]
712mod tests {
713 use super::*;
714 use dicom_toolkit_dict::ts::transfer_syntaxes;
715
716 #[test]
717 fn global_registry_has_rle() {
718 let codec = GLOBAL_REGISTRY.find(transfer_syntaxes::RLE_LOSSLESS.uid);
719 assert!(codec.is_some());
720 }
721
722 #[test]
723 fn global_registry_has_jpeg_baseline() {
724 let codec = GLOBAL_REGISTRY.find(transfer_syntaxes::JPEG_BASELINE.uid);
725 assert!(codec.is_some());
726 }
727
728 #[test]
729 fn global_registry_has_jpeg_extended() {
730 let codec = GLOBAL_REGISTRY.find(transfer_syntaxes::JPEG_EXTENDED.uid);
731 assert!(codec.is_some());
732 }
733
734 #[test]
735 fn global_registry_has_jpeg_ls() {
736 let codec = GLOBAL_REGISTRY.find(transfer_syntaxes::JPEG_LS_LOSSLESS.uid);
737 assert!(codec.is_some());
738 }
739
740 #[test]
741 fn global_registry_has_jpeg_lossless_encoder() {
742 let codec = GLOBAL_REGISTRY.find(transfer_syntaxes::JPEG_LOSSLESS.uid);
743 assert!(codec.is_some());
744 let sv1 = GLOBAL_REGISTRY.find(transfer_syntaxes::JPEG_LOSSLESS_SV1.uid);
745 assert!(sv1.is_some());
746 }
747
748 #[test]
749 fn global_registry_has_htj2k_decoder() {
750 let codec = GLOBAL_REGISTRY.find_decoder(transfer_syntaxes::HIGH_THROUGHPUT_JPEG_2000.uid);
751 assert!(codec.is_some());
752 }
753
754 #[test]
755 fn global_registry_exposes_htj2k_encoder_variants() {
756 let lossless =
757 GLOBAL_REGISTRY.find(transfer_syntaxes::HIGH_THROUGHPUT_JPEG_2000_LOSSLESS_ONLY.uid);
758 let generic = GLOBAL_REGISTRY.find(transfer_syntaxes::HIGH_THROUGHPUT_JPEG_2000.uid);
759 assert!(lossless.is_some());
760 assert!(generic.is_some());
761 }
762
763 #[test]
764 fn global_registry_keeps_htj2k_rpcl_decode_only() {
765 let codec = GLOBAL_REGISTRY
766 .find(transfer_syntaxes::HIGH_THROUGHPUT_JPEG_2000_RPCL_LOSSLESS_ONLY.uid);
767 assert!(codec.is_none());
768 }
769
770 #[test]
771 fn unknown_uid_returns_none() {
772 let codec = GLOBAL_REGISTRY.find("1.2.3.4.5.999");
773 assert!(codec.is_none());
774 }
775
776 #[test]
777 fn find_required_returns_error_for_unknown() {
778 let result = GLOBAL_REGISTRY.find_required("1.9.9.9.9");
779 assert!(matches!(result, Err(DcmError::NoCodec { .. })));
780 }
781
782 #[test]
785 fn codec_registry_can_decode_rle() {
786 assert!(can_decode(transfer_syntaxes::RLE_LOSSLESS.uid));
787 }
788
789 #[test]
790 fn codec_registry_can_decode_jpeg_baseline() {
791 assert!(can_decode(transfer_syntaxes::JPEG_BASELINE.uid));
792 }
793
794 #[test]
795 fn codec_registry_can_decode_htj2k() {
796 assert!(can_decode(transfer_syntaxes::HIGH_THROUGHPUT_JPEG_2000.uid));
797 }
798
799 #[test]
800 fn codec_registry_cannot_decode_unknown() {
801 assert!(!can_decode("1.2.3.4.5.999"));
802 }
803
804 #[test]
805 fn supported_transfer_syntaxes_is_non_empty() {
806 let list = supported_decode_transfer_syntaxes();
807 assert!(!list.is_empty());
808 assert!(list.contains(&transfer_syntaxes::RLE_LOSSLESS.uid));
809 assert!(list.contains(&transfer_syntaxes::JPEG_BASELINE.uid));
810 assert!(list.contains(&transfer_syntaxes::HIGH_THROUGHPUT_JPEG_2000.uid));
811 }
812
813 #[test]
814 fn supported_encode_transfer_syntaxes_excludes_decode_only_codecs() {
815 let list = supported_encode_transfer_syntaxes();
816 assert!(list.contains(&transfer_syntaxes::RLE_LOSSLESS.uid));
817 assert!(list.contains(&transfer_syntaxes::JPEG_LOSSLESS.uid));
818 assert!(list.contains(&transfer_syntaxes::JPEG_LOSSLESS_SV1.uid));
819 assert!(list.contains(&transfer_syntaxes::JPEG_2000_LOSSLESS.uid));
820 assert!(list.contains(&transfer_syntaxes::HIGH_THROUGHPUT_JPEG_2000.uid));
821 assert!(
822 !list.contains(&transfer_syntaxes::HIGH_THROUGHPUT_JPEG_2000_RPCL_LOSSLESS_ONLY.uid)
823 );
824 }
825
826 #[test]
827 fn can_encode_distinguishes_decode_only_transfer_syntaxes() {
828 assert!(can_encode(transfer_syntaxes::JPEG_BASELINE.uid));
829 assert!(can_encode(transfer_syntaxes::JPEG_LS_LOSSLESS.uid));
830 assert!(can_encode(transfer_syntaxes::JPEG_LOSSLESS.uid));
831 assert!(can_encode(transfer_syntaxes::JPEG_LOSSLESS_SV1.uid));
832 assert!(!can_encode(
833 transfer_syntaxes::HIGH_THROUGHPUT_JPEG_2000_RPCL_LOSSLESS_ONLY.uid
834 ));
835 }
836
837 #[test]
838 fn rle_codec_roundtrip_via_registry() {
839 use crate::rle::rle_encode_frame;
840
841 let rows = 4u16;
842 let cols = 4u16;
843 let samples = 1u8;
844 let bits = 8u8;
845 let pixels: Vec<u8> = (0u8..16).collect();
846
847 let encoded_frame = rle_encode_frame(&pixels, rows, cols, samples, bits).unwrap();
848 let pixel_data = PixelData::Encapsulated {
849 offset_table: vec![0],
850 fragments: vec![encoded_frame],
851 };
852
853 let codec = GLOBAL_REGISTRY
854 .find(transfer_syntaxes::RLE_LOSSLESS.uid)
855 .unwrap();
856 let decoded = codec
857 .decode(&pixel_data, rows, cols, samples, bits)
858 .unwrap();
859 assert_eq!(&decoded[..16], &pixels[..]);
860 }
861
862 #[test]
863 fn jp2k_codec_multiframe_decode_via_registry() {
864 let rows = 4u16;
865 let cols = 4u16;
866 let samples = 1u8;
867 let bits = 8u8;
868 let frame_a: Vec<u8> = (0u8..16).collect();
869 let frame_b: Vec<u8> = (16u8..32).collect();
870
871 let encoded_a = crate::jp2k::encoder::encode_jp2k(
872 &frame_a,
873 cols as u32,
874 rows as u32,
875 bits,
876 samples,
877 true,
878 )
879 .unwrap();
880 let encoded_b = crate::jp2k::encoder::encode_jp2k(
881 &frame_b,
882 cols as u32,
883 rows as u32,
884 bits,
885 samples,
886 true,
887 )
888 .unwrap();
889
890 let pixel_data = PixelData::Encapsulated {
891 offset_table: vec![],
892 fragments: vec![encoded_a, encoded_b],
893 };
894
895 let codec = GLOBAL_REGISTRY
896 .find(transfer_syntaxes::JPEG_2000_LOSSLESS.uid)
897 .unwrap();
898 let decoded = codec
899 .decode(&pixel_data, rows, cols, samples, bits)
900 .unwrap();
901
902 let mut expected = frame_a;
903 expected.extend_from_slice(&frame_b);
904 assert_eq!(decoded, expected);
905 }
906
907 #[test]
908 fn jpeg_ls_codec_encode_uses_bits_stored_precision() {
909 let rows = 4u16;
910 let cols = 4u16;
911 let samples = 1u8;
912 let bits_allocated = 16u8;
913 let bits_stored = 12u8;
914 let mut pixels = Vec::with_capacity(32);
915 for i in 0u16..16 {
916 pixels.extend_from_slice(&((i * 257) & 0x0FFF).to_le_bytes());
917 }
918
919 let codec = GLOBAL_REGISTRY
920 .find(transfer_syntaxes::JPEG_LS_LOSSLESS.uid)
921 .unwrap();
922 let encoded = codec
923 .encode(&pixels, rows, cols, samples, bits_allocated, bits_stored)
924 .unwrap();
925 let fragment = match encoded {
926 PixelData::Encapsulated { fragments, .. } => fragments.into_iter().next().unwrap(),
927 PixelData::Native { .. } => panic!("expected encapsulated pixel data"),
928 };
929
930 let decoded = crate::jpeg_ls::decoder::decode_jpeg_ls(&fragment).unwrap();
931 assert_eq!(decoded.bits_per_sample, bits_stored);
932 assert_eq!(decoded.pixels, pixels);
933 }
934
935 #[test]
936 fn jp2k_codec_encode_uses_bits_stored_precision() {
937 let rows = 4u16;
938 let cols = 4u16;
939 let samples = 1u8;
940 let bits_allocated = 16u8;
941 let bits_stored = 12u8;
942 let mut pixels = Vec::with_capacity(32);
943 for i in 0u16..16 {
944 pixels.extend_from_slice(&((i * 257) & 0x0FFF).to_le_bytes());
945 }
946
947 let codec = GLOBAL_REGISTRY
948 .find(transfer_syntaxes::JPEG_2000_LOSSLESS.uid)
949 .unwrap();
950 let encoded = codec
951 .encode(&pixels, rows, cols, samples, bits_allocated, bits_stored)
952 .unwrap();
953 let fragment = match encoded {
954 PixelData::Encapsulated { fragments, .. } => fragments.into_iter().next().unwrap(),
955 PixelData::Native { .. } => panic!("expected encapsulated pixel data"),
956 };
957
958 let decoded = crate::jp2k::decoder::decode_jp2k(&fragment).unwrap();
959 assert_eq!(decoded.bits_per_sample, bits_stored);
960 assert_eq!(decoded.pixels, pixels);
961 }
962
963 #[test]
964 fn htj2k_codec_encode_uses_bits_stored_precision() {
965 let rows = 4u16;
966 let cols = 4u16;
967 let samples = 1u8;
968 let bits_allocated = 16u8;
969 let bits_stored = 12u8;
970 let mut pixels = Vec::with_capacity(32);
971 for _ in 0..16 {
972 pixels.extend_from_slice(&2048u16.to_le_bytes());
973 }
974
975 let codec = GLOBAL_REGISTRY
976 .find(transfer_syntaxes::HIGH_THROUGHPUT_JPEG_2000_LOSSLESS_ONLY.uid)
977 .unwrap();
978 let encoded = codec
979 .encode(&pixels, rows, cols, samples, bits_allocated, bits_stored)
980 .unwrap();
981 let fragment = match encoded {
982 PixelData::Encapsulated { fragments, .. } => fragments.into_iter().next().unwrap(),
983 PixelData::Native { .. } => panic!("expected encapsulated pixel data"),
984 };
985
986 assert!(fragment.windows(2).any(|window| window == [0xFF, 0x50]));
987 let decoded = crate::jp2k::decoder::decode_jp2k(&fragment).unwrap();
988 assert_eq!(decoded.bits_per_sample, bits_stored);
989 assert_eq!(decoded.pixels, pixels);
990 }
991}