Skip to main content

openjph_core/
lib.rs

1//! **OpenJPH-RS** — Pure Rust HTJ2K (JPEG 2000 Part 15) codec.
2//!
3//! This crate is a faithful port of the [OpenJPH](https://github.com/aous72/OpenJPH)
4//! C++ library (v0.26.3) providing encoding and decoding of HTJ2K codestreams as
5//! defined in ISO/IEC 15444-15.
6//!
7//! # Overview
8//!
9//! The main entry point is [`codestream::Codestream`], which provides both the
10//! encoder (write) and decoder (read) pipeline. Image parameters are configured
11//! through marker segment types in the [`params`] module.
12//!
13//! # Quick Start — Encoding
14//!
15//! ```rust
16//! use openjph_core::codestream::Codestream;
17//! use openjph_core::file::MemOutfile;
18//! use openjph_core::types::{Point, Size};
19//!
20//! let (width, height) = (8u32, 8u32);
21//! let pixels: Vec<i32> = vec![128; (width * height) as usize];
22//!
23//! let mut cs = Codestream::new();
24//! cs.access_siz_mut().set_image_extent(Point::new(width, height));
25//! cs.access_siz_mut().set_num_components(1);
26//! cs.access_siz_mut().set_comp_info(0, Point::new(1, 1), 8, false);
27//! cs.access_siz_mut().set_tile_size(Size::new(width, height));
28//! cs.access_cod_mut().set_num_decomposition(0);
29//! cs.access_cod_mut().set_reversible(true);
30//! cs.access_cod_mut().set_color_transform(false);
31//! cs.set_planar(0);
32//!
33//! let mut outfile = MemOutfile::new();
34//! cs.write_headers(&mut outfile, &[]).unwrap();
35//! for y in 0..height as usize {
36//!     let start = y * width as usize;
37//!     cs.exchange(&pixels[start..start + width as usize], 0).unwrap();
38//! }
39//! cs.flush(&mut outfile).unwrap();
40//!
41//! let encoded = outfile.get_data();
42//! assert!(encoded.len() > 20);
43//! ```
44//!
45//! # Quick Start — Decoding
46//!
47//! ```rust
48//! # use openjph_core::codestream::Codestream;
49//! # use openjph_core::file::{MemOutfile, MemInfile};
50//! # use openjph_core::types::{Point, Size};
51//! # let (width, height) = (8u32, 8u32);
52//! # let pixels: Vec<i32> = vec![128; (width * height) as usize];
53//! # let mut cs = Codestream::new();
54//! # cs.access_siz_mut().set_image_extent(Point::new(width, height));
55//! # cs.access_siz_mut().set_num_components(1);
56//! # cs.access_siz_mut().set_comp_info(0, Point::new(1, 1), 8, false);
57//! # cs.access_siz_mut().set_tile_size(Size::new(width, height));
58//! # cs.access_cod_mut().set_num_decomposition(0);
59//! # cs.access_cod_mut().set_reversible(true);
60//! # cs.access_cod_mut().set_color_transform(false);
61//! # cs.set_planar(0);
62//! # let mut outfile = MemOutfile::new();
63//! # cs.write_headers(&mut outfile, &[]).unwrap();
64//! # for y in 0..height as usize {
65//! #     let start = y * width as usize;
66//! #     cs.exchange(&pixels[start..start + width as usize], 0).unwrap();
67//! # }
68//! # cs.flush(&mut outfile).unwrap();
69//! # let encoded = outfile.get_data().to_vec();
70//! let mut infile = MemInfile::new(&encoded);
71//! let mut decoder = Codestream::new();
72//! decoder.read_headers(&mut infile).unwrap();
73//! decoder.create(&mut infile).unwrap();
74//!
75//! for _y in 0..height {
76//!     let line = decoder.pull(0).expect("expected decoded line");
77//!     assert_eq!(line.len(), width as usize);
78//! }
79//! ```
80//!
81//! # Modules
82//!
83//! | Module | Description |
84//! |--------|-------------|
85//! | [`types`] | Numeric aliases, geometric primitives (`Size`, `Point`, `Rect`) |
86//! | [`error`] | Error types ([`OjphError`]) and [`Result`] alias |
87//! | [`message`] | Diagnostic message dispatch (info/warn/error) |
88//! | [`arch`] | CPU feature detection and alignment constants |
89//! | [`mem`] | Aligned allocators and line buffers |
90//! | [`file`](mod@file) | I/O traits and file/memory stream implementations |
91//! | [`params`] | JPEG 2000 marker segment types (SIZ, COD, QCD, NLT, …) |
92//! | [`codestream`] | Main codec interface ([`Codestream`](codestream::Codestream)) |
93//! | [`arg`] | Minimal CLI argument interpreter |
94//! | [`coding`] | HTJ2K block entropy coding (internal) |
95//! | [`transform`] | Wavelet and color transforms (internal) |
96
97pub mod arch;
98pub mod arg;
99pub mod codestream;
100pub mod coding;
101pub mod error;
102pub mod file;
103pub mod mem;
104pub mod message;
105pub mod params;
106pub mod transform;
107pub mod types;
108
109pub use error::{OjphError, Result};
110pub use types::*;
111
112#[cfg(test)]
113mod pipeline_tests {
114    use crate::codestream::Codestream;
115    use crate::file::{MemInfile, MemOutfile};
116    use crate::types::*;
117
118    /// Encode an 8×8 image through the full pipeline, decode it, and verify
119    /// the round-trip produces values within CUP tolerance.
120    #[test]
121    fn roundtrip_8x8_single_component() {
122        let width = 8u32;
123        let height = 8u32;
124
125        // Use pixel value 131 (level-shifted: 3, magnitude=3, odd → exact with p=1)
126        let image: Vec<Vec<i32>> = vec![vec![131i32; width as usize]; height as usize];
127
128        // --- Encode ---
129        let mut cs = Codestream::new();
130        cs.access_siz_mut()
131            .set_image_extent(Point::new(width, height));
132        cs.access_siz_mut().set_num_components(1);
133        cs.access_siz_mut()
134            .set_comp_info(0, Point::new(1, 1), 8, false);
135        cs.access_siz_mut().set_tile_size(Size::new(width, height));
136        cs.access_cod_mut().set_num_decomposition(1);
137        cs.access_cod_mut().set_reversible(true);
138        cs.access_cod_mut().set_color_transform(false);
139        cs.set_planar(0);
140
141        let mut outfile = MemOutfile::new();
142        cs.write_headers(&mut outfile, &[]).unwrap();
143
144        for row in &image {
145            cs.exchange(row, 0).unwrap();
146        }
147        cs.flush(&mut outfile).unwrap();
148
149        let encoded = outfile.get_data().to_vec();
150        assert!(
151            encoded.len() > 20,
152            "encoded data too small: {} bytes",
153            encoded.len()
154        );
155
156        // --- Decode ---
157        let mut infile = MemInfile::new(&encoded);
158        let mut cs2 = Codestream::new();
159        cs2.read_headers(&mut infile).unwrap();
160        cs2.create(&mut infile).unwrap();
161
162        for (y, expected_row) in image.iter().enumerate() {
163            let line = cs2.pull(0).expect("expected decoded line");
164            assert_eq!(
165                line,
166                *expected_row,
167                "mismatch at row {y}: got {:?}, expected {:?}",
168                &line[..],
169                &expected_row[..]
170            );
171        }
172
173        // No more lines
174        assert!(cs2.pull(0).is_none());
175    }
176
177    /// Encode + decode a flat (constant value) image with exact roundtrip.
178    #[test]
179    fn roundtrip_8x8_constant() {
180        let width = 8u32;
181        let height = 8u32;
182        // Pixel value 125: level-shifted = -3, magnitude 3 (odd), roundtrips exactly
183        let image: Vec<Vec<i32>> = vec![vec![125i32; width as usize]; height as usize];
184
185        let mut cs = Codestream::new();
186        cs.access_siz_mut()
187            .set_image_extent(Point::new(width, height));
188        cs.access_siz_mut().set_num_components(1);
189        cs.access_siz_mut()
190            .set_comp_info(0, Point::new(1, 1), 8, false);
191        cs.access_siz_mut().set_tile_size(Size::new(width, height));
192        cs.access_cod_mut().set_num_decomposition(1);
193        cs.access_cod_mut().set_reversible(true);
194        cs.access_cod_mut().set_color_transform(false);
195        cs.set_planar(0);
196
197        let mut outfile = MemOutfile::new();
198        cs.write_headers(&mut outfile, &[]).unwrap();
199        for row in &image {
200            cs.exchange(row, 0).unwrap();
201        }
202        cs.flush(&mut outfile).unwrap();
203
204        let encoded = outfile.get_data().to_vec();
205        let mut infile = MemInfile::new(&encoded);
206        let mut cs2 = Codestream::new();
207        cs2.read_headers(&mut infile).unwrap();
208        cs2.create(&mut infile).unwrap();
209
210        for (y, expected_row) in image.iter().enumerate() {
211            let line = cs2.pull(0).expect("expected decoded line");
212            assert_eq!(line, *expected_row, "mismatch at row {y}");
213        }
214    }
215
216    /// Zero-decomposition (no DWT): test end-to-end with CUP-compatible values.
217    #[test]
218    fn roundtrip_8x8_no_dwt() {
219        let width = 8u32;
220        let height = 8u32;
221        // Use constant value for no-DWT test (pixel=131, level-shift=3, magnitude=3, p=1)
222        let image: Vec<Vec<i32>> = vec![vec![131i32; width as usize]; height as usize];
223
224        let mut cs = Codestream::new();
225        cs.access_siz_mut()
226            .set_image_extent(Point::new(width, height));
227        cs.access_siz_mut().set_num_components(1);
228        cs.access_siz_mut()
229            .set_comp_info(0, Point::new(1, 1), 8, false);
230        cs.access_siz_mut().set_tile_size(Size::new(width, height));
231        cs.access_cod_mut().set_num_decomposition(0);
232        cs.access_cod_mut().set_reversible(true);
233        cs.access_cod_mut().set_color_transform(false);
234        cs.set_planar(0);
235
236        let mut outfile = MemOutfile::new();
237        cs.write_headers(&mut outfile, &[]).unwrap();
238        for row in &image {
239            cs.exchange(row, 0).unwrap();
240        }
241        cs.flush(&mut outfile).unwrap();
242
243        let encoded = outfile.get_data().to_vec();
244        let mut infile = MemInfile::new(&encoded);
245        let mut cs2 = Codestream::new();
246        cs2.read_headers(&mut infile).unwrap();
247        cs2.create(&mut infile).unwrap();
248
249        for (y, expected_row) in image.iter().enumerate() {
250            let line = cs2.pull(0).expect("expected decoded line");
251            assert_eq!(line, *expected_row, "mismatch at row {y}");
252        }
253    }
254
255    /// Block coder roundtrip with corrected missing_msbs computation.
256    #[test]
257    fn block_coder_roundtrip_magnitude_3() {
258        use crate::coding::decoder32::decode_codeblock32;
259        use crate::coding::encoder::encode_codeblock32;
260
261        // Magnitude 3 with p=1 should roundtrip exactly
262        let mag = 3u32;
263        let u32_val = (1u32 << 31) | mag;
264        let buf = vec![u32_val; 16];
265        let num_bits = 32 - mag.leading_zeros();
266        let missing_msbs = 31 - num_bits; // p=1, msbs=29
267
268        let enc = encode_codeblock32(&buf, missing_msbs, 1, 4, 4, 4).unwrap();
269        assert!(enc.length >= 2);
270
271        let mut coded = enc.data.clone();
272        coded.resize(coded.len() + 16, 0);
273        let mut decoded = vec![0u32; 16];
274        decode_codeblock32(
275            &mut coded,
276            &mut decoded,
277            missing_msbs,
278            1,
279            enc.length,
280            0,
281            4,
282            4,
283            4,
284            false,
285        )
286        .unwrap();
287
288        for i in 0..16 {
289            assert_eq!(
290                decoded[i], buf[i],
291                "mismatch at {}: got 0x{:08X}, expected 0x{:08X}",
292                i, decoded[i], buf[i]
293            );
294        }
295    }
296}
297
298#[cfg(test)]
299mod debug_roundtrip_test {
300    use crate::codestream::Codestream;
301    use crate::coding::decoder32::decode_codeblock32;
302    use crate::coding::encoder::encode_codeblock32;
303    use crate::file::{MemInfile, MemOutfile};
304    use crate::types::{Point, Size};
305
306    #[test]
307    fn debug_no_dwt_value100() {
308        let width = 8u32;
309        let height = 8u32;
310        let pixels = vec![100i32; (width * height) as usize];
311
312        let mut cs = Codestream::new();
313        cs.access_siz_mut()
314            .set_image_extent(Point::new(width, height));
315        cs.access_siz_mut().set_num_components(1);
316        cs.access_siz_mut()
317            .set_comp_info(0, Point::new(1, 1), 8, false);
318        cs.access_siz_mut().set_tile_size(Size::new(width, height));
319        cs.access_cod_mut().set_num_decomposition(0);
320        cs.access_cod_mut().set_reversible(true);
321        cs.access_cod_mut().set_color_transform(false);
322        cs.set_planar(0);
323
324        let mut outfile = MemOutfile::new();
325        cs.write_headers(&mut outfile, &[]).unwrap();
326        for y in 0..height as usize {
327            let start = y * width as usize;
328            let end = start + width as usize;
329            cs.exchange(&pixels[start..end], 0).unwrap();
330        }
331        cs.flush(&mut outfile).unwrap();
332
333        let encoded = outfile.get_data().to_vec();
334        let mut infile = MemInfile::new(&encoded);
335        let mut cs2 = Codestream::new();
336        cs2.read_headers(&mut infile).unwrap();
337        cs2.create(&mut infile).unwrap();
338
339        for y in 0..height as usize {
340            let line = cs2.pull(0).expect("expected decoded line");
341            for x in 0..width as usize {
342                if line[x] != pixels[y * width as usize + x] {
343                    panic!(
344                        "Mismatch at ({},{}): expected {}, got {}",
345                        x,
346                        y,
347                        pixels[y * width as usize + x],
348                        line[x]
349                    );
350                }
351            }
352        }
353    }
354
355    #[test]
356    fn debug_no_dwt_33x33_packet_preserves_block_bytes() {
357        let width = 33u32;
358        let height = 33u32;
359        let mut image = Vec::with_capacity((width * height) as usize);
360        for y in 0..height {
361            for x in 0..width {
362                image.push(((3 * x + 7 * y) & 0xFF) as i32);
363            }
364        }
365
366        let kmax = 8u32;
367        let shift = 31 - kmax;
368        let missing_msbs = kmax - 1;
369        let stride = 64u32;
370        let nominal_h = 64u32;
371        let mut direct_samples = vec![0u32; (stride * nominal_h) as usize];
372        for y in 0..height as usize {
373            for x in 0..width as usize {
374                let pixel = image[y * width as usize + x];
375                let centered = pixel - 128;
376                let sign = if centered < 0 { 0x8000_0000 } else { 0 };
377                let mag = centered.unsigned_abs() << shift;
378                direct_samples[y * stride as usize + x] = sign | mag;
379            }
380        }
381        let direct = encode_codeblock32(&direct_samples, missing_msbs, 1, width, height, stride)
382            .expect("direct block encode failed");
383        let direct_bytes = direct.data[..direct.length as usize].to_vec();
384
385        let mut cs = Codestream::new();
386        cs.access_siz_mut()
387            .set_image_extent(Point::new(width, height));
388        cs.access_siz_mut().set_num_components(1);
389        cs.access_siz_mut()
390            .set_comp_info(0, Point::new(1, 1), 8, false);
391        cs.access_siz_mut().set_tile_size(Size::new(width, height));
392        cs.access_cod_mut().set_num_decomposition(0);
393        cs.access_cod_mut().set_reversible(true);
394        cs.access_cod_mut().set_color_transform(false);
395        cs.set_planar(0);
396
397        let mut outfile = MemOutfile::new();
398        cs.write_headers(&mut outfile, &[]).unwrap();
399        for y in 0..height as usize {
400            let start = y * width as usize;
401            let end = start + width as usize;
402            cs.exchange(&image[start..end], 0).unwrap();
403        }
404        cs.flush(&mut outfile).unwrap();
405
406        let enc_cb =
407            &cs.debug_inner().tiles[0].tile_comps[0].resolutions[0].subbands[0].codeblocks[0];
408        let enc_state = enc_cb.enc_state.as_ref().expect("missing encode state");
409        assert_eq!(enc_state.pass1_bytes as usize, direct_bytes.len());
410        assert_eq!(enc_cb.coded_data, direct_bytes);
411
412        let encoded = outfile.get_data().to_vec();
413        let mut infile = MemInfile::new(&encoded);
414        let mut dec = Codestream::new();
415        dec.read_headers(&mut infile).unwrap();
416        dec.create(&mut infile).unwrap();
417
418        let dec_cb =
419            &dec.debug_inner().tiles[0].tile_comps[0].resolutions[0].subbands[0].codeblocks[0];
420        let dec_state = dec_cb.dec_state.as_ref().expect("missing decode state");
421        assert_eq!(dec_state.pass1_len as usize, direct_bytes.len());
422        assert_eq!(dec_cb.coded_data, direct_bytes);
423
424        let mut decoded = Vec::with_capacity((width * height) as usize);
425        for _ in 0..height {
426            decoded.extend(dec.pull(0).expect("missing decoded line"));
427        }
428        assert_eq!(decoded, image);
429    }
430
431    #[test]
432    fn debug_d2_subband_coeffs_survive_roundtrip() {
433        let width = 64u32;
434        let height = 64u32;
435        let mut image = Vec::with_capacity((width * height) as usize);
436        for y in 0..height {
437            for x in 0..width {
438                image.push(((3 * x + 7 * y) & 0xFF) as i32);
439            }
440        }
441
442        let mut cs = Codestream::new();
443        cs.access_siz_mut()
444            .set_image_extent(Point::new(width, height));
445        cs.access_siz_mut().set_num_components(1);
446        cs.access_siz_mut()
447            .set_comp_info(0, Point::new(1, 1), 8, false);
448        cs.access_siz_mut().set_tile_size(Size::new(width, height));
449        cs.access_cod_mut().set_num_decomposition(2);
450        cs.access_cod_mut().set_reversible(true);
451        cs.access_cod_mut().set_color_transform(false);
452        cs.set_planar(0);
453
454        let mut outfile = MemOutfile::new();
455        cs.write_headers(&mut outfile, &[]).unwrap();
456        for y in 0..height as usize {
457            let start = y * width as usize;
458            let end = start + width as usize;
459            cs.exchange(&image[start..end], 0).unwrap();
460        }
461        cs.flush(&mut outfile).unwrap();
462
463        let enc_tc = &cs.debug_inner().tiles[0].tile_comps[0];
464        let enc_coeffs: Vec<Vec<Vec<i32>>> = enc_tc
465            .resolutions
466            .iter()
467            .map(|res| res.subbands.iter().map(|sb| sb.coeffs.clone()).collect())
468            .collect();
469
470        let encoded = outfile.get_data().to_vec();
471        let mut infile = MemInfile::new(&encoded);
472        let mut dec = Codestream::new();
473        dec.read_headers(&mut infile).unwrap();
474        dec.create(&mut infile).unwrap();
475
476        let dec_tc = &dec.debug_inner().tiles[0].tile_comps[0];
477        for (res_idx, (enc_res, dec_res)) in enc_coeffs.iter().zip(&dec_tc.resolutions).enumerate()
478        {
479            for (sb_idx, (enc_sb, dec_sb)) in enc_res.iter().zip(&dec_res.subbands).enumerate() {
480                assert_eq!(
481                    enc_sb, &dec_sb.coeffs,
482                    "subband mismatch at resolution {res_idx}, subband {sb_idx}"
483                );
484            }
485        }
486    }
487
488    #[test]
489    fn debug_tiled_d5_encoder_codeblocks_are_decodable() {
490        let width = 256u32;
491        let height = 256u32;
492        let mut components = (0..3)
493            .map(|_| Vec::<i32>::with_capacity((width * height) as usize))
494            .collect::<Vec<_>>();
495        for y in 0..height {
496            for x in 0..width {
497                components[0].push(((x * 255) / width.max(1)) as i32);
498                components[1].push(((y * 255) / height.max(1)) as i32);
499                components[2].push((((x + y) * 127) / (width + height).max(1)) as i32);
500            }
501        }
502
503        let mut cs = Codestream::new();
504        cs.access_siz_mut()
505            .set_image_extent(Point::new(width, height));
506        cs.access_siz_mut().set_tile_size(Size::new(33, 33));
507        cs.access_siz_mut().set_num_components(3);
508        for c in 0..3 {
509            cs.access_siz_mut()
510                .set_comp_info(c, Point::new(1, 1), 8, false);
511        }
512        cs.access_cod_mut().set_num_decomposition(5);
513        cs.access_cod_mut().set_reversible(true);
514        cs.access_cod_mut().set_color_transform(true);
515
516        let mut outfile = MemOutfile::new();
517        cs.write_headers(&mut outfile, &[]).unwrap();
518        for y in 0..height as usize {
519            let start = y * width as usize;
520            let end = start + width as usize;
521            for (c, comp) in components.iter().enumerate() {
522                cs.exchange(&comp[start..end], c as u32).unwrap();
523            }
524        }
525        cs.flush(&mut outfile).unwrap();
526
527        for (tile_idx, tile) in cs.debug_inner().tiles.iter().enumerate() {
528            for (comp_idx, tc) in tile.tile_comps.iter().enumerate() {
529                for (res_idx, res) in tc.resolutions.iter().enumerate() {
530                    for (sb_idx, sb) in res.subbands.iter().enumerate() {
531                        for (cb_idx, cb) in sb.codeblocks.iter().enumerate() {
532                            let Some(enc) = cb.enc_state.as_ref() else {
533                                continue;
534                            };
535                            if !enc.has_data || enc.num_passes == 0 {
536                                continue;
537                            }
538
539                            let mut coded = cb.coded_data.clone();
540                            let nominal_w = 1u32 << cb.log_block_dims.w;
541                            let nominal_h = 1u32 << cb.log_block_dims.h;
542                            let stride = (nominal_w + 7) & !7;
543                            let mut decoded = vec![0u32; (stride * nominal_h) as usize];
544                            let result =
545                                std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
546                                    decode_codeblock32(
547                                        &mut coded,
548                                        &mut decoded,
549                                        enc.missing_msbs,
550                                        enc.num_passes,
551                                        enc.pass1_bytes,
552                                        enc.pass2_bytes,
553                                        cb.width(),
554                                        cb.height(),
555                                        stride,
556                                        false,
557                                    )
558                                }));
559
560                            match result {
561                                Ok(Ok(true)) | Ok(Ok(false)) => {}
562                                Ok(Err(err)) => panic!(
563                                    "decode error for tile={tile_idx} comp={comp_idx} res={res_idx} sb={sb_idx} cb={cb_idx} rect={:?} passes={} p1={} p2={} mmsbs={}: {err:?}",
564                                    cb.cb_rect,
565                                    enc.num_passes,
566                                    enc.pass1_bytes,
567                                    enc.pass2_bytes,
568                                    enc.missing_msbs,
569                                ),
570                                Err(_) => panic!(
571                                    "decode panic for tile={tile_idx} comp={comp_idx} res={res_idx} sb={sb_idx} cb={cb_idx} rect={:?} passes={} p1={} p2={} mmsbs={} bytes={:02X?}",
572                                    cb.cb_rect,
573                                    enc.num_passes,
574                                    enc.pass1_bytes,
575                                    enc.pass2_bytes,
576                                    enc.missing_msbs,
577                                    cb.coded_data,
578                                ),
579                            }
580                        }
581                    }
582                }
583            }
584        }
585    }
586
587    #[test]
588    fn debug_rev53_4x1024_encoder_codeblocks_are_decodable() {
589        let width = 256u32;
590        let height = 256u32;
591        let mut components = (0..3)
592            .map(|_| Vec::<i32>::with_capacity((width * height) as usize))
593            .collect::<Vec<_>>();
594        for y in 0..height {
595            for x in 0..width {
596                components[0].push(((x * 255) / width.max(1)) as i32);
597                components[1].push(((y * 255) / height.max(1)) as i32);
598                components[2].push((((x + y) * 127) / (width + height).max(1)) as i32);
599            }
600        }
601
602        let mut cs = Codestream::new();
603        cs.access_siz_mut()
604            .set_image_extent(Point::new(width, height));
605        cs.access_siz_mut().set_tile_size(Size::new(width, height));
606        cs.access_siz_mut().set_num_components(3);
607        for c in 0..3 {
608            cs.access_siz_mut()
609                .set_comp_info(c, Point::new(1, 1), 8, false);
610        }
611        cs.access_cod_mut().set_num_decomposition(5);
612        cs.access_cod_mut().set_reversible(true);
613        cs.access_cod_mut().set_color_transform(true);
614        cs.access_cod_mut().set_block_dims(4, 1024);
615
616        let mut outfile = MemOutfile::new();
617        cs.write_headers(&mut outfile, &[]).unwrap();
618        for y in 0..height as usize {
619            let start = y * width as usize;
620            let end = start + width as usize;
621            for (c, comp) in components.iter().enumerate() {
622                cs.exchange(&comp[start..end], c as u32).unwrap();
623            }
624        }
625        cs.flush(&mut outfile).unwrap();
626
627        for (tile_idx, tile) in cs.debug_inner().tiles.iter().enumerate() {
628            for (comp_idx, tc) in tile.tile_comps.iter().enumerate() {
629                for (res_idx, res) in tc.resolutions.iter().enumerate() {
630                    for (sb_idx, sb) in res.subbands.iter().enumerate() {
631                        for (cb_idx, cb) in sb.codeblocks.iter().enumerate() {
632                            let Some(enc) = cb.enc_state.as_ref() else {
633                                continue;
634                            };
635                            if !enc.has_data || enc.num_passes == 0 {
636                                continue;
637                            }
638
639                            let nominal_w = 1u32 << cb.log_block_dims.w;
640                            let nominal_h = 1u32 << cb.log_block_dims.h;
641                            let stride = (nominal_w + 7) & !7;
642                            let mut coded = cb.coded_data.clone();
643                            let mut decoded = vec![0u32; (stride * nominal_h) as usize];
644                            let result =
645                                std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
646                                    decode_codeblock32(
647                                        &mut coded,
648                                        &mut decoded,
649                                        enc.missing_msbs,
650                                        enc.num_passes,
651                                        enc.pass1_bytes,
652                                        enc.pass2_bytes,
653                                        cb.width(),
654                                        cb.height(),
655                                        stride,
656                                        false,
657                                    )
658                                }));
659
660                            match result {
661                                Ok(Ok(true)) | Ok(Ok(false)) => {}
662                                Ok(Err(err)) => panic!(
663                                    "decode error for tile={tile_idx} comp={comp_idx} res={res_idx} sb={sb_idx} cb={cb_idx} rect={:?} log_dims={:?} passes={} p1={} p2={} mmsbs={} bytes={:02X?}: {err:?}",
664                                    cb.cb_rect,
665                                    cb.log_block_dims,
666                                    enc.num_passes,
667                                    enc.pass1_bytes,
668                                    enc.pass2_bytes,
669                                    enc.missing_msbs,
670                                    cb.coded_data,
671                                ),
672                                Err(_) => panic!(
673                                    "decode panic for tile={tile_idx} comp={comp_idx} res={res_idx} sb={sb_idx} cb={cb_idx} rect={:?} log_dims={:?} passes={} p1={} p2={} mmsbs={} bytes={:02X?}",
674                                    cb.cb_rect,
675                                    cb.log_block_dims,
676                                    enc.num_passes,
677                                    enc.pass1_bytes,
678                                    enc.pass2_bytes,
679                                    enc.missing_msbs,
680                                    cb.coded_data,
681                                ),
682                            }
683                        }
684                    }
685                }
686            }
687        }
688    }
689
690    #[test]
691    fn debug_rev53_4x1024_packet_preserves_codeblocks() {
692        let width = 256u32;
693        let height = 256u32;
694        let mut components = (0..3)
695            .map(|_| Vec::<i32>::with_capacity((width * height) as usize))
696            .collect::<Vec<_>>();
697        for y in 0..height {
698            for x in 0..width {
699                components[0].push(((x * 255) / width.max(1)) as i32);
700                components[1].push(((y * 255) / height.max(1)) as i32);
701                components[2].push((((x + y) * 127) / (width + height).max(1)) as i32);
702            }
703        }
704
705        let mut cs = Codestream::new();
706        cs.access_siz_mut()
707            .set_image_extent(Point::new(width, height));
708        cs.access_siz_mut().set_tile_size(Size::new(width, height));
709        cs.access_siz_mut().set_num_components(3);
710        for c in 0..3 {
711            cs.access_siz_mut()
712                .set_comp_info(c, Point::new(1, 1), 8, false);
713        }
714        cs.access_cod_mut().set_num_decomposition(5);
715        cs.access_cod_mut().set_reversible(true);
716        cs.access_cod_mut().set_color_transform(true);
717        cs.access_cod_mut().set_block_dims(4, 1024);
718
719        let mut outfile = MemOutfile::new();
720        cs.write_headers(&mut outfile, &[]).unwrap();
721        for y in 0..height as usize {
722            let start = y * width as usize;
723            let end = start + width as usize;
724            for (c, comp) in components.iter().enumerate() {
725                cs.exchange(&comp[start..end], c as u32).unwrap();
726            }
727        }
728        cs.flush(&mut outfile).unwrap();
729
730        let encoded = outfile.get_data().to_vec();
731        let mut infile = MemInfile::new(&encoded);
732        let mut dec = Codestream::new();
733        dec.read_headers(&mut infile).unwrap();
734        dec.create(&mut infile).unwrap();
735
736        for (tile_idx, (enc_tile, dec_tile)) in cs
737            .debug_inner()
738            .tiles
739            .iter()
740            .zip(&dec.debug_inner().tiles)
741            .enumerate()
742        {
743            for (comp_idx, (enc_tc, dec_tc)) in enc_tile
744                .tile_comps
745                .iter()
746                .zip(&dec_tile.tile_comps)
747                .enumerate()
748            {
749                for (res_idx, (enc_res, dec_res)) in enc_tc
750                    .resolutions
751                    .iter()
752                    .zip(&dec_tc.resolutions)
753                    .enumerate()
754                {
755                    for (sb_idx, (enc_sb, dec_sb)) in
756                        enc_res.subbands.iter().zip(&dec_res.subbands).enumerate()
757                    {
758                        for (cb_idx, (enc_cb, dec_cb)) in
759                            enc_sb.codeblocks.iter().zip(&dec_sb.codeblocks).enumerate()
760                        {
761                            let enc_state = enc_cb.enc_state.as_ref();
762                            let dec_state = dec_cb.dec_state.as_ref();
763                            match (enc_state, dec_state) {
764                                (None, None) => continue,
765                                (Some(enc), None) if !enc.has_data || enc.num_passes == 0 => continue,
766                                (Some(enc), Some(dec_state)) => {
767                                    if enc.has_data != (dec_state.num_passes > 0) {
768                                        panic!(
769                                            "has_data mismatch tile={tile_idx} comp={comp_idx} res={res_idx} sb={sb_idx} cb={cb_idx} rect={:?} enc={:?} dec={:?}",
770                                            enc_cb.cb_rect, enc, dec_state
771                                        );
772                                    }
773                                    if !enc.has_data {
774                                        continue;
775                                    }
776                                    if enc.pass1_bytes != dec_state.pass1_len
777                                        || enc.pass2_bytes != dec_state.pass2_len
778                                        || enc.num_passes != dec_state.num_passes
779                                        || enc.missing_msbs != dec_state.missing_msbs
780                                        || enc_cb.coded_data != dec_cb.coded_data
781                                    {
782                                        panic!(
783                                            "packet mismatch tile={tile_idx} comp={comp_idx} res={res_idx} sb={sb_idx} cb={cb_idx} rect={:?} enc={:?} dec={:?} enc_bytes={:02X?} dec_bytes={:02X?}",
784                                            enc_cb.cb_rect,
785                                            enc,
786                                            dec_state,
787                                            enc_cb.coded_data,
788                                            dec_cb.coded_data,
789                                        );
790                                    }
791                                }
792                                _ => panic!(
793                                    "state presence mismatch tile={tile_idx} comp={comp_idx} res={res_idx} sb={sb_idx} cb={cb_idx} rect={:?} enc={:?} dec={:?}",
794                                    enc_cb.cb_rect,
795                                    enc_state,
796                                    dec_state,
797                                ),
798                            }
799                        }
800                    }
801                }
802            }
803        }
804    }
805}