1use super::channel::{Channel, ModularImage};
8use super::encode::{
9 build_histogram_from_residuals, collect_all_residuals, select_best_rct_at,
10 write_global_modular_section, write_group_modular_section_idx, write_improved_modular_stream,
11 write_modular_stream_with_tree,
12};
13use super::palette::{CHANNEL_COLORS_PERCENT, analyze_channel_compact};
14use super::section::write_global_modular_section_with_tree;
15use crate::GROUP_DIM;
16use crate::bit_writer::BitWriter;
17use crate::entropy_coding::lz77::Lz77Method;
18use crate::error::Result;
19use crate::headers::ColorEncoding;
20use crate::headers::frame_header::{BlendMode, FrameCrop, FrameHeader};
21
22#[derive(Debug, Clone)]
24pub struct FrameEncoderOptions {
25 pub use_modular: bool,
27 pub effort: u8,
29 pub use_ans: bool,
31 pub use_tree_learning: bool,
33 pub use_squeeze: bool,
35 pub enable_lz77: bool,
37 pub lz77_method: Lz77Method,
39 pub lossy_palette: bool,
41 pub encoder_mode: crate::api::EncoderMode,
43 pub profile: crate::effort::EffortProfile,
45 pub have_animation: bool,
47 pub duration: u32,
49 pub is_last: bool,
51 pub crop: Option<FrameCrop>,
53 pub skip_rct: bool,
55}
56
57impl Default for FrameEncoderOptions {
58 fn default() -> Self {
59 Self {
60 use_modular: true, effort: 7,
62 use_ans: false,
63 use_tree_learning: false,
64 use_squeeze: false,
65 enable_lz77: false,
66 lz77_method: Lz77Method::Rle,
67 lossy_palette: false,
68 encoder_mode: crate::api::EncoderMode::Reference,
69 profile: crate::effort::EffortProfile::lossless(7, crate::api::EncoderMode::Reference),
70 have_animation: false,
71 duration: 0,
72 is_last: true,
73 crop: None,
74 skip_rct: false,
75 }
76 }
77}
78
79pub struct FrameEncoder {
81 #[allow(dead_code)]
83 options: FrameEncoderOptions,
84 width: usize,
86 height: usize,
88 #[allow(dead_code)]
89 num_extra_channels: usize,
91}
92
93impl FrameEncoder {
94 pub fn new(width: usize, height: usize, options: FrameEncoderOptions) -> Self {
96 Self {
97 options,
98 width,
99 height,
100 num_extra_channels: 0,
101 }
102 }
103
104 pub fn new_with_extra_channels(
106 width: usize,
107 height: usize,
108 options: FrameEncoderOptions,
109 num_extra_channels: usize,
110 ) -> Self {
111 Self {
112 options,
113 width,
114 height,
115 num_extra_channels,
116 }
117 }
118
119 pub(crate) fn encode_modular_with_patches(
124 &self,
125 image: &ModularImage,
126 color_encoding: &ColorEncoding,
127 writer: &mut BitWriter,
128 patches: Option<&crate::vardct::patches::PatchesData>,
129 ) -> Result<()> {
130 if patches.is_none() {
131 return self.encode_modular(image, color_encoding, writer);
132 }
133 let patches = patches.unwrap();
134
135 let num_extra_channels = if image.has_alpha { 1 } else { 0 };
137
138 {
140 use crate::headers::frame_header::PATCHES_FLAG;
141 let mut fh = FrameHeader::lossless();
142 fh.flags |= PATCHES_FLAG;
143 fh.ec_upsampling = vec![1; num_extra_channels];
144 fh.ec_blend_modes = vec![BlendMode::Replace; num_extra_channels];
145 fh.have_animation = self.options.have_animation;
146 fh.duration = self.options.duration;
147 fh.is_last = self.options.is_last;
148 if let Some(ref crop) = self.options.crop {
149 fh.x0 = crop.x0;
150 fh.y0 = crop.y0;
151 fh.width = crop.width;
152 fh.height = crop.height;
153 fh.blend_mode = BlendMode::Replace;
154 fh.blend_source = 1;
155 }
156 if self.options.have_animation && !self.options.is_last {
157 fh.save_as_reference = 1;
158 }
159 fh.write(writer)?;
160 }
161
162 let num_groups = self.num_groups();
163
164 if num_groups == 1 {
165 let mut section_writer = BitWriter::new();
167
168 crate::vardct::patches::encode_patches_section(
170 patches,
171 self.options.use_ans,
172 &mut section_writer,
173 )?;
174
175 let has_squeeze = self.options.use_squeeze
177 && !super::squeeze::default_squeeze_params(image).is_empty();
178
179 if self.options.lossy_palette && image.channels.len() >= 3 {
180 let max_colors = 1usize << image.bit_depth.min(12);
181 super::encode::write_modular_stream_with_lossy_palette(
182 image,
183 &mut section_writer,
184 self.options.use_ans,
185 0,
186 image.channels.len().min(3),
187 max_colors,
188 )?;
189 } else if has_squeeze && self.options.use_tree_learning && self.options.use_ans {
190 super::encode::write_modular_stream_with_squeeze_and_tree(
191 image,
192 &mut section_writer,
193 &self.options.profile,
194 self.options.enable_lz77,
195 self.options.lz77_method,
196 )?;
197 } else if has_squeeze {
198 super::encode::write_modular_stream_with_squeeze(
199 image,
200 &mut section_writer,
201 self.options.use_ans,
202 )?;
203 } else if self.options.use_tree_learning && self.options.use_ans {
204 write_modular_stream_with_tree(
206 image,
207 &mut section_writer,
208 &self.options.profile,
209 image.channels.len() >= 3,
210 self.options.enable_lz77,
211 self.options.lz77_method,
212 )?;
213 } else if image.channels.len() >= 3 {
214 super::encode::write_modular_stream_with_rct(
215 image,
216 &mut section_writer,
217 self.options.use_ans,
218 )?;
219 } else {
220 write_improved_modular_stream(image, &mut section_writer, self.options.use_ans)?;
221 }
222
223 let section_data = section_writer.finish();
224 self.write_toc(writer, section_data.len())?;
225 for byte in section_data {
226 writer.write_u8(byte)?;
227 }
228 } else {
229 self.encode_modular_multi_group_inner(image, writer, Some(patches))?;
232 }
233
234 Ok(())
235 }
236
237 pub fn encode_modular(
239 &self,
240 image: &ModularImage,
241 _color_encoding: &ColorEncoding,
242 writer: &mut BitWriter,
243 ) -> Result<()> {
244 let num_extra_channels = if image.has_alpha { 1 } else { 0 };
246
247 {
249 let mut fh = FrameHeader::lossless();
250 fh.ec_upsampling = vec![1; num_extra_channels];
251 fh.ec_blend_modes = vec![BlendMode::Replace; num_extra_channels];
252 fh.have_animation = self.options.have_animation;
253 fh.duration = self.options.duration;
254 fh.is_last = self.options.is_last;
255 if let Some(ref crop) = self.options.crop {
256 fh.x0 = crop.x0;
257 fh.y0 = crop.y0;
258 fh.width = crop.width;
259 fh.height = crop.height;
260 fh.blend_mode = BlendMode::Replace;
261 fh.blend_source = 1;
262 }
263 if self.options.have_animation && !self.options.is_last {
266 fh.save_as_reference = 1;
267 }
268 fh.write(writer)?;
269 }
270
271 let num_groups = self.num_groups();
272
273 if num_groups == 1 {
274 let mut section_writer = BitWriter::new();
276 let has_squeeze = self.options.use_squeeze
277 && !super::squeeze::default_squeeze_params(image).is_empty();
278
279 if self.options.lossy_palette && image.channels.len() >= 3 {
280 let max_colors = 1usize << image.bit_depth.min(12);
282 super::encode::write_modular_stream_with_lossy_palette(
283 image,
284 &mut section_writer,
285 self.options.use_ans,
286 0,
287 image.channels.len().min(3),
288 max_colors,
289 )?;
290 } else if has_squeeze && self.options.use_tree_learning && self.options.use_ans {
291 super::encode::write_modular_stream_with_squeeze_and_tree(
293 image,
294 &mut section_writer,
295 &self.options.profile,
296 self.options.enable_lz77,
297 self.options.lz77_method,
298 )?;
299 } else if has_squeeze {
300 super::encode::write_modular_stream_with_squeeze(
302 image,
303 &mut section_writer,
304 self.options.use_ans,
305 )?;
306 } else if self.options.use_tree_learning && self.options.use_ans {
307 write_modular_stream_with_tree(
309 image,
310 &mut section_writer,
311 &self.options.profile, image.channels.len() >= 3, self.options.enable_lz77,
314 self.options.lz77_method,
315 )?;
316 } else if image.channels.len() >= 3 {
317 super::encode::write_modular_stream_with_rct(
318 image,
319 &mut section_writer,
320 self.options.use_ans,
321 )?;
322 } else {
323 write_improved_modular_stream(image, &mut section_writer, self.options.use_ans)?;
324 }
325
326 let section_data = section_writer.finish();
327 let section_size = section_data.len();
328
329 crate::trace::debug_eprintln!("FRAME_ENCODER: section_size = {} bytes", section_size);
330
331 self.write_toc(writer, section_size)?;
333
334 for byte in section_data {
336 writer.write_u8(byte)?;
337 }
338 } else if self.options.lossy_palette && image.channels.len() >= 3 {
339 self.encode_modular_multi_group_lossy_palette(image, writer)?;
341 } else if self.options.use_squeeze
342 && !super::squeeze::default_squeeze_params(image).is_empty()
343 {
344 if self.options.use_tree_learning && self.options.use_ans {
345 self.encode_modular_multi_group_squeeze_with_tree(image, writer)?;
347 } else {
348 self.encode_modular_multi_group_squeeze(image, writer)?;
350 }
351 } else {
352 self.encode_modular_multi_group(image, writer)?;
354 }
355
356 Ok(())
357 }
358
359 fn encode_modular_multi_group(
367 &self,
368 image: &ModularImage,
369 writer: &mut BitWriter,
370 ) -> Result<()> {
371 self.encode_modular_multi_group_inner(image, writer, None)
372 }
373
374 fn encode_modular_multi_group_inner(
377 &self,
378 image: &ModularImage,
379 writer: &mut BitWriter,
380 patches: Option<&crate::vardct::patches::PatchesData>,
381 ) -> Result<()> {
382 let num_groups = self.num_groups();
383 let num_lf_groups = self.num_lf_groups();
384 let num_passes = 1;
385
386 crate::trace::debug_eprintln!(
387 "MULTI_GROUP: Encoding {}x{} image with {} groups, {} lf_groups",
388 self.width,
389 self.height,
390 num_groups,
391 num_lf_groups
392 );
393
394 let has_rct = !self.options.skip_rct && image.channels.len() >= 3;
409 let num_color_channels = if has_rct {
410 3
411 } else {
412 image.channels.len().min(3)
413 };
414 let try_compact = self.options.use_tree_learning && self.options.use_ans;
415
416 let compact_analyses: Vec<(usize, super::palette::PaletteAnalysis)> = if try_compact {
417 (0..num_color_channels)
422 .filter_map(|ch_idx| {
423 let analysis =
424 analyze_channel_compact(&image.channels[ch_idx], CHANNEL_COLORS_PERCENT)?;
425 let ch = &image.channels[ch_idx];
427 let mut min_v = i32::MAX;
428 let mut max_v = i32::MIN;
429 for y in 0..ch.height() {
430 for x in 0..ch.width() {
431 let v = ch.get(x, y);
432 min_v = min_v.min(v);
433 max_v = max_v.max(v);
434 }
435 }
436 let range = (max_v as i64 - min_v as i64 + 1).max(1) as f64;
437 let density = analysis.num_colors as f64 / range;
438 crate::trace::debug_eprintln!(
439 "COMPACT_FILTER: ch={} unique={} range={:.0} density={:.3}",
440 ch_idx,
441 analysis.num_colors,
442 range,
443 density
444 );
445 if density > 0.5 {
446 return None;
447 }
448 Some((ch_idx, analysis))
449 })
450 .collect()
451 } else {
452 Vec::new()
453 };
454
455 let (meta_image, source_image_owned, compact_info, rct_type);
456 if !compact_analyses.is_empty() {
457 let mut palettes: Vec<Channel> = Vec::new();
461 let mut non_meta: Vec<Channel> = Vec::new();
462 let mut info: Vec<(usize, usize)> = Vec::new();
463 let mut nb_meta = 0usize;
464
465 for (orig_idx, ch) in image.channels.iter().enumerate() {
466 if let Some((_, analysis)) =
467 compact_analyses.iter().find(|(idx, _)| *idx == orig_idx)
468 {
469 let mut pal_ch = Channel::new(analysis.num_colors, 1)?;
471 for (i, color) in analysis.palette.iter().enumerate() {
472 pal_ch.set(i, 0, color[0]);
473 }
474 palettes.push(pal_ch);
475
476 let mut idx_ch = Channel::new(ch.width(), ch.height())?;
478 for y in 0..ch.height() {
479 for x in 0..ch.width() {
480 let val = ch.get(x, y);
481 let index = analysis.color_to_index[&vec![val]];
482 idx_ch.set(x, y, index);
483 }
484 }
485 non_meta.push(idx_ch);
486
487 let begin_c = orig_idx + nb_meta;
489 info.push((begin_c, analysis.num_colors));
490 nb_meta += 1;
491 } else {
492 non_meta.push(ch.clone());
493 }
494 }
495
496 palettes.reverse();
499
500 crate::trace::debug_eprintln!(
501 "MULTI_GROUP_COMPACT: {} channels compacted, {} meta + {} non-meta, info={:?}",
502 compact_analyses.len(),
503 nb_meta,
504 non_meta.len(),
505 info,
506 );
507
508 let mut meta_img = image.clone();
510 meta_img.channels = palettes;
511
512 let mut work = image.clone();
514 work.channels = non_meta;
515
516 if has_rct && work.channels.len() >= 3 {
518 let (selected_rct, transformed) =
519 select_best_rct_at(&work, 0, self.options.profile.nb_rcts_to_try);
520 rct_type = Some(selected_rct);
521 work = transformed;
522 } else {
523 rct_type = None;
524 }
525
526 meta_image = Some(meta_img);
527 source_image_owned = work;
528 compact_info = info;
529 } else {
530 if has_rct {
532 let (selected_rct, rct_image) =
533 super::encode::select_best_rct(image, self.options.profile.nb_rcts_to_try);
534 rct_type = Some(selected_rct);
535 source_image_owned = rct_image;
536 } else {
537 rct_type = None;
538 source_image_owned = image.clone();
539 }
540 meta_image = None;
541 compact_info = Vec::new();
542 };
543
544 let global_transforms = super::section::GlobalTransforms {
545 compact_info,
546 rct_type,
547 };
548
549 let mut group_images: Vec<ModularImage> = Vec::with_capacity(num_groups);
552 let group_transforms: Vec<super::section::GroupTransforms> =
553 vec![super::section::GroupTransforms::none(); num_groups];
554 for group_idx in 0..num_groups {
555 let (x_start, y_start, x_end, y_end) = self.group_bounds(group_idx);
556 let group_image = source_image_owned.extract_region(x_start, y_start, x_end, y_end)?;
557 group_images.push(group_image);
558 }
559
560 let mut lf_global_writer = BitWriter::new();
562
563 if let Some(pd) = patches {
565 crate::vardct::patches::encode_patches_section(
566 pd,
567 self.options.use_ans,
568 &mut lf_global_writer,
569 )?;
570 }
571
572 let global_state = if self.options.use_tree_learning && self.options.use_ans {
573 write_global_modular_section_with_tree(
575 &group_images,
576 &mut lf_global_writer,
577 &self.options.profile, global_transforms,
579 self.options.enable_lz77,
580 self.options.lz77_method,
581 meta_image.as_ref(),
582 )?
583 } else {
584 let mut all_residuals = Vec::new();
586 let mut max_residual: u32 = 0;
587 for group_image in &group_images {
588 let (group_residuals, group_max) = collect_all_residuals(group_image);
589 all_residuals.extend(group_residuals);
590 max_residual = max_residual.max(group_max);
591 }
592 let (histogram, max_token) =
593 build_histogram_from_residuals(&all_residuals, max_residual);
594
595 crate::trace::debug_eprintln!(
596 "MULTI_GROUP: {} total residuals, max_raw={}, max_token={}, {} unique tokens",
597 all_residuals.len(),
598 max_residual,
599 max_token,
600 histogram.iter().filter(|&&c| c > 0).count()
601 );
602
603 write_global_modular_section(
604 &all_residuals,
605 &histogram,
606 max_token,
607 &mut lf_global_writer,
608 self.options.use_ans,
609 global_transforms,
610 )?
611 };
612 let lf_global_data = lf_global_writer.finish();
613
614 crate::trace::debug_eprintln!(
615 "MULTI_GROUP: LfGlobal section = {} bytes",
616 lf_global_data.len()
617 );
618
619 let hf_global_data: Vec<u8> = Vec::new();
621 crate::trace::debug_eprintln!(
622 "MULTI_GROUP: HfGlobal section = 0 bytes (empty for modular)"
623 );
624
625 let lf_group_data: Vec<Vec<u8>> = (0..num_lf_groups).map(|_| Vec::new()).collect();
627 crate::trace::debug_eprintln!(
628 "MULTI_GROUP: {} LfGroup sections = 0 bytes each (empty for modular)",
629 num_lf_groups
630 );
631
632 let per_group_id_offset: u32 = if meta_image.is_some() { 1 } else { 0 };
639 let pass_group_data: Vec<Vec<u8>> =
641 crate::parallel::parallel_map_result(num_groups * num_passes, |flat_idx| {
642 let group_idx = flat_idx / num_passes;
643 let group_image = &group_images[group_idx];
644
645 let mut group_writer = BitWriter::new();
646 write_group_modular_section_idx(
647 group_image,
648 &global_state,
649 group_idx as u32 + per_group_id_offset,
650 &group_transforms[group_idx],
651 &mut group_writer,
652 )?;
653
654 crate::trace::debug_eprintln!(
655 "MULTI_GROUP: PassGroup {} section = {} bytes",
656 group_idx,
657 group_writer.bits_written() / 8,
658 );
659 Ok(group_writer.finish())
660 })?;
661
662 let mut section_sizes = Vec::with_capacity(2 + num_lf_groups + num_groups * num_passes);
666 section_sizes.push(lf_global_data.len());
667 for data in &lf_group_data {
668 section_sizes.push(data.len());
669 }
670 section_sizes.push(hf_global_data.len());
671 for data in &pass_group_data {
672 section_sizes.push(data.len());
673 }
674
675 crate::trace::debug_eprintln!(
676 "MULTI_GROUP: {} total sections, sizes = {:?}",
677 section_sizes.len(),
678 section_sizes
679 );
680
681 self.write_toc_multi(writer, §ion_sizes)?;
682
683 for byte in lf_global_data {
685 writer.write_u8(byte)?;
686 }
687 for data in lf_group_data {
688 for byte in data {
689 writer.write_u8(byte)?;
690 }
691 }
692 for byte in hf_global_data {
693 writer.write_u8(byte)?;
694 }
695 for data in pass_group_data {
696 for byte in data {
697 writer.write_u8(byte)?;
698 }
699 }
700
701 Ok(())
702 }
703
704 fn encode_modular_multi_group_lossy_palette(
715 &self,
716 image: &ModularImage,
717 writer: &mut BitWriter,
718 ) -> Result<()> {
719 use super::encode::{
720 write_gradient_tree_tokens, write_hybrid_data_histogram,
721 write_tree_histogram_for_gradient,
722 };
723 use super::encode_transforms::write_palette_transform;
724 use super::predictor::pack_signed;
725 use crate::entropy_coding::encode::{build_entropy_code_ans, write_tokens_ans};
726 use crate::entropy_coding::hybrid_uint::HybridUintConfig;
727 use crate::entropy_coding::token::Token as AnsToken;
728
729 const MODULAR_HYBRID_UINT: HybridUintConfig = HybridUintConfig {
730 split_exponent: 4,
731 split: 16,
732 msb_in_token: 2,
733 lsb_in_token: 0,
734 };
735
736 let num_groups = self.num_groups();
737 let num_lf_groups = self.num_lf_groups();
738
739 let mut transformed = image.clone();
741 let max_colors = 1usize << image.bit_depth.min(12);
742 let num_c = image.channels.len().min(3);
743 let result = super::palette::apply_lossy_palette(&mut transformed, 0, num_c, max_colors);
744 let result = match result {
745 Some(r) => r,
746 None => {
747 return self.encode_modular_multi_group_inner(image, writer, None);
749 }
750 };
751
752 crate::trace::debug_eprintln!(
753 "LOSSY_PALETTE_MULTI: {} colors + {} deltas, predictor={}, {} → {} channels, {}x{}",
754 result.nb_colors,
755 result.nb_deltas,
756 result.predictor,
757 image.channels.len(),
758 transformed.channels.len(),
759 self.width,
760 self.height,
761 );
762
763 let palette_meta = transformed.channels[0].clone();
766
767 let spatial_image = ModularImage {
769 channels: transformed.channels[1..].to_vec(),
770 bit_depth: transformed.bit_depth,
771 is_grayscale: transformed.is_grayscale,
772 has_alpha: transformed.has_alpha,
773 };
774
775 let mut group_images: Vec<ModularImage> = Vec::with_capacity(num_groups);
777 for group_idx in 0..num_groups {
778 let (x_start, y_start, x_end, y_end) = self.group_bounds(group_idx);
779 let group_image = spatial_image.extract_region(x_start, y_start, x_end, y_end)?;
780 group_images.push(group_image);
781 }
782
783 let predict_gradient = |left: i32, top: i32, topleft: i32| -> i32 {
785 let grad = left + top - topleft;
786 grad.clamp(left.min(top), left.max(top))
787 };
788
789 let collect_channel_residuals = |channel: &super::channel::Channel| -> Vec<u32> {
790 let w = channel.width();
791 let h = channel.height();
792 let mut residuals = Vec::with_capacity(w * h);
793 for y in 0..h {
794 for x in 0..w {
795 let pixel = channel.get(x, y);
796 let left = if x > 0 { channel.get(x - 1, y) } else { 0 };
797 let top = if y > 0 { channel.get(x, y - 1) } else { left };
798 let topleft = if x > 0 && y > 0 {
799 channel.get(x - 1, y - 1)
800 } else {
801 left
802 };
803 let prediction = predict_gradient(left, top, topleft);
804 residuals.push(pack_signed(pixel - prediction));
805 }
806 }
807 residuals
808 };
809
810 let palette_residuals = collect_channel_residuals(&palette_meta);
812
813 let mut all_residuals = palette_residuals.clone();
815 for group_image in &group_images {
816 for channel in &group_image.channels {
817 all_residuals.extend(collect_channel_residuals(channel));
818 }
819 }
820
821 let mut max_token: u32 = 0;
823 for &r in &all_residuals {
824 let (token, _, _) = MODULAR_HYBRID_UINT.encode(r);
825 max_token = max_token.max(token);
826 }
827
828 let mut lf_global_writer = BitWriter::new();
830
831 lf_global_writer.write(1, 1)?;
833 lf_global_writer.write(1, 1)?;
835
836 let (tree_depths, tree_codes) = write_tree_histogram_for_gradient(&mut lf_global_writer)?;
838 write_gradient_tree_tokens(&mut lf_global_writer, &tree_depths, &tree_codes)?;
839
840 let use_ans = self.options.use_ans;
842
843 enum EntropyState {
844 Huffman {
845 depths: Vec<u8>,
846 codes: Vec<u16>,
847 },
848 Ans {
849 code: crate::entropy_coding::encode::OwnedAnsEntropyCode,
850 },
851 }
852
853 let entropy_state = if use_ans {
854 let tokens: Vec<AnsToken> =
855 all_residuals.iter().map(|&r| AnsToken::new(0, r)).collect();
856 let code = build_entropy_code_ans(&tokens, 1);
857 super::section::write_ans_modular_header(&mut lf_global_writer, &code)?;
858 EntropyState::Ans { code }
859 } else {
860 let histogram_size = (max_token + 1) as usize;
861 let mut histogram = vec![0u32; histogram_size];
862 for &r in &all_residuals {
863 let (token, _, _) = MODULAR_HYBRID_UINT.encode(r);
864 histogram[token as usize] += 1;
865 }
866 let (depths, codes) =
867 write_hybrid_data_histogram(&mut lf_global_writer, &histogram, max_token)?;
868 EntropyState::Huffman { depths, codes }
869 };
870
871 lf_global_writer.write(1, 1)?; lf_global_writer.write(1, 1)?; lf_global_writer.write(2, 1)?; write_palette_transform(
876 &mut lf_global_writer,
877 0,
878 num_c,
879 result.nb_colors,
880 result.nb_deltas,
881 result.predictor,
882 )?;
883
884 let encode_residuals =
886 |residuals: &[u32], writer: &mut BitWriter, state: &EntropyState| -> Result<()> {
887 match state {
888 EntropyState::Huffman { depths, codes } => {
889 for &r in residuals {
890 let (token, extra_bits, num_extra) = MODULAR_HYBRID_UINT.encode(r);
891 let depth = depths.get(token as usize).copied().unwrap_or(0);
892 let code = codes.get(token as usize).copied().unwrap_or(0);
893 if depth > 0 {
894 writer.write(depth as usize, code as u64)?;
895 }
896 if num_extra > 0 {
897 writer.write(num_extra as usize, extra_bits as u64)?;
898 }
899 }
900 }
901 EntropyState::Ans { code } => {
902 let tokens: Vec<AnsToken> =
903 residuals.iter().map(|&r| AnsToken::new(0, r)).collect();
904 write_tokens_ans(&tokens, code, None, writer)?;
905 }
906 }
907 Ok(())
908 };
909
910 encode_residuals(&palette_residuals, &mut lf_global_writer, &entropy_state)?;
911
912 lf_global_writer.zero_pad_to_byte();
913 let lf_global_data = lf_global_writer.finish();
914
915 crate::trace::debug_eprintln!(
916 "LOSSY_PALETTE_MULTI: LfGlobal = {} bytes (palette_meta {}x{})",
917 lf_global_data.len(),
918 palette_meta.width(),
919 palette_meta.height(),
920 );
921
922 let hf_global_data: Vec<u8> = Vec::new();
924
925 let lf_group_data: Vec<Vec<u8>> = (0..num_lf_groups).map(|_| Vec::new()).collect();
927
928 let pass_group_data: Vec<Vec<u8>> =
930 crate::parallel::parallel_map_result(num_groups, |g| {
931 let group_image = &group_images[g];
932 let mut group_writer = BitWriter::new();
933
934 group_writer.write(1, 1)?; group_writer.write(1, 1)?; group_writer.write(2, 0)?; let mut section_residuals: Vec<u32> = Vec::new();
941 for channel in &group_image.channels {
942 section_residuals.extend(collect_channel_residuals(channel));
943 }
944 encode_residuals(§ion_residuals, &mut group_writer, &entropy_state)?;
945
946 group_writer.zero_pad_to_byte();
947 let data = group_writer.finish();
948 crate::trace::debug_eprintln!(
949 "LOSSY_PALETTE_MULTI: PassGroup[{}] = {} bytes",
950 g,
951 data.len(),
952 );
953 Ok(data)
954 })?;
955
956 let mut section_sizes = Vec::with_capacity(2 + num_lf_groups + num_groups);
959 section_sizes.push(lf_global_data.len());
960 for data in &lf_group_data {
961 section_sizes.push(data.len());
962 }
963 section_sizes.push(hf_global_data.len());
964 for data in &pass_group_data {
965 section_sizes.push(data.len());
966 }
967
968 self.write_toc_multi(writer, §ion_sizes)?;
969
970 for byte in lf_global_data {
972 writer.write_u8(byte)?;
973 }
974 for data in lf_group_data {
975 for byte in data {
976 writer.write_u8(byte)?;
977 }
978 }
979 for byte in hf_global_data {
980 writer.write_u8(byte)?;
981 }
982 for data in pass_group_data {
983 for byte in data {
984 writer.write_u8(byte)?;
985 }
986 }
987
988 Ok(())
989 }
990
991 fn encode_modular_multi_group_squeeze(
998 &self,
999 image: &ModularImage,
1000 writer: &mut BitWriter,
1001 ) -> Result<()> {
1002 use super::encode::{
1003 write_gradient_tree_tokens, write_rct_transform, write_squeeze_transform,
1004 write_tree_histogram_for_gradient,
1005 };
1006 use super::predictor::pack_signed;
1007 use super::rct::{RctType, forward_rct};
1008 use super::squeeze::{apply_squeeze, default_squeeze_params};
1009 use crate::entropy_coding::encode::{build_entropy_code_ans, write_tokens_ans};
1010 use crate::entropy_coding::hybrid_uint::HybridUintConfig;
1011 use crate::entropy_coding::token::Token as AnsToken;
1012
1013 const MODULAR_HYBRID_UINT: HybridUintConfig = HybridUintConfig {
1014 split_exponent: 4,
1015 split: 16,
1016 msb_in_token: 2,
1017 lsb_in_token: 0,
1018 };
1019
1020 let num_groups = self.num_groups();
1021 let num_lf_groups = self.num_lf_groups();
1022 let lf_group_dim = GROUP_DIM * 8; let squeeze_params = default_squeeze_params(image);
1026 let mut squeezed = image.clone();
1027 let has_rct = squeezed.channels.len() >= 3;
1028 if has_rct {
1029 forward_rct(&mut squeezed.channels, 0, RctType::YCOCG)?;
1030 }
1031 apply_squeeze(&mut squeezed, &squeeze_params)?;
1032
1033 #[cfg(test)]
1034 {
1035 eprintln!(
1036 "SQUEEZE_MULTI: {} steps, {} → {} channels, image {}x{}",
1037 squeeze_params.len(),
1038 image.channels.len(),
1039 squeezed.channels.len(),
1040 self.width,
1041 self.height,
1042 );
1043 for (i, ch) in squeezed.channels.iter().enumerate() {
1044 eprintln!(
1045 " ch[{}]: {}x{} hshift={} vshift={} min_shift={}",
1046 i,
1047 ch.width(),
1048 ch.height(),
1049 ch.hshift,
1050 ch.vshift,
1051 ch.hshift.min(ch.vshift),
1052 );
1053 }
1054 }
1055
1056 let global_cutoff = squeezed
1059 .channels
1060 .iter()
1061 .position(|c| c.width() > GROUP_DIM || c.height() > GROUP_DIM)
1062 .unwrap_or(squeezed.channels.len());
1063
1064 crate::trace::debug_eprintln!(
1065 "SQUEEZE_MULTI: {} global channels (<={}x{}), {} group channels",
1066 global_cutoff,
1067 GROUP_DIM,
1068 GROUP_DIM,
1069 squeezed.channels.len() - global_cutoff,
1070 );
1071
1072 let mut lf_channel_indices: Vec<usize> = Vec::new();
1076 let mut pass_channel_indices: Vec<usize> = Vec::new();
1077 for i in global_cutoff..squeezed.channels.len() {
1078 let ch = &squeezed.channels[i];
1079 let min_shift = ch.hshift.min(ch.vshift);
1080 if min_shift >= 3 {
1081 lf_channel_indices.push(i);
1082 } else {
1083 pass_channel_indices.push(i);
1084 }
1085 }
1086
1087 #[cfg(test)]
1088 eprintln!(
1089 "SQUEEZE_MULTI: {} global, {} LfGroup (shift>=3), {} PassGroup (shift<3) channels",
1090 global_cutoff,
1091 lf_channel_indices.len(),
1092 pass_channel_indices.len(),
1093 );
1094
1095 let predict_gradient = |left: i32, top: i32, topleft: i32| -> i32 {
1097 let grad = left + top - topleft;
1098 grad.clamp(left.min(top), left.max(top))
1099 };
1100
1101 let collect_channel_residuals = |channel: &super::channel::Channel| -> Vec<u32> {
1102 let w = channel.width();
1103 let h = channel.height();
1104 let mut residuals = Vec::with_capacity(w * h);
1105 for y in 0..h {
1106 for x in 0..w {
1107 let pixel = channel.get(x, y);
1108 let left = if x > 0 { channel.get(x - 1, y) } else { 0 };
1109 let top = if y > 0 { channel.get(x, y - 1) } else { left };
1110 let topleft = if x > 0 && y > 0 {
1111 channel.get(x - 1, y - 1)
1112 } else {
1113 left
1114 };
1115 let prediction = predict_gradient(left, top, topleft);
1116 residuals.push(pack_signed(pixel - prediction));
1117 }
1118 }
1119 residuals
1120 };
1121
1122 let mut all_residuals: Vec<u32> = Vec::new();
1124 for i in 0..global_cutoff {
1125 all_residuals.extend(collect_channel_residuals(&squeezed.channels[i]));
1126 }
1127
1128 let num_lf_groups_x = self.width.div_ceil(lf_group_dim);
1132 let mut lf_group_channel_data: Vec<Vec<Vec<u32>>> = vec![Vec::new(); num_lf_groups]; for &ch_idx in &lf_channel_indices {
1134 let ch = &squeezed.channels[ch_idx];
1135 for (lg, lg_channels) in lf_group_channel_data
1136 .iter_mut()
1137 .enumerate()
1138 .take(num_lf_groups)
1139 {
1140 let lg_x = lg % num_lf_groups_x;
1141 let lg_y = lg / num_lf_groups_x;
1142 if let Some(cropped) = ch.extract_grid_cell(lg_x, lg_y, lf_group_dim) {
1143 let residuals = collect_channel_residuals(&cropped);
1144 all_residuals.extend(&residuals);
1145 lg_channels.push(residuals);
1146 }
1147 }
1148 }
1149
1150 let num_groups_x = self.num_groups_x();
1153 let mut pass_group_channel_data: Vec<Vec<Vec<u32>>> = vec![Vec::new(); num_groups]; for &ch_idx in &pass_channel_indices {
1155 let ch = &squeezed.channels[ch_idx];
1156 for (g, g_channels) in pass_group_channel_data
1157 .iter_mut()
1158 .enumerate()
1159 .take(num_groups)
1160 {
1161 let gx = g % num_groups_x;
1162 let gy = g / num_groups_x;
1163 if let Some(cropped) = ch.extract_grid_cell(gx, gy, GROUP_DIM) {
1164 let residuals = collect_channel_residuals(&cropped);
1165 all_residuals.extend(&residuals);
1166 g_channels.push(residuals);
1167 }
1168 }
1169 }
1170
1171 let mut max_token: u32 = 0;
1173 for &r in &all_residuals {
1174 let (token, _, _) = MODULAR_HYBRID_UINT.encode(r);
1175 max_token = max_token.max(token);
1176 }
1177
1178 let mut lf_global_writer = BitWriter::new();
1180
1181 lf_global_writer.write(1, 1)?;
1183 lf_global_writer.write(1, 1)?;
1185
1186 let (tree_depths, tree_codes) = write_tree_histogram_for_gradient(&mut lf_global_writer)?;
1188 write_gradient_tree_tokens(&mut lf_global_writer, &tree_depths, &tree_codes)?;
1189
1190 let use_ans = self.options.use_ans;
1192
1193 enum EntropyState {
1195 Huffman {
1196 depths: Vec<u8>,
1197 codes: Vec<u16>,
1198 },
1199 Ans {
1200 code: crate::entropy_coding::encode::OwnedAnsEntropyCode,
1201 },
1202 }
1203
1204 let entropy_state = if use_ans {
1205 let tokens: Vec<AnsToken> =
1206 all_residuals.iter().map(|&r| AnsToken::new(0, r)).collect();
1207 let code = build_entropy_code_ans(&tokens, 1);
1208 super::section::write_ans_modular_header(&mut lf_global_writer, &code)?;
1209 EntropyState::Ans { code }
1210 } else {
1211 let histogram_size = (max_token + 1) as usize;
1212 let mut histogram = vec![0u32; histogram_size];
1213 for &r in &all_residuals {
1214 let (token, _, _) = MODULAR_HYBRID_UINT.encode(r);
1215 histogram[token as usize] += 1;
1216 }
1217 let (depths, codes) = super::encode::write_hybrid_data_histogram(
1218 &mut lf_global_writer,
1219 &histogram,
1220 max_token,
1221 )?;
1222 EntropyState::Huffman { depths, codes }
1223 };
1224
1225 lf_global_writer.write(1, 1)?; lf_global_writer.write(1, 1)?; if has_rct {
1229 lf_global_writer.write(2, 2)?;
1231 lf_global_writer.write(4, 0)?;
1232 write_rct_transform(&mut lf_global_writer, 0, RctType::YCOCG)?;
1233 write_squeeze_transform(&mut lf_global_writer, &squeeze_params)?;
1234 } else {
1235 lf_global_writer.write(2, 1)?; write_squeeze_transform(&mut lf_global_writer, &squeeze_params)?;
1237 }
1238
1239 let encode_residuals =
1241 |residuals: &[u32], writer: &mut BitWriter, state: &EntropyState| -> Result<()> {
1242 match state {
1243 EntropyState::Huffman { depths, codes } => {
1244 for &r in residuals {
1245 let (token, extra_bits, num_extra) = MODULAR_HYBRID_UINT.encode(r);
1246 let depth = depths.get(token as usize).copied().unwrap_or(0);
1247 let code = codes.get(token as usize).copied().unwrap_or(0);
1248 if depth > 0 {
1249 writer.write(depth as usize, code as u64)?;
1250 }
1251 if num_extra > 0 {
1252 writer.write(num_extra as usize, extra_bits as u64)?;
1253 }
1254 }
1255 }
1256 EntropyState::Ans { code } => {
1257 let tokens: Vec<AnsToken> =
1258 residuals.iter().map(|&r| AnsToken::new(0, r)).collect();
1259 write_tokens_ans(&tokens, code, None, writer)?;
1260 }
1261 }
1262 Ok(())
1263 };
1264
1265 let mut global_residuals: Vec<u32> = Vec::new();
1267 for i in 0..global_cutoff {
1268 global_residuals.extend(collect_channel_residuals(&squeezed.channels[i]));
1269 }
1270 encode_residuals(&global_residuals, &mut lf_global_writer, &entropy_state)?;
1271
1272 lf_global_writer.zero_pad_to_byte();
1273 let lf_global_data = lf_global_writer.finish();
1274
1275 crate::trace::debug_eprintln!(
1276 "SQUEEZE_MULTI: LfGlobal = {} bytes ({} global channels)",
1277 lf_global_data.len(),
1278 global_cutoff,
1279 );
1280
1281 let mut lf_group_data: Vec<Vec<u8>> = Vec::with_capacity(num_lf_groups);
1283 for (_lg, lg_channels) in lf_group_channel_data.iter().enumerate().take(num_lf_groups) {
1284 let mut lg_writer = BitWriter::new();
1285
1286 if lg_channels.is_empty() {
1287 lf_group_data.push(lg_writer.finish());
1289 continue;
1290 }
1291
1292 lg_writer.write(1, 1)?; lg_writer.write(1, 1)?; lg_writer.write(2, 0)?; let mut section_residuals: Vec<u32> = Vec::new();
1300 for channel_residuals in lg_channels {
1301 section_residuals.extend(channel_residuals);
1302 }
1303 encode_residuals(§ion_residuals, &mut lg_writer, &entropy_state)?;
1304
1305 lg_writer.zero_pad_to_byte();
1306 let data = lg_writer.finish();
1307 crate::trace::debug_eprintln!(
1308 "SQUEEZE_MULTI: LfGroup[{}] = {} bytes ({} channels)",
1309 _lg,
1310 data.len(),
1311 lg_channels.len(),
1312 );
1313 lf_group_data.push(data);
1314 }
1315
1316 let hf_global_data: Vec<u8> = Vec::new();
1318
1319 let pass_group_data: Vec<Vec<u8>> =
1322 crate::parallel::parallel_map_result(num_groups, |g| {
1323 let g_channels = &pass_group_channel_data[g];
1324 let mut pg_writer = BitWriter::new();
1325
1326 if g_channels.is_empty() {
1327 return Ok(pg_writer.finish());
1329 }
1330
1331 pg_writer.write(1, 1)?; pg_writer.write(1, 1)?; pg_writer.write(2, 0)?; let mut section_residuals: Vec<u32> = Vec::new();
1338 for channel_residuals in g_channels {
1339 section_residuals.extend(channel_residuals);
1340 }
1341 encode_residuals(§ion_residuals, &mut pg_writer, &entropy_state)?;
1342
1343 pg_writer.zero_pad_to_byte();
1344 let data = pg_writer.finish();
1345 crate::trace::debug_eprintln!(
1346 "SQUEEZE_MULTI: PassGroup[{}] = {} bytes ({} channels)",
1347 g,
1348 data.len(),
1349 g_channels.len(),
1350 );
1351 Ok(data)
1352 })?;
1353
1354 let mut section_sizes = Vec::with_capacity(2 + num_lf_groups + num_groups);
1357 section_sizes.push(lf_global_data.len());
1358 for data in &lf_group_data {
1359 section_sizes.push(data.len());
1360 }
1361 section_sizes.push(hf_global_data.len());
1362 for data in &pass_group_data {
1363 section_sizes.push(data.len());
1364 }
1365
1366 #[cfg(test)]
1367 eprintln!(
1368 "SQUEEZE_MULTI: {} sections, sizes = {:?}",
1369 section_sizes.len(),
1370 section_sizes,
1371 );
1372
1373 self.write_toc_multi(writer, §ion_sizes)?;
1374
1375 for byte in lf_global_data {
1377 writer.write_u8(byte)?;
1378 }
1379 for data in lf_group_data {
1380 for byte in data {
1381 writer.write_u8(byte)?;
1382 }
1383 }
1384 for byte in hf_global_data {
1385 writer.write_u8(byte)?;
1386 }
1387 for data in pass_group_data {
1388 for byte in data {
1389 writer.write_u8(byte)?;
1390 }
1391 }
1392
1393 Ok(())
1394 }
1395
1396 fn encode_modular_multi_group_squeeze_with_tree(
1405 &self,
1406 image: &ModularImage,
1407 writer: &mut BitWriter,
1408 ) -> Result<()> {
1409 use super::encode::{write_rct_transform, write_squeeze_transform, write_tree};
1410 use super::rct::{RctType, forward_rct};
1411 use super::squeeze::{apply_squeeze, default_squeeze_params};
1412 use super::tree::count_contexts;
1413 use super::tree_learn::{
1414 TreeLearningParams, TreeSamples, collect_residuals_with_tree, compute_best_tree,
1415 compute_gather_stride_from_profile, gather_samples_strided,
1416 };
1417 use crate::entropy_coding::encode::build_entropy_code_ans_with_options;
1418 use crate::entropy_coding::encode::{write_entropy_code_ans, write_tokens_ans};
1419 use crate::entropy_coding::token::Token as AnsToken;
1420
1421 let num_groups = self.num_groups();
1422 let num_lf_groups = self.num_lf_groups();
1423 let lf_group_dim = GROUP_DIM * 8; let squeeze_params = default_squeeze_params(image);
1427 let mut squeezed = image.clone();
1428 let has_rct = squeezed.channels.len() >= 3;
1429 if has_rct {
1430 forward_rct(&mut squeezed.channels, 0, RctType::YCOCG)?;
1431 }
1432 apply_squeeze(&mut squeezed, &squeeze_params)?;
1433
1434 crate::trace::debug_eprintln!(
1435 "SQUEEZE_TREE_MULTI: {} steps, {} → {} channels, image {}x{}",
1436 squeeze_params.len(),
1437 image.channels.len(),
1438 squeezed.channels.len(),
1439 self.width,
1440 self.height,
1441 );
1442
1443 let global_cutoff = squeezed
1445 .channels
1446 .iter()
1447 .position(|c| c.width() > GROUP_DIM || c.height() > GROUP_DIM)
1448 .unwrap_or(squeezed.channels.len());
1449
1450 let mut lf_channel_indices: Vec<usize> = Vec::new();
1451 let mut pass_channel_indices: Vec<usize> = Vec::new();
1452 for i in global_cutoff..squeezed.channels.len() {
1453 let ch = &squeezed.channels[i];
1454 let min_shift = ch.hshift.min(ch.vshift);
1455 if min_shift >= 3 {
1456 lf_channel_indices.push(i);
1457 } else {
1458 pass_channel_indices.push(i);
1459 }
1460 }
1461
1462 crate::trace::debug_eprintln!(
1463 "SQUEEZE_TREE_MULTI: {} global, {} LfGroup, {} PassGroup channels",
1464 global_cutoff,
1465 lf_channel_indices.len(),
1466 pass_channel_indices.len(),
1467 );
1468
1469 let total_pixels: usize = squeezed
1472 .channels
1473 .iter()
1474 .map(|ch| ch.width() * ch.height())
1475 .sum();
1476 let stride = compute_gather_stride_from_profile(total_pixels, &self.options.profile);
1477
1478 let wp_params = if self.options.profile.wp_num_param_sets > 0 {
1480 super::predictor::find_best_wp_params(
1481 &squeezed.channels,
1482 self.options.profile.wp_num_param_sets,
1483 )
1484 } else {
1485 super::predictor::WeightedPredictorParams::default()
1486 };
1487
1488 let mut samples = TreeSamples::new();
1489
1490 const NUM_QUANT_TABLES: usize = 17;
1497 let stream_id_lf_base = 1 + num_lf_groups;
1498 let stream_id_hf_base = 1 + 3 * num_lf_groups + NUM_QUANT_TABLES;
1499
1500 let global_sub = ModularImage {
1502 channels: squeezed.channels[..global_cutoff].to_vec(),
1503 bit_depth: squeezed.bit_depth,
1504 is_grayscale: squeezed.is_grayscale,
1505 has_alpha: false,
1506 };
1507 gather_samples_strided(&mut samples, &global_sub, 0, 0, stride, &wp_params);
1509
1510 let num_lf_groups_x = self.width.div_ceil(lf_group_dim);
1512 let mut lf_group_sub_images: Vec<Vec<super::channel::Channel>> =
1514 vec![Vec::new(); num_lf_groups];
1515 for &ch_idx in &lf_channel_indices {
1516 let ch = &squeezed.channels[ch_idx];
1517 for (lg, lg_channels) in lf_group_sub_images.iter_mut().enumerate() {
1518 let lg_x = lg % num_lf_groups_x;
1519 let lg_y = lg / num_lf_groups_x;
1520 if let Some(cropped) = ch.extract_grid_cell(lg_x, lg_y, lf_group_dim) {
1521 lg_channels.push(cropped);
1522 }
1523 }
1524 }
1525 for (lg, lg_channels) in lf_group_sub_images.iter().enumerate() {
1563 if lg_channels.is_empty() {
1564 continue;
1565 }
1566 let sub_image = ModularImage {
1567 channels: lg_channels.clone(),
1568 bit_depth: squeezed.bit_depth,
1569 is_grayscale: squeezed.is_grayscale,
1570 has_alpha: false,
1571 };
1572 gather_samples_strided(
1574 &mut samples,
1575 &sub_image,
1576 (stream_id_lf_base + lg) as u32,
1577 0,
1578 stride,
1579 &wp_params,
1580 );
1581 }
1582
1583 let num_groups_x = self.num_groups_x();
1585 let mut pass_group_sub_images: Vec<Vec<super::channel::Channel>> =
1586 vec![Vec::new(); num_groups];
1587 for &ch_idx in &pass_channel_indices {
1588 let ch = &squeezed.channels[ch_idx];
1589 for (g, g_channels) in pass_group_sub_images.iter_mut().enumerate() {
1590 let gx = g % num_groups_x;
1591 let gy = g / num_groups_x;
1592 if let Some(cropped) = ch.extract_grid_cell(gx, gy, GROUP_DIM) {
1593 g_channels.push(cropped);
1594 }
1595 }
1596 }
1597 for (g, g_channels) in pass_group_sub_images.iter().enumerate() {
1599 if g_channels.is_empty() {
1600 continue;
1601 }
1602 let sub_image = ModularImage {
1603 channels: g_channels.clone(),
1604 bit_depth: squeezed.bit_depth,
1605 is_grayscale: squeezed.is_grayscale,
1606 has_alpha: false,
1607 };
1608 gather_samples_strided(
1610 &mut samples,
1611 &sub_image,
1612 (stream_id_hf_base + g) as u32,
1613 0,
1614 stride,
1615 &wp_params,
1616 );
1617 }
1618
1619 let pixel_fraction = if total_pixels > 0 {
1621 samples.num_samples as f64 / total_pixels as f64
1622 } else {
1623 1.0
1624 };
1625 let tree_params = TreeLearningParams::from_profile(&self.options.profile)
1626 .with_pixel_fraction(pixel_fraction)
1627 .with_total_pixels(total_pixels);
1628 let tree = compute_best_tree(&mut samples, &tree_params);
1629 let num_contexts = count_contexts(&tree) as usize;
1630
1631 crate::trace::debug_eprintln!(
1632 "SQUEEZE_TREE_MULTI: {} tree nodes, {} contexts from {} samples (pf={:.3})",
1633 tree.len(),
1634 num_contexts,
1635 samples.num_samples,
1636 pixel_fraction,
1637 );
1638
1639 let mut global_tokens = collect_residuals_with_tree(&global_sub, &tree, 0, &wp_params);
1642
1643 let mut lf_group_tokens: Vec<Vec<AnsToken>> = Vec::with_capacity(num_lf_groups);
1645 for (lg, lg_channels) in lf_group_sub_images.iter().enumerate() {
1646 if lg_channels.is_empty() {
1647 lf_group_tokens.push(Vec::new());
1648 continue;
1649 }
1650 let sub_image = ModularImage {
1651 channels: lg_channels.clone(),
1652 bit_depth: squeezed.bit_depth,
1653 is_grayscale: squeezed.is_grayscale,
1654 has_alpha: false,
1655 };
1656 let tokens = collect_residuals_with_tree(
1657 &sub_image,
1658 &tree,
1659 (stream_id_lf_base + lg) as u32,
1660 &wp_params,
1661 );
1662 lf_group_tokens.push(tokens);
1663 }
1664
1665 let mut pass_group_tokens: Vec<Vec<AnsToken>> = Vec::with_capacity(num_groups);
1667 for (g, g_channels) in pass_group_sub_images.iter().enumerate() {
1668 if g_channels.is_empty() {
1669 pass_group_tokens.push(Vec::new());
1670 continue;
1671 }
1672 let sub_image = ModularImage {
1673 channels: g_channels.clone(),
1674 bit_depth: squeezed.bit_depth,
1675 is_grayscale: squeezed.is_grayscale,
1676 has_alpha: false,
1677 };
1678 let tokens = collect_residuals_with_tree(
1679 &sub_image,
1680 &tree,
1681 (stream_id_hf_base + g) as u32,
1682 &wp_params,
1683 );
1684 pass_group_tokens.push(tokens);
1685 }
1686
1687 let use_lz77 = self.options.enable_lz77;
1692 let lz77_method = self.options.lz77_method;
1693 let lz77_params = if use_lz77 {
1694 use crate::entropy_coding::lz77::apply_lz77;
1695
1696 let try_lz77 = |tokens: &[AnsToken], dist_multiplier: i32| -> Vec<AnsToken> {
1697 if tokens.is_empty() {
1698 return tokens.to_vec();
1699 }
1700 match apply_lz77(tokens, num_contexts, false, lz77_method, dist_multiplier) {
1701 Some((lz77_tokens, _)) => lz77_tokens,
1702 None => tokens.to_vec(),
1703 }
1704 };
1705
1706 let global_dm = squeezed.channels[..global_cutoff]
1708 .iter()
1709 .map(|c| c.width())
1710 .max()
1711 .unwrap_or(0) as i32;
1712 global_tokens = try_lz77(&global_tokens, global_dm);
1713
1714 for (lg, lg_tokens) in lf_group_tokens.iter_mut().enumerate() {
1716 let dm = lf_group_sub_images[lg]
1717 .iter()
1718 .map(|c| c.width())
1719 .max()
1720 .unwrap_or(0) as i32;
1721 *lg_tokens = try_lz77(lg_tokens, dm);
1722 }
1723
1724 for (g, pg_tokens) in pass_group_tokens.iter_mut().enumerate() {
1726 let dm = pass_group_sub_images[g]
1727 .iter()
1728 .map(|c| c.width())
1729 .max()
1730 .unwrap_or(0) as i32;
1731 *pg_tokens = try_lz77(pg_tokens, dm);
1732 }
1733
1734 let has_lz77 = global_tokens.iter().any(|t| t.is_lz77_length())
1736 || lf_group_tokens
1737 .iter()
1738 .any(|ts| ts.iter().any(|t| t.is_lz77_length()))
1739 || pass_group_tokens
1740 .iter()
1741 .any(|ts| ts.iter().any(|t| t.is_lz77_length()));
1742
1743 if has_lz77 {
1744 let mut params = crate::entropy_coding::lz77::Lz77Params::new(num_contexts, false);
1745 params.enabled = true;
1746 Some(params)
1747 } else {
1748 None
1749 }
1750 } else {
1751 None
1752 };
1753 let ans_num_contexts = if lz77_params.is_some() {
1754 num_contexts + 1
1755 } else {
1756 num_contexts
1757 };
1758
1759 let mut all_tokens: Vec<AnsToken> = Vec::new();
1761 all_tokens.extend(&global_tokens);
1762 for lg_tokens in &lf_group_tokens {
1763 all_tokens.extend(lg_tokens);
1764 }
1765 for pg_tokens in &pass_group_tokens {
1766 all_tokens.extend(pg_tokens);
1767 }
1768 let code = build_entropy_code_ans_with_options(
1769 &all_tokens,
1770 ans_num_contexts,
1771 true, true, lz77_params.as_ref(),
1774 Some(total_pixels),
1775 );
1776
1777 let mut lf_global_writer = BitWriter::new();
1779
1780 lf_global_writer.write(1, 1)?;
1782 lf_global_writer.write(1, 1)?;
1784
1785 write_tree(&mut lf_global_writer, &tree)?;
1787
1788 if ans_num_contexts > 1 {
1790 crate::entropy_coding::lz77::write_lz77_header(
1791 lz77_params.as_ref(),
1792 &mut lf_global_writer,
1793 )?;
1794 write_entropy_code_ans(&code, &mut lf_global_writer)?;
1795 } else {
1796 super::section::write_ans_modular_header(&mut lf_global_writer, &code)?;
1797 }
1798
1799 lf_global_writer.write(1, 1)?; super::encode::write_wp_header(&mut lf_global_writer, &wp_params)?;
1802 if has_rct {
1803 lf_global_writer.write(2, 2)?;
1805 lf_global_writer.write(4, 0)?;
1806 write_rct_transform(&mut lf_global_writer, 0, RctType::YCOCG)?;
1807 write_squeeze_transform(&mut lf_global_writer, &squeeze_params)?;
1808 } else {
1809 lf_global_writer.write(2, 1)?; write_squeeze_transform(&mut lf_global_writer, &squeeze_params)?;
1811 }
1812
1813 write_tokens_ans(
1815 &global_tokens,
1816 &code,
1817 lz77_params.as_ref(),
1818 &mut lf_global_writer,
1819 )?;
1820
1821 lf_global_writer.zero_pad_to_byte();
1822 let lf_global_data = lf_global_writer.finish();
1823
1824 crate::trace::debug_eprintln!(
1825 "SQUEEZE_TREE_MULTI: LfGlobal = {} bytes ({} global channels, {} contexts)",
1826 lf_global_data.len(),
1827 global_cutoff,
1828 num_contexts,
1829 );
1830
1831 let mut lf_group_data: Vec<Vec<u8>> = Vec::with_capacity(num_lf_groups);
1833 for lg_tokens in &lf_group_tokens {
1834 let mut lg_writer = BitWriter::new();
1835
1836 if lg_tokens.is_empty() {
1837 lf_group_data.push(lg_writer.finish());
1838 continue;
1839 }
1840
1841 lg_writer.write(1, 1)?; super::encode::write_wp_header(&mut lg_writer, &wp_params)?;
1844 lg_writer.write(2, 0)?; write_tokens_ans(lg_tokens, &code, lz77_params.as_ref(), &mut lg_writer)?;
1847
1848 lg_writer.zero_pad_to_byte();
1849 let data = lg_writer.finish();
1850 crate::trace::debug_eprintln!(
1851 "SQUEEZE_TREE_MULTI: LfGroup = {} bytes ({} tokens)",
1852 data.len(),
1853 lg_tokens.len(),
1854 );
1855 lf_group_data.push(data);
1856 }
1857
1858 let hf_global_data: Vec<u8> = Vec::new();
1860
1861 let pass_group_data: Vec<Vec<u8>> =
1863 crate::parallel::parallel_map_result(num_groups, |g| {
1864 let pg_tokens = &pass_group_tokens[g];
1865 let mut pg_writer = BitWriter::new();
1866
1867 if pg_tokens.is_empty() {
1868 return Ok(pg_writer.finish());
1869 }
1870
1871 pg_writer.write(1, 1)?; super::encode::write_wp_header(&mut pg_writer, &wp_params)?;
1874 pg_writer.write(2, 0)?; write_tokens_ans(pg_tokens, &code, lz77_params.as_ref(), &mut pg_writer)?;
1877
1878 pg_writer.zero_pad_to_byte();
1879 let data = pg_writer.finish();
1880 crate::trace::debug_eprintln!(
1881 "SQUEEZE_TREE_MULTI: PassGroup = {} bytes ({} tokens)",
1882 data.len(),
1883 pg_tokens.len(),
1884 );
1885 Ok(data)
1886 })?;
1887
1888 let mut section_sizes = Vec::with_capacity(2 + num_lf_groups + num_groups);
1890 section_sizes.push(lf_global_data.len());
1891 for data in &lf_group_data {
1892 section_sizes.push(data.len());
1893 }
1894 section_sizes.push(hf_global_data.len());
1895 for data in &pass_group_data {
1896 section_sizes.push(data.len());
1897 }
1898
1899 self.write_toc_multi(writer, §ion_sizes)?;
1900
1901 for byte in lf_global_data {
1903 writer.write_u8(byte)?;
1904 }
1905 for data in lf_group_data {
1906 for byte in data {
1907 writer.write_u8(byte)?;
1908 }
1909 }
1910 for byte in hf_global_data {
1911 writer.write_u8(byte)?;
1912 }
1913 for data in pass_group_data {
1914 for byte in data {
1915 writer.write_u8(byte)?;
1916 }
1917 }
1918
1919 Ok(())
1920 }
1921
1922 pub(crate) fn encode_modular_body(
1929 &self,
1930 image: &ModularImage,
1931 writer: &mut BitWriter,
1932 ) -> Result<()> {
1933 let num_groups = self.num_groups();
1934
1935 if num_groups == 1 {
1936 let mut section_writer = BitWriter::new();
1938
1939 let use_rct = image.channels.len() >= 3 && !self.options.skip_rct;
1940 if self.options.use_tree_learning && self.options.use_ans {
1941 write_modular_stream_with_tree(
1942 image,
1943 &mut section_writer,
1944 &self.options.profile,
1945 use_rct,
1946 self.options.enable_lz77,
1947 self.options.lz77_method,
1948 )?;
1949 } else if use_rct {
1950 super::encode::write_modular_stream_with_rct(
1951 image,
1952 &mut section_writer,
1953 self.options.use_ans,
1954 )?;
1955 } else {
1956 write_improved_modular_stream(image, &mut section_writer, self.options.use_ans)?;
1957 }
1958
1959 let section_data = section_writer.finish();
1960 self.write_toc(writer, section_data.len())?;
1961 for byte in section_data {
1962 writer.write_u8(byte)?;
1963 }
1964 } else {
1965 self.encode_modular_multi_group_inner(image, writer, None)?;
1967 }
1968
1969 Ok(())
1970 }
1971
1972 fn write_toc(&self, writer: &mut BitWriter, section_size: usize) -> Result<()> {
1974 self.write_toc_multi(writer, &[section_size])
1975 }
1976
1977 fn write_toc_multi(&self, writer: &mut BitWriter, section_sizes: &[usize]) -> Result<()> {
1979 crate::trace::debug_eprintln!("TOC [bit {}]: Writing permuted = 0", writer.bits_written());
1980 writer.write(1, 0)?;
1982
1983 crate::trace::debug_eprintln!(
1984 "TOC [bit {}]: After permuted, byte aligning",
1985 writer.bits_written()
1986 );
1987 writer.zero_pad_to_byte();
1989
1990 #[allow(clippy::unused_enumerate_index)]
1992 for (_i, &size) in section_sizes.iter().enumerate() {
1993 crate::trace::debug_eprintln!(
1994 "TOC [bit {}]: Writing entry {} size={}",
1995 writer.bits_written(),
1996 _i,
1997 size
1998 );
1999 self.write_toc_entry(writer, size as u32)?;
2000 }
2001 crate::trace::debug_eprintln!("TOC [bit {}]: After TOC entries", writer.bits_written());
2002
2003 writer.zero_pad_to_byte();
2005
2006 Ok(())
2007 }
2008
2009 fn write_toc_entry(&self, writer: &mut BitWriter, size: u32) -> Result<()> {
2011 if size < 1024 {
2013 writer.write(2, 0)?; writer.write(10, size as u64)?;
2015 } else if size < 17408 {
2016 writer.write(2, 1)?; writer.write(14, (size - 1024) as u64)?;
2018 } else if size < 4211712 {
2019 writer.write(2, 2)?; writer.write(22, (size - 17408) as u64)?;
2021 } else {
2022 writer.write(2, 3)?; writer.write(30, (size - 4211712) as u64)?;
2024 }
2025 Ok(())
2026 }
2027
2028 pub fn num_groups(&self) -> usize {
2030 let num_groups_x = self.width.div_ceil(GROUP_DIM);
2031 let num_groups_y = self.height.div_ceil(GROUP_DIM);
2032 num_groups_x * num_groups_y
2033 }
2034
2035 pub fn num_groups_x(&self) -> usize {
2037 self.width.div_ceil(GROUP_DIM)
2038 }
2039
2040 pub fn num_groups_y(&self) -> usize {
2042 self.height.div_ceil(GROUP_DIM)
2043 }
2044
2045 pub fn num_lf_groups(&self) -> usize {
2048 let lf_group_dim = GROUP_DIM * 8; let lf_groups_x = self.width.div_ceil(lf_group_dim);
2050 let lf_groups_y = self.height.div_ceil(lf_group_dim);
2051 lf_groups_x * lf_groups_y
2052 }
2053
2054 pub fn num_toc_entries(&self, num_passes: usize) -> usize {
2058 let num_groups = self.num_groups();
2059 if num_groups == 1 && num_passes == 1 {
2060 1
2061 } else {
2062 2 + self.num_lf_groups() + num_groups * num_passes
2063 }
2064 }
2065
2066 pub fn group_bounds(&self, group_idx: usize) -> (usize, usize, usize, usize) {
2069 let num_groups_x = self.num_groups_x();
2070 let gx = group_idx % num_groups_x;
2071 let gy = group_idx / num_groups_x;
2072
2073 let x_start = gx * GROUP_DIM;
2074 let y_start = gy * GROUP_DIM;
2075 let x_end = (x_start + GROUP_DIM).min(self.width);
2076 let y_end = (y_start + GROUP_DIM).min(self.height);
2077
2078 (x_start, y_start, x_end, y_end)
2079 }
2080}
2081
2082#[cfg(test)]
2083mod tests {
2084 use super::*;
2085
2086 #[test]
2087 fn test_frame_encoder_creation() {
2088 let encoder = FrameEncoder::new(256, 256, FrameEncoderOptions::default());
2089 assert_eq!(encoder.num_groups(), 1);
2090 }
2091
2092 #[test]
2093 fn test_frame_encoder_multi_group() {
2094 let encoder = FrameEncoder::new(512, 512, FrameEncoderOptions::default());
2095 assert_eq!(encoder.num_groups(), 4); assert_eq!(encoder.num_groups_x(), 2);
2097 assert_eq!(encoder.num_groups_y(), 2);
2098 assert_eq!(encoder.num_lf_groups(), 1); }
2100
2101 #[test]
2102 fn test_group_bounds() {
2103 let encoder = FrameEncoder::new(512, 512, FrameEncoderOptions::default());
2104
2105 let (x0, y0, x1, y1) = encoder.group_bounds(0);
2107 assert_eq!((x0, y0, x1, y1), (0, 0, 256, 256));
2108
2109 let (x0, y0, x1, y1) = encoder.group_bounds(1);
2111 assert_eq!((x0, y0, x1, y1), (256, 0, 512, 256));
2112
2113 let (x0, y0, x1, y1) = encoder.group_bounds(2);
2115 assert_eq!((x0, y0, x1, y1), (0, 256, 256, 512));
2116
2117 let (x0, y0, x1, y1) = encoder.group_bounds(3);
2119 assert_eq!((x0, y0, x1, y1), (256, 256, 512, 512));
2120 }
2121
2122 #[test]
2123 fn test_group_bounds_partial() {
2124 let encoder = FrameEncoder::new(300, 200, FrameEncoderOptions::default());
2126 assert_eq!(encoder.num_groups(), 2); let (x0, y0, x1, y1) = encoder.group_bounds(0);
2129 assert_eq!((x0, y0, x1, y1), (0, 0, 256, 200));
2130
2131 let (x0, y0, x1, y1) = encoder.group_bounds(1);
2132 assert_eq!((x0, y0, x1, y1), (256, 0, 300, 200)); }
2134
2135 #[test]
2136 fn test_num_toc_entries() {
2137 let encoder = FrameEncoder::new(256, 256, FrameEncoderOptions::default());
2139 assert_eq!(encoder.num_toc_entries(1), 1);
2140
2141 let encoder = FrameEncoder::new(512, 512, FrameEncoderOptions::default());
2143 assert_eq!(encoder.num_toc_entries(1), 7);
2144
2145 assert_eq!(encoder.num_toc_entries(2), 11);
2147 }
2148
2149 #[test]
2150 fn test_encode_multi_group_image() {
2151 let mut data = Vec::with_capacity(300 * 300 * 3);
2153 for y in 0..300 {
2154 for x in 0..300 {
2155 data.push(((x + y) % 256) as u8); data.push(((x * 2) % 256) as u8); data.push(((y * 2) % 256) as u8); }
2160 }
2161
2162 let image = ModularImage::from_rgb8(&data, 300, 300).unwrap();
2163
2164 let encoder = FrameEncoder::new(300, 300, FrameEncoderOptions::default());
2165 assert_eq!(encoder.num_groups(), 4); let mut writer = BitWriter::new();
2168 let color_encoding = ColorEncoding::srgb();
2169
2170 encoder
2171 .encode_modular(&image, &color_encoding, &mut writer)
2172 .unwrap();
2173
2174 let bytes = writer.finish_with_padding();
2175 crate::trace::debug_eprintln!("Multi-group modular: {} bytes", bytes.len());
2176 assert!(!bytes.is_empty());
2177 assert!(bytes.len() > 100); assert!(bytes.len() < 300 * 300 * 3); }
2181
2182 #[test]
2183 fn test_encode_small_image() {
2184 let mut data = Vec::with_capacity(4 * 4 * 3);
2187 for y in 0..4 {
2188 for x in 0..4 {
2189 let v = if (x + y) % 2 == 0 { 0u8 } else { 128u8 };
2190 data.push(v); data.push(v); data.push(v); }
2194 }
2195
2196 let image = ModularImage::from_rgb8(&data, 4, 4).unwrap();
2197
2198 let encoder = FrameEncoder::new(4, 4, FrameEncoderOptions::default());
2199 let mut writer = BitWriter::new();
2200 let color_encoding = ColorEncoding::srgb();
2201
2202 encoder
2203 .encode_modular(&image, &color_encoding, &mut writer)
2204 .unwrap();
2205
2206 let bytes = writer.finish_with_padding();
2207 assert!(!bytes.is_empty());
2208 }
2209}