1use alloc::vec;
9use alloc::vec::Vec;
10
11use super::bitplane_encode;
12use super::build::SubBandType;
13use super::codestream_write::{self, BlockCodingMode, EncodeParams};
14use super::fdwt::{self, DwtDecomposition};
15use super::forward_mct;
16use super::ht_block_encode;
17use super::packet_encode::{self, CodeBlockPacketData, ResolutionPacket, SubbandPrecinct};
18use super::quantize::{self, QuantStepSize};
19use crate::{DecodeSettings, Image};
20
21#[derive(Debug, Clone)]
23pub struct EncodeOptions {
24 pub num_decomposition_levels: u8,
26 pub reversible: bool,
28 pub code_block_width_exp: u8,
30 pub code_block_height_exp: u8,
32 pub guard_bits: u8,
34 pub use_ht_block_coding: bool,
36}
37
38impl Default for EncodeOptions {
39 fn default() -> Self {
40 Self {
41 num_decomposition_levels: 5,
42 reversible: true,
43 code_block_width_exp: 4,
44 code_block_height_exp: 4,
45 guard_bits: 1,
46 use_ht_block_coding: false,
47 }
48 }
49}
50
51pub fn encode(
65 pixels: &[u8],
66 width: u32,
67 height: u32,
68 num_components: u8,
69 bit_depth: u8,
70 signed: bool,
71 options: &EncodeOptions,
72) -> Result<Vec<u8>, &'static str> {
73 let block_coding_mode = block_coding_mode(options);
74 let codestream = encode_impl(
75 pixels,
76 width,
77 height,
78 num_components,
79 bit_depth,
80 signed,
81 options,
82 block_coding_mode,
83 )?;
84
85 if block_coding_mode == BlockCodingMode::HighThroughput {
86 validate_htj2k_codestream(
87 &codestream,
88 pixels,
89 width,
90 height,
91 num_components,
92 bit_depth,
93 signed,
94 options.reversible,
95 )?;
96 }
97
98 Ok(codestream)
99}
100
101pub fn encode_htj2k(
105 pixels: &[u8],
106 width: u32,
107 height: u32,
108 num_components: u8,
109 bit_depth: u8,
110 signed: bool,
111 options: &EncodeOptions,
112) -> Result<Vec<u8>, &'static str> {
113 let mut options = options.clone();
114 options.use_ht_block_coding = true;
115 encode(
116 pixels,
117 width,
118 height,
119 num_components,
120 bit_depth,
121 signed,
122 &options,
123 )
124}
125
126fn block_coding_mode(options: &EncodeOptions) -> BlockCodingMode {
127 if options.use_ht_block_coding {
128 BlockCodingMode::HighThroughput
129 } else {
130 BlockCodingMode::Classic
131 }
132}
133
134fn validate_htj2k_codestream(
135 codestream: &[u8],
136 pixels: &[u8],
137 width: u32,
138 height: u32,
139 num_components: u8,
140 bit_depth: u8,
141 signed: bool,
142 reversible: bool,
143) -> Result<(), &'static str> {
144 let image = Image::new(codestream, &DecodeSettings::default())
145 .map_err(|_| "generated HTJ2K codestream failed self-validation")?;
146 let decoded = image
147 .decode_native()
148 .map_err(|_| "generated HTJ2K codestream failed self-validation")?;
149
150 if decoded.width != width
151 || decoded.height != height
152 || decoded.bit_depth != bit_depth
153 || decoded.num_components != num_components
154 {
155 return Err("generated HTJ2K codestream failed self-validation");
156 }
157
158 if reversible && !native_samples_equal(pixels, &decoded.data, bit_depth, signed) {
159 return Err("generated HTJ2K codestream did not roundtrip");
160 }
161
162 Ok(())
163}
164
165fn native_samples_equal(expected: &[u8], actual: &[u8], bit_depth: u8, signed: bool) -> bool {
166 if expected.len() != actual.len() {
167 return false;
168 }
169
170 let bytes_per_sample = if bit_depth <= 8 { 1 } else { 2 };
171 let sample_count = expected.len() / bytes_per_sample;
172 (0..sample_count).all(|sample_index| {
173 decode_native_sample(expected, sample_index, bit_depth, signed)
174 == decode_native_sample(actual, sample_index, bit_depth, signed)
175 })
176}
177
178fn decode_native_sample(bytes: &[u8], sample_index: usize, bit_depth: u8, signed: bool) -> i32 {
179 let byte_offset = sample_index * if bit_depth <= 8 { 1 } else { 2 };
180 let mask = (1u32 << u32::from(bit_depth)) - 1;
181 let raw = if bit_depth <= 8 {
182 u32::from(bytes[byte_offset])
183 } else {
184 u32::from(u16::from_le_bytes([
185 bytes[byte_offset],
186 bytes[byte_offset + 1],
187 ]))
188 } & mask;
189
190 if signed {
191 let shift = 32 - u32::from(bit_depth);
192 ((raw << shift) as i32) >> shift
193 } else {
194 raw as i32
195 }
196}
197
198fn encode_impl(
199 pixels: &[u8],
200 width: u32,
201 height: u32,
202 num_components: u8,
203 bit_depth: u8,
204 signed: bool,
205 options: &EncodeOptions,
206 block_coding_mode: BlockCodingMode,
207) -> Result<Vec<u8>, &'static str> {
208 if width == 0 || height == 0 {
209 return Err("invalid dimensions");
210 }
211 if num_components == 0 || num_components > 4 {
212 return Err("unsupported component count");
213 }
214 if bit_depth == 0 || bit_depth > 16 {
215 return Err("unsupported bit depth");
216 }
217
218 let num_pixels = (width * height) as usize;
219 let bytes_per_sample = if bit_depth <= 8 { 1 } else { 2 };
220 let expected_len = num_pixels * num_components as usize * bytes_per_sample;
221 if pixels.len() < expected_len {
222 return Err("pixel data too short");
223 }
224
225 #[cfg(feature = "openjph-htj2k")]
226 if block_coding_mode == BlockCodingMode::HighThroughput {
227 let mut openjph_options = options.clone();
228 openjph_options.num_decomposition_levels = options
229 .num_decomposition_levels
230 .min(max_decomposition_levels(width, height));
231 return crate::openjph_htj2k::encode(
232 pixels,
233 width,
234 height,
235 num_components,
236 bit_depth,
237 signed,
238 &openjph_options,
239 );
240 }
241
242 let mut components = deinterleave_to_f32(pixels, num_pixels, num_components, bit_depth, signed);
244
245 let use_mct = num_components >= 3;
247 if use_mct {
248 if options.reversible {
249 forward_mct::forward_rct(&mut components);
250 } else {
251 forward_mct::forward_ict(&mut components);
252 }
253 }
254
255 let num_levels = options.num_decomposition_levels.min(
257 max_decomposition_levels(width, height),
259 );
260
261 let decompositions: Vec<DwtDecomposition> = components
262 .iter()
263 .map(|comp| fdwt::forward_dwt(comp, width, height, num_levels, options.reversible))
264 .collect();
265
266 let guard_bits = if options.reversible {
268 options.guard_bits
269 } else {
270 options.guard_bits.max(2)
271 };
272
273 let step_sizes =
274 quantize::compute_step_sizes(bit_depth, num_levels, options.reversible, guard_bits);
275
276 let cb_width = 1u32 << (options.code_block_width_exp + 2);
278 let cb_height = 1u32 << (options.code_block_height_exp + 2);
279
280 let mut resolution_packets: Vec<ResolutionPacket> = Vec::new();
281
282 for decomp in decompositions.iter().take(num_components as usize) {
283 let ll_subband = encode_subband(
285 &decomp.ll,
286 decomp.ll_width,
287 decomp.ll_height,
288 &step_sizes[0],
289 guard_bits,
290 options.reversible,
291 block_coding_mode,
292 cb_width,
293 cb_height,
294 SubBandType::LowLow,
295 )?;
296 resolution_packets.push(ResolutionPacket {
297 subbands: vec![ll_subband],
298 });
299
300 for (level_idx, level) in decomp.levels.iter().enumerate() {
302 let step_base = 1 + level_idx * 3;
303
304 let hl_subband = encode_subband(
306 &level.hl,
307 level.high_width,
308 level.low_height,
309 &step_sizes[step_base],
310 guard_bits,
311 options.reversible,
312 block_coding_mode,
313 cb_width,
314 cb_height,
315 SubBandType::HighLow,
316 )?;
317
318 let lh_subband = encode_subband(
320 &level.lh,
321 level.low_width,
322 level.high_height,
323 &step_sizes[step_base + 1],
324 guard_bits,
325 options.reversible,
326 block_coding_mode,
327 cb_width,
328 cb_height,
329 SubBandType::LowHigh,
330 )?;
331
332 let hh_subband = encode_subband(
334 &level.hh,
335 level.high_width,
336 level.high_height,
337 &step_sizes[step_base + 2],
338 guard_bits,
339 options.reversible,
340 block_coding_mode,
341 cb_width,
342 cb_height,
343 SubBandType::HighHigh,
344 )?;
345
346 resolution_packets.push(ResolutionPacket {
347 subbands: vec![hl_subband, lh_subband, hh_subband],
348 });
349 }
350 }
351
352 let tile_data = packet_encode::form_tile_bitstream(&mut resolution_packets, 1, num_components);
354
355 let quant_params: Vec<(u16, u16)> = step_sizes
357 .iter()
358 .map(|s| (s.exponent, s.mantissa))
359 .collect();
360
361 let params = EncodeParams {
362 width,
363 height,
364 num_components,
365 bit_depth,
366 signed,
367 num_decomposition_levels: num_levels,
368 reversible: options.reversible,
369 code_block_width_exp: options.code_block_width_exp,
370 code_block_height_exp: options.code_block_height_exp,
371 num_layers: 1,
372 use_mct,
373 guard_bits,
374 block_coding_mode,
375 };
376
377 Ok(codestream_write::write_codestream(
378 ¶ms,
379 &tile_data,
380 &quant_params,
381 ))
382}
383
384fn encode_subband(
386 coefficients: &[f32],
387 width: u32,
388 height: u32,
389 step_size: &QuantStepSize,
390 guard_bits: u8,
391 reversible: bool,
392 block_coding_mode: BlockCodingMode,
393 cb_width: u32,
394 cb_height: u32,
395 sub_band_type: SubBandType,
396) -> Result<SubbandPrecinct, &'static str> {
397 if width == 0 || height == 0 {
398 return Ok(SubbandPrecinct {
399 code_blocks: Vec::new(),
400 num_cbs_x: 0,
401 num_cbs_y: 0,
402 });
403 }
404
405 let quantized = quantize::quantize_subband(coefficients, step_size, guard_bits, reversible);
407 debug_assert!(step_size.exponent <= u16::from(u8::MAX));
408 let total_bitplanes = guard_bits
409 .saturating_add(step_size.exponent as u8)
410 .saturating_sub(1);
411
412 let num_cbs_x = width.div_ceil(cb_width);
414 let num_cbs_y = height.div_ceil(cb_height);
415 let mut code_blocks = Vec::with_capacity((num_cbs_x * num_cbs_y) as usize);
416
417 for cby in 0..num_cbs_y {
418 for cbx in 0..num_cbs_x {
419 let x0 = cbx * cb_width;
420 let y0 = cby * cb_height;
421 let x1 = (x0 + cb_width).min(width);
422 let y1 = (y0 + cb_height).min(height);
423 let cbw = x1 - x0;
424 let cbh = y1 - y0;
425
426 let mut cb_coeffs = vec![0i32; (cbw * cbh) as usize];
428 for y in 0..cbh {
429 for x in 0..cbw {
430 cb_coeffs[(y * cbw + x) as usize] =
431 quantized[((y0 + y) * width + (x0 + x)) as usize];
432 }
433 }
434
435 let encoded = if block_coding_mode == BlockCodingMode::HighThroughput {
436 ht_block_encode::encode_code_block(&cb_coeffs, cbw, cbh, total_bitplanes)?
437 } else {
438 bitplane_encode::encode_code_block(
439 &cb_coeffs,
440 cbw,
441 cbh,
442 sub_band_type,
443 total_bitplanes,
444 )
445 };
446
447 code_blocks.push(CodeBlockPacketData {
448 data: encoded.data,
449 num_coding_passes: encoded.num_coding_passes,
450 num_zero_bitplanes: encoded.num_zero_bitplanes,
451 previously_included: false,
452 l_block: 3,
453 block_coding_mode,
454 });
455 }
456 }
457
458 Ok(SubbandPrecinct {
459 code_blocks,
460 num_cbs_x,
461 num_cbs_y,
462 })
463}
464
465fn deinterleave_to_f32(
467 pixels: &[u8],
468 num_pixels: usize,
469 num_components: u8,
470 bit_depth: u8,
471 signed: bool,
472) -> Vec<Vec<f32>> {
473 let nc = num_components as usize;
474 let mut components = vec![vec![0.0f32; num_pixels]; nc];
475 let unsigned_offset = if signed {
476 0.0
477 } else {
478 (1u32 << (bit_depth as u32 - 1)) as f32
479 };
480
481 if bit_depth <= 8 {
482 for (i, pixel) in pixels.chunks_exact(nc).take(num_pixels).enumerate() {
483 for (c, component) in components.iter_mut().enumerate().take(nc) {
484 let val = pixel[c];
485 component[i] = if signed {
486 (val as i8) as f32
487 } else {
488 val as f32 - unsigned_offset
489 };
490 }
491 }
492 } else {
493 for (i, pixel) in pixels.chunks_exact(nc * 2).take(num_pixels).enumerate() {
495 for (c, component) in components.iter_mut().enumerate().take(nc) {
496 let offset = c * 2;
497 let val = u16::from_le_bytes([pixel[offset], pixel[offset + 1]]);
498 component[i] = if signed {
499 (val as i16) as f32
500 } else {
501 val as f32 - unsigned_offset
502 };
503 }
504 }
505 }
506
507 components
508}
509
510fn max_decomposition_levels(width: u32, height: u32) -> u8 {
512 let min_dim = width.min(height);
513 if min_dim <= 1 {
514 return 0;
515 }
516 (min_dim as f32).log2().floor() as u8
517}
518
519#[cfg(test)]
520mod tests {
521 use super::*;
522
523 #[test]
524 fn test_encode_8bit_gray() {
525 let width = 8u32;
526 let height = 8u32;
527 let pixels: Vec<u8> = (0..64).collect();
528
529 let result = encode(
530 &pixels,
531 width,
532 height,
533 1,
534 8,
535 false,
536 &EncodeOptions {
537 num_decomposition_levels: 2,
538 ..Default::default()
539 },
540 );
541
542 assert!(result.is_ok());
543 let codestream = result.unwrap();
544 assert_eq!(codestream[0], 0xFF);
546 assert_eq!(codestream[1], 0x4F);
547 let len = codestream.len();
549 assert_eq!(codestream[len - 2], 0xFF);
550 assert_eq!(codestream[len - 1], 0xD9);
551 }
552
553 #[test]
554 fn test_encode_16bit_gray() {
555 let width = 8u32;
556 let height = 8u32;
557 let mut pixels = Vec::with_capacity(128);
558 for i in 0..64u16 {
559 let val = i * 100;
560 pixels.extend_from_slice(&val.to_le_bytes());
561 }
562
563 let result = encode(
564 &pixels,
565 width,
566 height,
567 1,
568 16,
569 false,
570 &EncodeOptions {
571 num_decomposition_levels: 2,
572 ..Default::default()
573 },
574 );
575
576 assert!(result.is_ok());
577 }
578
579 #[test]
580 fn test_encode_rgb() {
581 let width = 16u32;
582 let height = 16u32;
583 let pixels: Vec<u8> = (0..width * height * 3).map(|i| (i & 0xFF) as u8).collect();
584
585 let result = encode(
586 &pixels,
587 width,
588 height,
589 3,
590 8,
591 false,
592 &EncodeOptions {
593 num_decomposition_levels: 3,
594 ..Default::default()
595 },
596 );
597
598 assert!(result.is_ok(), "RGB encode failed: {:?}", result.err());
599 }
600
601 #[test]
602 fn test_encode_lossy() {
603 let pixels: Vec<u8> = (0..64).collect();
604
605 let result = encode(
606 &pixels,
607 8,
608 8,
609 1,
610 8,
611 false,
612 &EncodeOptions {
613 num_decomposition_levels: 2,
614 reversible: false,
615 guard_bits: 2,
616 ..Default::default()
617 },
618 );
619
620 assert!(result.is_ok());
621 }
622
623 fn assert_htj2k_lossless_roundtrip(
624 pixels: &[u8],
625 width: u32,
626 height: u32,
627 bit_depth: u8,
628 num_decomposition_levels: u8,
629 ) {
630 let codestream = encode_htj2k(
631 pixels,
632 width,
633 height,
634 1,
635 bit_depth,
636 false,
637 &EncodeOptions {
638 num_decomposition_levels,
639 ..Default::default()
640 },
641 )
642 .expect("HTJ2K encode");
643
644 assert!(codestream.windows(2).any(|window| window == [0xFF, 0x50]));
645 let cod_offset = codestream
646 .windows(2)
647 .position(|window| window == [0xFF, 0x52])
648 .expect("COD marker");
649 assert_eq!(codestream[cod_offset + 12], 0x40);
650
651 let image = Image::new(
652 &codestream,
653 &DecodeSettings {
654 resolve_palette_indices: true,
655 strict: true,
656 target_resolution: None,
657 },
658 )
659 .expect("parse HT codestream");
660 let decoded = image.decode_native().expect("decode HT codestream");
661
662 assert_eq!(decoded.width, width);
663 assert_eq!(decoded.height, height);
664 assert_eq!(decoded.bit_depth, bit_depth);
665 assert_eq!(decoded.data, pixels);
666 }
667
668 fn gradient_u8(width: u32, height: u32) -> Vec<u8> {
669 let mut pixels = Vec::with_capacity((width * height) as usize);
670 for y in 0..height {
671 for x in 0..width {
672 pixels.push(((x * 17 + y * 31) % 256) as u8);
673 }
674 }
675 pixels
676 }
677
678 #[test]
679 fn test_encode_high_throughput_zero_image_roundtrip() {
680 let width = 4u32;
681 let height = 4u32;
682 let sample = 2048u16.to_le_bytes();
683 let mut pixels = Vec::with_capacity((width * height * 2) as usize);
684 for _ in 0..(width * height) {
685 pixels.extend_from_slice(&sample);
686 }
687
688 let codestream = encode(
689 &pixels,
690 width,
691 height,
692 1,
693 12,
694 false,
695 &EncodeOptions {
696 num_decomposition_levels: 2,
697 use_ht_block_coding: true,
698 ..Default::default()
699 },
700 )
701 .expect("HT all-zero encode");
702
703 assert!(codestream.windows(2).any(|window| window == [0xFF, 0x50]));
704 let cod_offset = codestream
705 .windows(2)
706 .position(|window| window == [0xFF, 0x52])
707 .expect("COD marker");
708 assert_eq!(codestream[cod_offset + 12], 0x40);
709
710 let image =
711 Image::new(&codestream, &DecodeSettings::default()).expect("parse HT codestream");
712 let decoded = image.decode_native().expect("decode HT codestream");
713
714 assert_eq!(decoded.width, width);
715 assert_eq!(decoded.height, height);
716 assert_eq!(decoded.bit_depth, 12);
717 assert_eq!(decoded.data, pixels);
718 }
719
720 #[test]
721 fn test_encode_high_throughput_nonzero_roundtrip() {
722 let width = 1u32;
723 let height = 1u32;
724 let pixels = 2049u16.to_le_bytes().to_vec();
725
726 let codestream = encode_htj2k(
727 &pixels,
728 width,
729 height,
730 1,
731 12,
732 false,
733 &EncodeOptions {
734 num_decomposition_levels: 0,
735 ..Default::default()
736 },
737 )
738 .expect("HT non-zero encode");
739
740 assert!(codestream.windows(2).any(|window| window == [0xFF, 0x50]));
741 let image =
742 Image::new(&codestream, &DecodeSettings::default()).expect("parse HT codestream");
743 let decoded = image.decode_native().expect("decode HT codestream");
744
745 assert_eq!(decoded.width, width);
746 assert_eq!(decoded.height, height);
747 assert_eq!(decoded.bit_depth, 12);
748 assert_eq!(decoded.data, pixels);
749 }
750
751 #[test]
752 fn test_encode_high_throughput_varied_12bit_roundtrip() {
753 let mut pixels = Vec::with_capacity(32);
754 for i in 0u16..16 {
755 pixels.extend_from_slice(&((i * 257) & 0x0FFF).to_le_bytes());
756 }
757
758 let codestream = encode_htj2k(
759 &pixels,
760 4,
761 4,
762 1,
763 12,
764 false,
765 &EncodeOptions {
766 num_decomposition_levels: 1,
767 ..Default::default()
768 },
769 )
770 .expect("HT varied encode");
771
772 let image =
773 Image::new(&codestream, &DecodeSettings::default()).expect("parse HT codestream");
774 let decoded = image.decode_native().expect("decode HT codestream");
775
776 assert_eq!(decoded.width, 4);
777 assert_eq!(decoded.height, 4);
778 assert_eq!(decoded.bit_depth, 12);
779 assert_eq!(decoded.data, pixels);
780 }
781
782 #[test]
783 fn test_encode_high_throughput_gradient_8bit_roundtrip() {
784 let pixels: Vec<u8> = (0..64).collect();
785
786 let codestream = encode_htj2k(
787 &pixels,
788 8,
789 8,
790 1,
791 8,
792 false,
793 &EncodeOptions {
794 num_decomposition_levels: 3,
795 ..Default::default()
796 },
797 )
798 .expect("HT gradient encode");
799
800 let image =
801 Image::new(&codestream, &DecodeSettings::default()).expect("parse HT codestream");
802 let decoded = image.decode_native().expect("decode HT codestream");
803
804 assert_eq!(decoded.width, 8);
805 assert_eq!(decoded.height, 8);
806 assert_eq!(decoded.bit_depth, 8);
807 assert_eq!(decoded.data, pixels);
808 }
809
810 #[test]
811 fn test_encode_high_throughput_varied_12bit_large_roundtrip() {
812 let width = 16u32;
813 let height = 8u32;
814 let mut pixels = Vec::with_capacity((width * height * 2) as usize);
815 for y in 0u16..height as u16 {
816 for x in 0u16..width as u16 {
817 let value = (x * 257 + y * 17) & 0x0FFF;
818 pixels.extend_from_slice(&value.to_le_bytes());
819 }
820 }
821
822 assert_htj2k_lossless_roundtrip(&pixels, width, height, 12, 4);
823 }
824
825 #[test]
826 fn test_encode_high_throughput_ramp_16bit_roundtrip() {
827 let width = 48u32;
828 let height = 24u32;
829 let mut pixels = Vec::with_capacity((width * height * 2) as usize);
830 for y in 0u16..height as u16 {
831 for x in 0u16..width as u16 {
832 let value = x * 521 + y * 997;
833 pixels.extend_from_slice(&value.to_le_bytes());
834 }
835 }
836
837 assert_htj2k_lossless_roundtrip(&pixels, width, height, 16, 4);
838 }
839
840 #[test]
841 fn test_encode_high_throughput_lossy_large_gradient_is_parseable() {
842 let pixels = gradient_u8(128, 128);
843
844 let codestream = encode_htj2k(
845 &pixels,
846 128,
847 128,
848 1,
849 8,
850 false,
851 &EncodeOptions {
852 num_decomposition_levels: 5,
853 reversible: false,
854 guard_bits: 2,
855 ..Default::default()
856 },
857 )
858 .expect("lossy HT encode");
859
860 assert!(codestream.windows(2).any(|window| window == [0xFF, 0x50]));
861 assert!(!codestream.is_empty());
862
863 let image = Image::new(
864 &codestream,
865 &DecodeSettings {
866 resolve_palette_indices: true,
867 strict: true,
868 target_resolution: None,
869 },
870 )
871 .expect("parse lossy HT codestream");
872 let decoded = image.decode_native().expect("decode lossy HT codestream");
873
874 assert_eq!(decoded.width, 128);
875 assert_eq!(decoded.height, 128);
876 assert_eq!(decoded.bit_depth, 8);
877 }
878
879 #[test]
880 fn test_encode_invalid_dimensions() {
881 let result = encode(&[], 0, 0, 1, 8, false, &EncodeOptions::default());
882 assert!(result.is_err());
883 }
884
885 #[test]
886 fn test_encode_too_short() {
887 let pixels = vec![0u8; 10]; let result = encode(&pixels, 8, 8, 1, 8, false, &EncodeOptions::default());
889 assert!(result.is_err());
890 }
891
892 #[test]
893 fn test_deinterleave_rgb() {
894 let pixels = vec![
895 10u8, 20, 30, 40, 50, 60, ];
898 let comps = deinterleave_to_f32(&pixels, 2, 3, 8, false);
899 assert_eq!(comps[0], vec![-118.0, -88.0]); assert_eq!(comps[1], vec![-108.0, -78.0]); assert_eq!(comps[2], vec![-98.0, -68.0]); }
903
904 #[test]
905 fn test_encode_decode_roundtrip_gray_8bit() {
906 use crate::{DecodeSettings, Image};
907
908 let original: Vec<u8> = vec![42u8; 64]; let encoded = encode(
911 &original,
912 8,
913 8,
914 1,
915 8,
916 false,
917 &EncodeOptions {
918 num_decomposition_levels: 0,
919 reversible: true,
920 ..Default::default()
921 },
922 )
923 .expect("encode failed");
924
925 let settings = DecodeSettings {
926 resolve_palette_indices: false,
927 strict: false,
928 target_resolution: None,
929 };
930 let image = Image::new(&encoded, &settings).expect("parse failed");
931 let decoded = image.decode_native().expect("decode failed");
932
933 assert_eq!(decoded.width, 8);
934 assert_eq!(decoded.height, 8);
935 assert_eq!(decoded.data, original, "round-trip mismatch");
936 }
937}