diff --git a/transfer-syntax-registry/Cargo.toml b/transfer-syntax-registry/Cargo.toml index 6b0913ac..495a90fc 100644 --- a/transfer-syntax-registry/Cargo.toml +++ b/transfer-syntax-registry/Cargo.toml @@ -20,12 +20,18 @@ inventory-registry = ['dicom-encoding/inventory-registry'] deflate = ["dep:flate2"] # natively implemented image encodings native = ["jpeg", "rle"] -# native implementations that work on Windows +# (DEPRECATED) native implementations that work on Windows native_windows = ["jpeg", "rle"] # native JPEG support jpeg = ["jpeg-decoder", "jpeg-encoder"] -# native JPEG XL support -jpegxl = ["dep:jxl-oxide", "dep:zune-jpegxl", "dep:zune-core"] + + +# native JPEG XL decoding via jxl-oxide +jxl-oxide = ["dep:jxl-oxide"] +# native JPEG XL encoding via zune-jpegxl +zune-jpegxl = ["dep:zune-jpegxl", "dep:zune-core"] + +jpegxl = ["jxl-oxide", "zune-jpegxl"] # JPEG 2000 support via the OpenJPEG Rust port, # works on Linux and a few other platforms diff --git a/transfer-syntax-registry/src/adapters/jpegxl.rs b/transfer-syntax-registry/src/adapters/jpegxl.rs index c3fe7e52..2ce9ce26 100644 --- a/transfer-syntax-registry/src/adapters/jpegxl.rs +++ b/transfer-syntax-registry/src/adapters/jpegxl.rs @@ -2,23 +2,28 @@ use dicom_core::ops::{AttributeAction, AttributeOp}; use dicom_core::Tag; -use dicom_encoding::adapters::{ - decode_error, encode_error, DecodeResult, PixelDataObject, PixelDataReader, PixelDataWriter, -}; +use dicom_encoding::adapters::PixelDataObject; +#[cfg(feature = "jxl-oxide")] +use dicom_encoding::adapters::{decode_error, DecodeResult, PixelDataReader}; +#[cfg(feature = "zune-jpegxl")] +use dicom_encoding::adapters::{encode_error, PixelDataWriter}; + use dicom_encoding::snafu::prelude::*; +#[cfg(feature = "jxl-oxide")] use jxl_oxide::JxlImage; -use zune_core::bit_depth::BitDepth; -use zune_core::colorspace::ColorSpace; -use zune_core::options::EncoderOptions; +#[cfg(feature = "zune-jpegxl")] +use zune_core::{bit_depth::BitDepth, colorspace::ColorSpace, options::EncoderOptions}; /// Base pixel data adapter (decoder and encoder) for transfer syntaxes based on JPEG XL. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct JpegXlAdapter; /// Pixel data encoder specifically for JPEG XL lossless compression. +#[cfg(feature = "zune-jpegxl")] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct JpegXlLosslessEncoder; +#[cfg(feature = "jxl-oxide")] impl PixelDataReader for JpegXlAdapter { /// Decode a single frame in JPEG XL from a DICOM object. fn decode_frame( @@ -152,6 +157,7 @@ impl PixelDataReader for JpegXlAdapter { } } +#[cfg(feature = "zune-jpegxl")] impl PixelDataWriter for JpegXlAdapter { fn encode_frame( &self, @@ -281,6 +287,7 @@ impl PixelDataWriter for JpegXlAdapter { } } +#[cfg(feature = "zune-jpegxl")] impl PixelDataWriter for JpegXlLosslessEncoder { fn encode_frame( &self, diff --git a/transfer-syntax-registry/src/adapters/mod.rs b/transfer-syntax-registry/src/adapters/mod.rs index 16027777..139fe000 100644 --- a/transfer-syntax-registry/src/adapters/mod.rs +++ b/transfer-syntax-registry/src/adapters/mod.rs @@ -3,35 +3,38 @@ //! Additional support for certain transfer syntaxes //! can be added via Cargo features. //! -//! - [`jpeg`](jpeg) provides native JPEG decoding +//! - [`jpeg`] provides native JPEG decoding //! (baseline and lossless) //! and encoding (baseline). //! Requires the `jpeg` feature, //! enabled by default. -//! - [`jpeg2k`](jpeg2k) contains JPEG 2000 support, +//! - [`jpeg2k`] contains JPEG 2000 support, //! which is currently available through [OpenJPEG]. -//! The `openjp2` feature provides native JPEG 2000 decoding +//! Use feature `openjpeg-sys` +//! to statically link to the OpenJPEG reference implementation, +//! thus providing JPEG 2000 decoding. +//! Alternatively, feature `openjp2` provides native JPEG 2000 decoding //! via the [Rust port of OpenJPEG][OpenJPEG-rs], -//! which works on Linux and Mac OS, but not on Windows. -//! Alternatively, enable the `openjpeg-sys` feature -//! to statically link to the OpenJPEG reference implementation. -//! `openjp2` is enabled by the feature `native`. -//! To build on Windows, enable `native_windows` instead. -//! - [`jpegxl`](jpegxl) provides JPEG XL decoding and encoding, -//! through `jxl-oxide` and `zune-jpegxl`, respectively. -//! - [`rle_lossless`](rle_lossless) provides native RLE lossless decoding. +//! which is maintained separately. +//! - [`jpegxl`] contains JPEG XL support. +//! `jxl-oxide` enables decoding via [jxl-oxide], +//! and `zune-jpegxl` adds lossless encoding via [zune-jpegxl]. +//! Currently, the `jpegxl` feature enables both. +//! - [`rle_lossless`] provides native RLE lossless decoding. //! Requires the `rle` feature, //! enabled by default. //! //! [OpenJPEG]: https://github.com/uclouvain/openjpeg //! [OpenJPEG-rs]: https://crates.io/crates/openjp2 +//! [jxl-oxide]: https://crates.io/crates/jxl-oxide +//! [zune-jpegxl]: https://crates.io/crates/zune-jpegxl #[cfg(feature = "jpeg")] pub mod jpeg; #[cfg(any(feature = "openjp2", feature = "openjpeg-sys"))] pub mod jpeg2k; #[cfg(feature = "charls")] pub mod jpegls; -#[cfg(feature = "jpegxl")] +#[cfg(any(feature = "jxl-oxide", feature = "zune-jpegxl"))] pub mod jpegxl; #[cfg(feature = "rle")] pub mod rle_lossless; @@ -59,6 +62,6 @@ pub mod rle {} pub mod jpegls {} /// **Note:** This module is a stub. -/// Enable the `jpegxl` feature to use this module. -#[cfg(not(feature = "jpegxl"))] +/// Enable the features `jxl-oxide` or `zune-jpegxl` to use this module. +#[cfg(not(any(feature = "jxl-oxide", feature = "zune-jpegxl")))] pub mod jpegxl {} diff --git a/transfer-syntax-registry/src/lib.rs b/transfer-syntax-registry/src/lib.rs index 11bc25ed..c5237bda 100644 --- a/transfer-syntax-registry/src/lib.rs +++ b/transfer-syntax-registry/src/lib.rs @@ -68,9 +68,9 @@ //! | High-Throughput JPEG 2000 with RPCL Options (Lossless Only) | Cargo feature `openjp2` or `openjpeg-sys` | x | //! | High-Throughput JPEG 2000 | Cargo feature `openjp2` or `openjpeg-sys` | x | //! | JPIP HTJ2K Referenced Deflate | Cargo feature `deflate` | ✓ | -//! | JPEG XL Lossless | Cargo feature `jpegxl` | ✓ | -//! | JPEG XL Recompression | Cargo feature `jpegxl` | x | -//! | JPEG XL | Cargo feature `jpegxl` | ✓ | +//! | JPEG XL Lossless | Cargo feature `jxl-oxide` | ✓ (Cargo feature `zune-jpegxl`) | +//! | JPEG XL Recompression | Cargo feature `jxl-oxide` | x | +//! | JPEG XL | Cargo feature `jxl-oxide` | ✓ (Cargo feature `zune-jpegxl`) | //! | RLE Lossless | Cargo feature `rle` | x | //! //! Cargo features behind `native` (`jpeg`, `rle`) are added by default. @@ -92,8 +92,7 @@ //! - `openjp2` provides a binding to a computer-translated Rust port of OpenJPEG. //! Due to the nature of this crate, //! it might not work on all modern platforms. -//! - `jpegxl` adds JPEG XL support using `jxl-oxide` for decoding -//! and `zune-jpegxl` for encoding. +//! - `jxl-oxide` and `zune-jpegxl` offer JPEG XL decoding and encoding. //! //! Transfer syntaxes which are not supported, //! either due to being unable to read the data set diff --git a/transfer-syntax-registry/tests/jpegxl.rs b/transfer-syntax-registry/tests/jpegxl.rs index 0c37f705..829dafdc 100644 --- a/transfer-syntax-registry/tests/jpegxl.rs +++ b/transfer-syntax-registry/tests/jpegxl.rs @@ -1,5 +1,5 @@ //! Test suite for JPEG XL pixel data reading and writing -#![cfg(feature = "jpegxl")] +#![cfg(any(feature = "jxl-oxide", feature = "zune-jpegxl"))] mod adapters; @@ -11,12 +11,14 @@ use std::{ use adapters::TestDataObject; use dicom_core::value::PixelFragmentSequence; -use dicom_encoding::{ - adapters::{EncodeOptions, PixelDataReader, PixelDataWriter}, - Codec, -}; -use dicom_transfer_syntax_registry::entries::{JPEG_XL, JPEG_XL_LOSSLESS}; +use dicom_encoding::Codec; + +#[cfg(feature = "jxl-oxide")] +use dicom_encoding::adapters::PixelDataReader; +#[cfg(feature = "zune-jpegxl")] +use dicom_encoding::adapters::{EncodeOptions, PixelDataWriter}; +#[cfg(feature = "jxl-oxide")] fn read_data_piece(test_file: impl AsRef, offset: u64, length: usize) -> Vec { let mut file = File::open(test_file).unwrap(); // single fragment found in file data offset 0x6b6, 3314 bytes @@ -26,6 +28,7 @@ fn read_data_piece(test_file: impl AsRef, offset: u64, length: usize) -> V buf } +#[cfg(feature = "jxl-oxide")] fn check_rgb_pixel(pixels: &[u8], columns: u16, x: u16, y: u16, expected_pixel: [u8; 3]) { let i = (y as usize * columns as usize + x as usize) * 3; let got = [pixels[i], pixels[i + 1], pixels[i + 2]]; @@ -35,6 +38,7 @@ fn check_rgb_pixel(pixels: &[u8], columns: u16, x: u16, y: u16, expected_pixel: ); } +#[cfg(feature = "jxl-oxide")] fn check_rgb_pixel_approx(pixels: &[u8], columns: u16, x: u16, y: u16, pixel: [u8; 3], margin: u8) { let i = (y as usize * columns as usize + x as usize) * 3; @@ -59,6 +63,7 @@ fn check_rgb_pixel_approx(pixels: &[u8], columns: u16, x: u16, y: u16, pixel: [u ); } +#[cfg(feature = "jxl-oxide")] #[test] #[ignore] fn read_jpeg_xl_1() { @@ -86,7 +91,7 @@ fn read_jpeg_xl_1() { // instantiate JpegAdapter and call decode_frame - let Codec::EncapsulatedPixelData(Some(adapter), _) = JPEG_XL.codec() else { + let Codec::EncapsulatedPixelData(Some(adapter), _) = dicom_transfer_syntax_registry::entries::JPEG_XL.codec() else { panic!("JPEG XL pixel data reader not found") }; @@ -114,6 +119,7 @@ fn read_jpeg_xl_1() { check_rgb_pixel_approx(&dest, 100, 16, 49, [4, 4, 226], err_margin); } +#[cfg(feature = "jxl-oxide")] #[test] #[ignore] fn read_jpeg_xl_lossless_1() { @@ -141,7 +147,7 @@ fn read_jpeg_xl_lossless_1() { // instantiate JpegAdapter and call decode_frame - let Codec::EncapsulatedPixelData(Some(adapter), _) = JPEG_XL.codec() else { + let Codec::EncapsulatedPixelData(Some(adapter), _) = dicom_transfer_syntax_registry::entries::JPEG_XL.codec() else { panic!("JPEG XL pixel data reader not found") }; @@ -168,6 +174,7 @@ fn read_jpeg_xl_lossless_1() { } /// writing to JPEG XL lossless and back should yield exactly the same pixel data +#[cfg(all(feature = "jxl-oxide", feature = "zune-jpegxl"))] #[test] fn write_and_read_jpeg_xl() { let rows: u16 = 128; @@ -219,7 +226,7 @@ fn write_and_read_jpeg_xl() { // fetch adapters for JPEG XL lossless - let Codec::EncapsulatedPixelData(Some(reader), Some(writer)) = JPEG_XL_LOSSLESS.codec() else { + let Codec::EncapsulatedPixelData(Some(reader), Some(writer)) = dicom_transfer_syntax_registry::entries::JPEG_XL_LOSSLESS.codec() else { panic!("JPEG XL pixel data adapters not found") };