oxigdal_jpeg2000/lib.rs
1//! Pure Rust JPEG2000 (JP2/J2K) driver for OxiGDAL
2//!
3//! This crate provides a Pure Rust implementation of JPEG2000 image decoding,
4//! supporting both JP2 (JPEG2000 Part 1) and raw J2K codestream formats.
5//!
6//! # Features
7//!
8//! - Pure Rust implementation (no C/C++ dependencies)
9//! - **Full JP2 box structure parsing** with support for all standard boxes
10//! - JPEG2000 codestream decoding
11//! - Wavelet transforms (5/3 reversible and 9/7 irreversible)
12//! - Multi-component images (RGB, RGBA, grayscale)
13//! - Tiling support with partial tile decoding
14//! - **Complete metadata extraction** (file type, resolution, color spec, XML, UUID)
15//! - **Error resilience modes** for handling corrupted files (Basic and Full)
16//! - **Progressive decoding** with quality layer support
17//! - **Region of Interest (ROI) decoding** for spatial and resolution-level extraction
18//!
19//! # Limitations
20//!
21//! This is a reference implementation with simplified decoding for common cases.
22//! Full JPEG2000 compliance (especially tier-1 EBCOT encoding) requires extensive
23//! additional implementation. For production use with complex JPEG2000 files,
24//! consider using a more complete decoder.
25//!
26//! # Examples
27//!
28//! ## Basic Usage
29//!
30//! ```no_run
31//! use oxigdal_jpeg2000::Jpeg2000Reader;
32//! use std::fs::File;
33//! use std::io::BufReader;
34//!
35//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
36//! let file = File::open("image.jp2")?;
37//! let reader = BufReader::new(file);
38//!
39//! let mut decoder = Jpeg2000Reader::new(reader)?;
40//! decoder.parse_headers()?;
41//!
42//! let width = decoder.width()?;
43//! let height = decoder.height()?;
44//! println!("Image size: {}x{}", width, height);
45//!
46//! let info = decoder.info()?;
47//! println!("Color space: {:?}", info.color_space);
48//! println!("Decomposition levels: {}", info.num_decomposition_levels);
49//!
50//! // Access metadata
51//! if let Some(res) = decoder.capture_resolution_dpi() {
52//! println!("Resolution: {:.1} x {:.1} DPI", res.0, res.1);
53//! }
54//!
55//! // Note: Full decoding not yet implemented
56//! // let rgb_data = decoder.decode_rgb()?;
57//! # Ok(())
58//! # }
59//! ```
60//!
61//! ## Progressive Decoding
62//!
63//! ```no_run
64//! use oxigdal_jpeg2000::Jpeg2000Reader;
65//! use std::fs::File;
66//! use std::io::BufReader;
67//!
68//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
69//! let file = File::open("image.jp2")?;
70//! let reader = BufReader::new(file);
71//! let mut decoder = Jpeg2000Reader::new(reader)?;
72//! decoder.parse_headers()?;
73//!
74//! // Decode progressively layer by layer
75//! let mut progressive = decoder.decode_progressive()?;
76//! while let Some(image_data) = progressive.next_layer()? {
77//! println!("Decoded layer {} of {}",
78//! progressive.current_layer(),
79//! progressive.total_layers());
80//! // Display or process intermediate quality image
81//! }
82//! # Ok(())
83//! # }
84//! ```
85//!
86//! ## Region of Interest Decoding
87//!
88//! ```no_run
89//! use oxigdal_jpeg2000::Jpeg2000Reader;
90//! use std::fs::File;
91//! use std::io::BufReader;
92//!
93//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
94//! let file = File::open("image.jp2")?;
95//! let reader = BufReader::new(file);
96//! let mut decoder = Jpeg2000Reader::new(reader)?;
97//! decoder.parse_headers()?;
98//!
99//! // Decode only a specific region (more efficient than full decode)
100//! let region = decoder.decode_region(100, 100, 256, 256)?;
101//!
102//! // Decode at lower resolution for thumbnail
103//! let thumbnail = decoder.decode_region_at_resolution(0, 0, 64, 64, 2)?;
104//! # Ok(())
105//! # }
106//! ```
107//!
108//! ## Error Resilience
109//!
110//! ```no_run
111//! use oxigdal_jpeg2000::{Jpeg2000Reader, ResilienceMode};
112//! use std::fs::File;
113//! use std::io::BufReader;
114//!
115//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
116//! let file = File::open("corrupted.jp2")?;
117//! let reader = BufReader::new(file);
118//! let mut decoder = Jpeg2000Reader::new(reader)?;
119//!
120//! // Enable error resilience for corrupted files
121//! decoder.enable_full_error_resilience();
122//!
123//! // Parser will attempt to recover from errors
124//! decoder.parse_headers()?;
125//! # Ok(())
126//! # }
127//! ```
128//!
129//! # Architecture
130//!
131//! The decoder is organized into several layers:
132//!
133//! - **Box Reader** ([`box_reader`]): Parses JP2 box structure
134//! - **Codestream** ([`codestream`]): Parses JPEG2000 codestream markers
135//! - **Tier-2** ([`tier2`]): Packet decoding and layer management
136//! - **Tier-1** ([`tier1`]): Code-block decoding (EBCOT)
137//! - **Wavelet** ([`wavelet`]): Inverse wavelet transforms
138//! - **Color** ([`color`]): Color space conversions
139//! - **Metadata** ([`metadata`]): JP2 metadata boxes
140//! - **Reader** ([`reader`]): High-level decoding interface
141//!
142//! # JPEG2000 Standard
143//!
144//! JPEG2000 is defined in ISO/IEC 15444-1:2019. This implementation follows
145//! the standard for basic decoding functionality.
146//!
147//! # Performance Considerations
148//!
149//! - Wavelet transforms are implemented with minimal optimizations
150//! - SIMD optimizations are not yet implemented
151//! - Memory usage is not optimized for large images
152//! - For high-performance applications, consider using native implementations
153//!
154//! # TODO
155//!
156//! - Complete tier-1 EBCOT decoder implementation (currently placeholder)
157//! - Add writing/encoding support for JP2/J2K files
158//! - SIMD optimization for wavelet transforms
159//! - Parallel tile decoding with multi-threading support
160//! - JPX (JPEG2000 Part 2) extended features
161//! - Memory-mapped file support for large images
162//!
163//! # Recently Implemented
164//!
165//! - ✅ Full JP2 format support (all standard boxes, complete metadata parsing)
166//! - ✅ Error resilience modes (None, Basic, Full) with packet-level error handling
167//! - ✅ Progressive decoding with quality layer support
168//! - ✅ ROI decoding support (spatial regions and resolution levels)
169
170#![warn(missing_docs)]
171#![deny(unsafe_code)]
172
173pub mod box_reader;
174pub mod codestream;
175pub mod color;
176pub mod error;
177pub mod jp2_boxes;
178pub mod metadata;
179pub mod reader;
180pub mod tier1;
181pub mod tier2;
182pub mod wavelet;
183
184// Re-exports
185pub use error::{Jpeg2000Error, ResilienceMode, Result};
186pub use jp2_boxes::{BoxType as Jp2BoxType, ColorSpace, Jp2Box, Jp2Parser};
187pub use reader::{ImageInfo, Jpeg2000Reader, ProgressiveDecoder};
188pub use tier2::progression::{CodeBlockAddress, ProgressionIterator};
189pub use tier2::rate_control::{QualityLayer, RateController, SlopeEntry};
190pub use tier2::roi::{RoiMap, RoiShift};
191
192/// Version information
193pub const VERSION: &str = env!("CARGO_PKG_VERSION");
194
195/// Check if a file is likely a JP2 file based on magic bytes
196///
197/// # Example
198///
199/// ```
200/// use oxigdal_jpeg2000::is_jp2;
201/// use std::io::Cursor;
202///
203/// let data = vec![
204/// 0x00, 0x00, 0x00, 0x0C,
205/// 0x6A, 0x50, 0x20, 0x20,
206/// 0x0D, 0x0A, 0x87, 0x0A,
207/// ];
208///
209/// assert!(is_jp2(&mut Cursor::new(data)).expect("check failed"));
210/// ```
211pub fn is_jp2<R: std::io::Read>(reader: &mut R) -> std::io::Result<bool> {
212 let mut magic = [0u8; 12];
213 match reader.read_exact(&mut magic) {
214 Ok(()) => Ok(&magic[4..8] == b"jP " && magic[8..12] == [0x0D, 0x0A, 0x87, 0x0A]),
215 Err(ref e) if e.kind() == std::io::ErrorKind::UnexpectedEof => Ok(false),
216 Err(e) => Err(e),
217 }
218}
219
220/// Check if a file is likely a J2K codestream based on SOC marker
221///
222/// # Example
223///
224/// ```
225/// use oxigdal_jpeg2000::is_j2k;
226/// use std::io::Cursor;
227///
228/// let data = vec![0xFF, 0x4F]; // SOC marker
229///
230/// assert!(is_j2k(&mut Cursor::new(data)).expect("check failed"));
231/// ```
232pub fn is_j2k<R: std::io::Read>(reader: &mut R) -> std::io::Result<bool> {
233 let mut magic = [0u8; 2];
234 reader.read_exact(&mut magic)?;
235
236 Ok(magic == [0xFF, 0x4F])
237}
238
239#[cfg(test)]
240mod tests {
241 use super::*;
242 use std::io::Cursor;
243
244 #[test]
245 fn test_is_jp2() {
246 let data = vec![
247 0x00, 0x00, 0x00, 0x0C, 0x6A, 0x50, 0x20, 0x20, 0x0D, 0x0A, 0x87, 0x0A,
248 ];
249
250 let mut cursor = Cursor::new(data);
251 assert!(is_jp2(&mut cursor).expect("check failed"));
252 }
253
254 #[test]
255 fn test_is_j2k() {
256 let data = vec![0xFF, 0x4F];
257
258 let mut cursor = Cursor::new(data);
259 assert!(is_j2k(&mut cursor).expect("check failed"));
260 }
261
262 #[test]
263 fn test_version() {
264 assert!(!VERSION.is_empty());
265 }
266}