1#![cfg_attr(not(feature = "std"), no_std)]
2#![cfg_attr(feature = "simd", feature(portable_simd))]
3extern crate alloc;
4
5pub mod ans;
6pub mod huffman;
7pub mod lzma;
8pub mod mixer;
9pub mod poor_compress;
10pub mod predictor;
11pub mod transform;
12
13pub mod loom;
14
15#[cfg(feature = "std")]
16extern crate std;
17#[cfg(feature = "std")]
18use rayon::prelude::*;
19#[cfg(feature = "std")]
20use std::sync::Once;
21
22#[cfg(feature = "std")]
23static THREAD_POOL_INIT: Once = Once::new();
24
25#[cfg(all(feature = "std", feature = "async"))]
27pub(crate) fn get_background_runtime() -> Option<std::sync::Arc<tokio::runtime::Runtime>> {
28 use std::sync::OnceLock;
29 static RUNTIME: OnceLock<Option<std::sync::Arc<tokio::runtime::Runtime>>> = OnceLock::new();
30 RUNTIME
31 .get_or_init(|| {
32 tokio::runtime::Builder::new_multi_thread()
33 .worker_threads(4) .enable_all()
35 .build()
36 .ok()
37 .map(std::sync::Arc::new)
38 })
39 .clone()
40}
41
42#[cfg(feature = "std")]
44pub fn init_thread_pool() {
45 THREAD_POOL_INIT.call_once(|| {
46 let cpus = num_cpus::get();
47 let threads = (cpus * 8 / 10).max(1);
48 let _ = rayon::ThreadPoolBuilder::new()
49 .num_threads(threads)
50 .build_global();
51 });
52}
53
54use crate::ans::{RansDecoder, RansEncoder};
55use crate::loom::{LoomEnum, LoomPredictor, LoomWeaver};
56use crate::transform::{Transform, TransformChain};
57use alloc::vec::Vec;
58
59const CHUNK_SIZE: usize = 1024 * 1024; #[derive(Clone, Copy, Debug, PartialEq, Eq)]
62pub enum LoomMode {
63 Off,
64 Standard,
65 Paged,
66}
67
68#[derive(Clone, Copy, Debug, PartialEq, Eq)]
69pub enum LoomAggression {
70 Low,
71 Med,
72 High,
73 Ultra,
74}
75
76impl LoomAggression {
77 pub fn throttle_rate(&self) -> usize {
78 match self {
79 LoomAggression::Low => 8,
80 LoomAggression::Med => 2,
81 LoomAggression::High => 1,
82 LoomAggression::Ultra => 1,
83 }
84 }
85}
86
87#[derive(Clone, Copy, Debug, PartialEq, Eq)]
89pub enum PruneAggression {
90 Gradual,
92 Balanced,
94 Aggressive,
96}
97
98impl PruneAggression {
99 pub fn prune_divisor(&self) -> usize {
101 match self {
102 PruneAggression::Gradual => 50, PruneAggression::Balanced => 20, PruneAggression::Aggressive => 10, }
106 }
107}
108
109#[derive(Clone, Debug)]
111pub struct PlcConfig {
112 pub model_dim: usize,
114 pub window_size: usize,
116 pub loom_mode: LoomMode,
118 pub aggression: LoomAggression,
120 pub use_mixer: bool,
122 pub mixer_orders: Vec<usize>,
124 pub use_lzma: bool,
126 pub lzma_window_size: usize,
128 pub transforms: Vec<Transform>,
130 pub paged_loom_max_ram: usize,
132 pub page_size_hint: usize,
134 pub page_max_nodes: usize,
136 pub min_loom_size: usize,
138 pub min_compress_size: usize,
140 pub max_nodes: usize,
142 pub dictionary: Option<alloc::vec::Vec<u8>>,
144 pub verbose: bool,
146 pub prune_aggression: PruneAggression,
148 pub persistent_dict_path: Option<alloc::string::String>,
150}
151
152impl Default for PlcConfig {
153 fn default() -> Self {
154 Self {
155 model_dim: 32,
156 window_size: 16,
157 loom_mode: LoomMode::Standard,
158 aggression: LoomAggression::Med,
159 use_mixer: true,
160 mixer_orders: alloc::vec![1, 2, 4, 8],
161 use_lzma: true,
162 lzma_window_size: 64 * 1024,
163 transforms: alloc::vec![Transform::Bwt, Transform::Mtf],
164 paged_loom_max_ram: 1024 * 1024 * 1024, page_size_hint: 128 * 1024 * 1024, page_max_nodes: 4096, min_loom_size: 0, min_compress_size: 32, max_nodes: 1024, dictionary: None,
171 verbose: false,
172 prune_aggression: PruneAggression::Balanced,
173 persistent_dict_path: None,
174 }
175 }
176}
177
178impl PlcConfig {
179 pub fn ultra() -> Self {
182 Self {
183 model_dim: 96,
184 window_size: 64,
185 loom_mode: LoomMode::Paged,
186 aggression: LoomAggression::Ultra,
187 use_mixer: true,
188 mixer_orders: alloc::vec![1, 2, 4, 8, 12, 16, 24, 32, 40, 48, 64],
189 use_lzma: true,
190 lzma_window_size: 1024 * 1024,
191 transforms: alloc::vec![Transform::Auto],
192 paged_loom_max_ram: 512 * 1024 * 1024, page_size_hint: 32 * 1024 * 1024,
194 page_max_nodes: 16384, min_loom_size: 0,
196 min_compress_size: 32,
197 max_nodes: 500_000,
198 dictionary: None,
199 verbose: false,
200 prune_aggression: PruneAggression::Balanced,
201 persistent_dict_path: None,
202 }
203 }
204
205 pub fn safe_ultra() -> Self {
206 Self {
207 model_dim: 128,
208 window_size: 64,
209 loom_mode: LoomMode::Paged,
210 aggression: LoomAggression::Ultra,
211 use_mixer: true,
212 mixer_orders: alloc::vec![1, 2, 4, 8, 12, 16, 24, 32, 40, 48, 56, 64],
214 use_lzma: true,
215 lzma_window_size: 1024 * 1024, transforms: alloc::vec![Transform::Auto],
217 paged_loom_max_ram: 1024 * 1024 * 1024, page_size_hint: 32 * 1024 * 1024, page_max_nodes: 32768, min_loom_size: 0,
221 min_compress_size: 32,
222 max_nodes: 1_000_000, dictionary: None,
224 verbose: false,
225 prune_aggression: PruneAggression::Gradual,
226 persistent_dict_path: None,
227 }
228 }
229}
230
231pub trait Compressor {
233 type Error;
234 fn compress(&mut self, data: &[u8]) -> Result<Vec<u8>, Self::Error>;
235}
236
237pub trait Decompressor {
239 type Error;
240 fn decompress(&mut self, data: &[u8], original_len: usize) -> Result<Vec<u8>, Self::Error>;
241}
242
243pub struct LoomCompressor {
245 loom: Option<LoomEnum>,
246 config: PlcConfig,
247}
248
249impl LoomCompressor {
250 pub fn new(config: PlcConfig) -> Self {
251 #[cfg(feature = "std")]
256 {
257 }
275
276 let estimated_ram_bytes = config.max_nodes * 1024; if config.aggression == LoomAggression::Ultra
278 && estimated_ram_bytes > config.paged_loom_max_ram
279 {
280 #[cfg(feature = "std")]
281 if config.verbose {
282 std::println!(
283 "[Atlas-Archive] Ultra mode RAM budget per thread exceeded, capping nodes."
284 );
285 }
286 }
288
289 let loom = if config.loom_mode == LoomMode::Off {
290 None
291 } else {
292 match LoomEnum::new(config.clone()) {
294 Ok(mut l) => {
295 if let Some(dict) = &config.dictionary {
297 let mut history = Vec::new();
298 for &sym in dict {
299 l.weave(&history, sym);
300 history.push(sym);
301 if history.len() > config.window_size {
302 history.remove(0);
303 }
304 }
305 }
306 Some(l)
307 }
308 Err(e) => {
309 #[cfg(feature = "std")]
310 if config.verbose {
311 std::println!(
312 "[Atlas-Archive] Loom initialization failed, falling back to Off: {}",
313 e
314 );
315 }
316 None
317 }
318 }
319 };
320 Self { loom, config }
321 }
322}
323
324#[derive(Debug)]
325pub enum LoomError {
326 PackingError,
327 AnsError,
328 LoomInternalError,
329 InvalidArchive,
330}
331
332impl core::fmt::Display for LoomError {
333 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
334 match self {
335 LoomError::PackingError => write!(f, "Packing error: internal buffer mismatch"),
336 LoomError::AnsError => write!(f, "ANS entropy coding error"),
337 LoomError::LoomInternalError => write!(f, "Loom adaptive model error"),
338 LoomError::InvalidArchive => write!(f, "Invalid NYX archive magic or format"),
339 }
340 }
341}
342
343#[cfg(feature = "std")]
344impl std::error::Error for LoomError {}
345
346#[derive(Clone, Debug, Default)]
348pub struct ArchiveEntry {
349 pub name: Vec<u8>,
350 pub data: Vec<u8>,
351}
352
353#[derive(Clone, Debug, Default)]
355pub struct AtlasArchive {
356 pub entries: Vec<ArchiveEntry>,
357}
358
359impl AtlasArchive {
360 pub const MAGIC: &'static [u8; 4] = b"NYX\x01";
361
362 pub fn pack(&self) -> Vec<u8> {
364 let mut buf = Vec::new();
365 buf.extend_from_slice(Self::MAGIC);
366 buf.extend_from_slice(&(self.entries.len() as u32).to_le_bytes());
367
368 for entry in &self.entries {
369 buf.extend_from_slice(&(entry.name.len() as u32).to_le_bytes());
370 buf.extend_from_slice(&entry.name);
371 buf.extend_from_slice(&(entry.data.len() as u64).to_le_bytes());
372 buf.extend_from_slice(&entry.data);
373 }
374 buf
375 }
376
377 pub fn unpack(data: &[u8]) -> Result<Self, LoomError> {
379 if data.len() < 8 || &data[0..4] != Self::MAGIC {
380 return Err(LoomError::InvalidArchive);
381 }
382
383 let mut entries = Vec::new();
384 let entry_count = u32::from_le_bytes(
385 data[4..8]
386 .try_into()
387 .map_err(|_| LoomError::InvalidArchive)?,
388 ) as usize;
389 let mut offset = 8;
390
391 for _ in 0..entry_count {
392 if offset + 4 > data.len() {
393 return Err(LoomError::InvalidArchive);
394 }
395 let name_len = u32::from_le_bytes(
396 data[offset..offset + 4]
397 .try_into()
398 .map_err(|_| LoomError::InvalidArchive)?,
399 ) as usize;
400 offset += 4;
401
402 if offset + name_len > data.len() {
403 return Err(LoomError::InvalidArchive);
404 }
405 let name = data[offset..offset + name_len].to_vec();
406 offset += name_len;
407
408 if offset + 8 > data.len() {
409 return Err(LoomError::InvalidArchive);
410 }
411 let data_len = u64::from_le_bytes(
412 data[offset..offset + 8]
413 .try_into()
414 .map_err(|_| LoomError::InvalidArchive)?,
415 ) as usize;
416 offset += 8;
417
418 if offset + data_len > data.len() {
419 return Err(LoomError::InvalidArchive);
420 }
421 let entry_data = data[offset..offset + data_len].to_vec();
422 offset += data_len;
423
424 entries.push(ArchiveEntry {
425 name,
426 data: entry_data,
427 });
428 }
429
430 Ok(Self { entries })
431 }
432}
433
434impl Compressor for LoomCompressor {
435 type Error = LoomError;
436
437 fn compress(&mut self, data: &[u8]) -> Result<Vec<u8>, Self::Error> {
438 if data.is_empty() {
439 return Ok(Vec::new());
440 }
441
442 self.compress_parallel(data)
447 }
448}
449
450impl LoomCompressor {
451 fn compress_parallel(&mut self, data: &[u8]) -> Result<Vec<u8>, LoomError> {
452 let chunk_size = CHUNK_SIZE;
453
454 #[cfg(feature = "std")]
455 {
456 let chunks: Vec<_> = data.chunks(chunk_size).collect();
458
459 let min_chunks_per_thread = 2;
461
462 let results: Vec<Result<Vec<u8>, LoomError>> = if chunks.len() == 1 {
463 chunks
465 .into_iter()
466 .map(|chunk| {
467 let mut inst = LoomCompressor::new(self.config.clone());
468 inst.compress_chunk(chunk)
469 })
470 .collect()
471 } else {
472 chunks
473 .into_par_iter()
474 .with_min_len(min_chunks_per_thread)
475 .map(|chunk| {
476 let mut inst = LoomCompressor::new(self.config.clone());
477 inst.compress_chunk(chunk)
478 })
479 .collect()
480 };
481
482 let mut packed = Vec::new();
483 let mut total_compressed = 0;
484 for res in results {
485 let chunk_data = res?;
486 total_compressed += chunk_data.len();
487 packed.extend_from_slice(&chunk_data);
488 }
489
490 if self.config.verbose {
491 let ratio = total_compressed as f64 / data.len() as f64;
492 std::println!(
493 "[Atlas] Parallel compression complete. Ratio: {:.4}, Peak RAM ~{}MB",
494 ratio,
495 (total_compressed * 1) / (1024 * 1024) );
497 }
498 Ok(packed)
499 }
500
501 #[cfg(not(feature = "std"))]
502 {
503 let mut packed = Vec::new();
505 for chunk in data.chunks(chunk_size) {
506 let chunk_data = self.compress_chunk(chunk)?;
507 packed.extend_from_slice(&chunk_data);
508 }
509 Ok(packed)
510 }
511 }
512
513 fn compress_chunk(&mut self, data: &[u8]) -> Result<Vec<u8>, LoomError> {
514 #[cfg(feature = "std")]
515 let chunk_start = std::time::Instant::now();
516
517 if data.len() < self.config.min_compress_size {
519 return self.pack_raw_chunk(data);
520 }
521
522 let file_type = crate::poor_compress::DetectedType::detect(data);
524
525 let mut transform_list = self.config.transforms.clone();
526
527 if transform_list.contains(&Transform::Auto) {
529 match file_type {
530 crate::poor_compress::DetectedType::Jpeg => {
531 transform_list.insert(0, Transform::Yuv);
533 }
534 crate::poor_compress::DetectedType::Png
535 | crate::poor_compress::DetectedType::Gif => {
536 transform_list = vec![Transform::Delta, Transform::Rle];
538 }
539 crate::poor_compress::DetectedType::Mp4
540 | crate::poor_compress::DetectedType::Mp3 => {
541 transform_list = vec![
543 Transform::ChannelSeparation(2),
544 Transform::Delta,
545 Transform::Rle,
546 ];
547 }
548 crate::poor_compress::DetectedType::Encrypted => {
549 transform_list = vec![];
551 }
552 crate::poor_compress::DetectedType::Zip => {
553 transform_list = vec![Transform::Rle];
555 }
556 _ => {}
557 }
558 }
559
560 let chain = TransformChain::new(transform_list);
561 let (transformed, bwt_indices) = chain.apply(data.to_vec());
562
563 if self.config.verbose {
564 let est_ram = transformed.len() + (bwt_indices.len() * 8);
565 std::println!(
566 "[Atlas] Detected Type: {:?}, Final Ratio: {:.4}, Est. Transform RAM: {:.2}MB",
567 file_type,
568 transformed.len() as f64 / data.len() as f64,
569 est_ram as f64 / (1024.0 * 1024.0)
570 );
571 }
572
573 if transformed.len() < self.config.min_loom_size || self.loom.is_none() {
575 #[cfg(feature = "std")]
576 if self.config.verbose {
577 std::println!(
578 "[Atlas] Chunk {}B compressed (no Loom) in {:?}",
579 data.len(),
580 chunk_start.elapsed()
581 );
582 }
583 return self.pack_ans_only_chunk(&transformed, bwt_indices, data.len(), file_type);
584 }
585
586 let loom = self.loom.as_mut().unwrap();
587
588 let mut models = Vec::with_capacity(transformed.len());
591 let mut history = Vec::with_capacity(transformed.len());
592
593 for &sym in transformed.iter() {
594 let model = loom.predict(&history);
596 models.push(model);
597
598 loom.weave(&history, sym);
600 history.push(sym);
601 }
602
603 let mut encoder = RansEncoder::new();
605 let mut compressed_u16 = Vec::new();
606 for (sym, model) in transformed.iter().zip(models.into_iter()).rev() {
607 encoder.encode(&model, *sym, &mut compressed_u16);
608 }
609 encoder.finish(&mut compressed_u16);
610
611 let mut compressed_ans = Vec::with_capacity(compressed_u16.len() * 2);
613 for &val in &compressed_u16 {
614 compressed_ans.extend_from_slice(&val.to_le_bytes());
615 }
616
617 let packed = self.pack_loom_chunk(
619 &compressed_ans,
620 bwt_indices,
621 data.len(),
622 transformed.len(),
623 file_type,
624 );
625
626 if packed.len() >= data.len() {
628 #[cfg(feature = "std")]
629 if self.config.verbose {
630 std::println!(
631 "[Loom] Chunk expanded ({} -> {}), falling back to Raw",
632 data.len(),
633 packed.len()
634 );
635 }
636 return self.pack_raw_chunk(data);
637 }
638
639 #[cfg(feature = "std")]
640 if self.config.verbose {
641 std::println!(
642 "[Loom] Chunk compressed: {} -> {} bytes (Ratio: {:.4}) in {:?}",
643 data.len(),
644 packed.len(),
645 packed.len() as f64 / data.len() as f64,
646 chunk_start.elapsed()
647 );
648 }
649
650 Ok(packed)
651 }
652
653 fn pack_raw_chunk(&self, data: &[u8]) -> Result<Vec<u8>, LoomError> {
654 let mut packed = Vec::with_capacity(data.len() + 8);
655 let file_type = crate::poor_compress::DetectedType::detect(data);
657 let type_val = (file_type as u32) & 0x7F;
658 let len = data.len() as u32;
659 let header_flag = (len & 0x00FF_FFFF) | (type_val << 24) | 0x8000_0000;
660
661 packed.extend_from_slice(&header_flag.to_le_bytes());
662 packed.extend_from_slice(&len.to_le_bytes()); packed.extend_from_slice(data);
664 Ok(packed)
665 }
666
667 fn pack_ans_only_chunk(
668 &self,
669 transformed: &[u8],
670 bwt_indices: Vec<usize>,
671 original_len: usize,
672 file_type: crate::poor_compress::DetectedType,
673 ) -> Result<Vec<u8>, LoomError> {
674 if bwt_indices.is_empty() && self.config.transforms.is_empty() {
676 return self.pack_raw_chunk(transformed);
677 }
678
679 Ok(self.pack_loom_chunk(
681 transformed,
682 bwt_indices,
683 original_len,
684 transformed.len(),
685 file_type,
686 ))
687 }
688
689 fn pack_loom_chunk(
690 &self,
691 data: &[u8],
692 bwt_indices: Vec<usize>,
693 original_len: usize,
694 transformed_len: usize,
695 file_type: crate::poor_compress::DetectedType,
696 ) -> Vec<u8> {
697 let mut packed = Vec::new();
698 let type_val = (file_type as u32) & 0x7F;
700 let meta = (original_len as u32 & 0x00FF_FFFF) | (type_val << 24);
701
702 packed.extend_from_slice(&meta.to_le_bytes());
703 packed.extend_from_slice(&(data.len() as u32).to_le_bytes());
704 packed.extend_from_slice(&(transformed_len as u32).to_le_bytes());
705 packed.extend_from_slice(&(bwt_indices.len() as u32).to_le_bytes());
706 for idx in bwt_indices {
707 packed.extend_from_slice(&(idx as u32).to_le_bytes());
708 }
709 packed.extend_from_slice(data);
710 packed
711 }
712}
713
714pub struct LoomDecompressor {
716 loom: Option<LoomEnum>,
717 config: PlcConfig,
718}
719
720impl LoomDecompressor {
721 pub fn new(config: PlcConfig) -> Self {
722 let loom = if config.loom_mode == LoomMode::Off {
723 None
724 } else {
725 LoomEnum::new(config.clone()).ok()
726 };
727 Self { loom, config }
728 }
729}
730
731impl Decompressor for LoomDecompressor {
732 type Error = LoomError;
733
734 fn decompress(&mut self, data: &[u8], original_len: usize) -> Result<Vec<u8>, Self::Error> {
735 if original_len == 0 {
736 return Ok(Vec::new());
737 }
738
739 let mut output = Vec::with_capacity(original_len);
740 let mut cursor = 0;
741
742 #[cfg(feature = "std")]
743 if self.config.verbose {
744 std::println!(
745 "[Decompress] Starting: data_len={}, original_len={}",
746 data.len(),
747 original_len
748 );
749 }
750
751 while cursor < data.len() {
752 if cursor + 4 > data.len() {
754 #[cfg(feature = "std")]
755 if self.config.verbose {
756 std::println!(
757 "[Decompress] Breaking: cursor={} + 4 > data_len={}",
758 cursor,
759 data.len()
760 );
761 }
762 break;
763 }
764 let meta = u32::from_le_bytes(data[cursor..cursor + 4].try_into().unwrap());
765 cursor += 4;
766
767 #[cfg(feature = "std")]
768 if self.config.verbose {
769 std::println!(
770 "[Decompress] Chunk meta={:#010x}, cursor={}, is_raw={}",
771 meta,
772 cursor,
773 (meta & 0x8000_0000) != 0
774 );
775 }
776
777 if (meta & 0x8000_0000) != 0 {
779 let _raw_len = (meta & 0x00FF_FFFF) as usize;
781 let _file_type_val = (meta >> 24) & 0x7F;
782 if cursor + 4 > data.len() {
783 return Err(LoomError::PackingError);
784 }
785 let compressed_len_field =
786 u32::from_le_bytes(data[cursor..cursor + 4].try_into().unwrap()) as usize;
787 cursor += 4;
788
789 if cursor + compressed_len_field > data.len() {
790 return Err(LoomError::PackingError);
791 }
792 output.extend_from_slice(&data[cursor..cursor + compressed_len_field]);
793 cursor += compressed_len_field;
794 continue;
795 }
796
797 let chunk_orig_len = (meta & 0x00FF_FFFF) as usize;
799 let file_type_val = (meta >> 24) & 0x7F;
800 let file_type: crate::poor_compress::DetectedType =
801 unsafe { core::mem::transmute(file_type_val as u8) };
802 if cursor + 12 > data.len() {
803 return Err(LoomError::PackingError);
804 }
805 let compressed_len =
806 u32::from_le_bytes(data[cursor..cursor + 4].try_into().unwrap()) as usize;
807 let transformed_len =
808 u32::from_le_bytes(data[cursor + 4..cursor + 8].try_into().unwrap()) as usize;
809 let bwt_count =
810 u32::from_le_bytes(data[cursor + 8..cursor + 12].try_into().unwrap()) as usize;
811 cursor += 12;
812
813 #[cfg(feature = "std")]
814 if self.config.verbose {
815 std::println!(
816 "[Decompress] Chunk: orig={}, compressed={}, transformed={}, bwt_count={}",
817 chunk_orig_len,
818 compressed_len,
819 transformed_len,
820 bwt_count
821 );
822 }
823
824 let mut bwt_indices = Vec::with_capacity(bwt_count);
825 for _ in 0..bwt_count {
826 if cursor + 4 > data.len() {
827 return Err(LoomError::PackingError);
828 }
829 bwt_indices.push(
830 u32::from_le_bytes(data[cursor..cursor + 4].try_into().unwrap()) as usize,
831 );
832 cursor += 4;
833 }
834
835 if cursor + compressed_len > data.len() {
836 return Err(LoomError::PackingError);
837 }
838 let ans_data = &data[cursor..cursor + compressed_len];
839 let chunk_out = self.decompress_chunk(
840 ans_data,
841 chunk_orig_len,
842 transformed_len,
843 bwt_indices,
844 file_type,
845 )?;
846
847 #[cfg(feature = "std")]
848 if self.config.verbose {
849 std::println!("[Decompress] Chunk output: {} bytes", chunk_out.len());
850 }
851
852 output.extend_from_slice(&chunk_out);
853 cursor += compressed_len;
854 }
855
856 #[cfg(feature = "std")]
857 if self.config.verbose {
858 std::println!("[Decompress] Finished: output_len={}", output.len());
859 }
860
861 Ok(output)
862 }
863}
864
865impl LoomDecompressor {
866 fn decompress_chunk(
867 &mut self,
868 data: &[u8],
869 _original_len: usize,
870 transformed_len: usize,
871 bwt_indices: Vec<usize>,
872 file_type: crate::poor_compress::DetectedType,
873 ) -> Result<Vec<u8>, LoomError> {
874 if self.loom.is_none() || data.len() == transformed_len {
875 let mut transform_list = self.config.transforms.clone();
877
878 if transform_list.contains(&Transform::Auto) {
880 match file_type {
881 crate::poor_compress::DetectedType::Jpeg => {
882 transform_list.insert(0, Transform::Yuv);
883 }
884 crate::poor_compress::DetectedType::Png
885 | crate::poor_compress::DetectedType::Gif => {
886 transform_list = vec![Transform::Delta, Transform::Rle];
887 }
888 crate::poor_compress::DetectedType::Mp4
889 | crate::poor_compress::DetectedType::Mp3 => {
890 transform_list = vec![
891 Transform::ChannelSeparation(2),
892 Transform::Delta,
893 Transform::Rle,
894 ];
895 }
896 crate::poor_compress::DetectedType::Encrypted => {
897 transform_list = vec![];
898 }
899 crate::poor_compress::DetectedType::Zip => {
900 transform_list = vec![Transform::Rle];
901 }
902 _ => {}
903 }
904 }
905
906 let chain = TransformChain::new(transform_list);
907 return Ok(chain.inverse(data.to_vec(), bwt_indices));
908 }
909
910 let mut transform_list = self.config.transforms.clone();
911 if transform_list.contains(&Transform::Auto) {
912 match file_type {
913 crate::poor_compress::DetectedType::Jpeg => {
914 transform_list.insert(0, Transform::Yuv);
915 }
916 crate::poor_compress::DetectedType::Png
917 | crate::poor_compress::DetectedType::Gif => {
918 transform_list = vec![Transform::Delta, Transform::Rle];
919 }
920 crate::poor_compress::DetectedType::Mp4
921 | crate::poor_compress::DetectedType::Mp3 => {
922 transform_list = vec![
923 Transform::ChannelSeparation(2),
924 Transform::Delta,
925 Transform::Rle,
926 ];
927 }
928 crate::poor_compress::DetectedType::Encrypted => {
929 transform_list = vec![];
930 }
931 crate::poor_compress::DetectedType::Zip => {
932 transform_list = vec![Transform::Rle];
933 }
934 _ => {}
935 }
936 }
937 let chain = TransformChain::new(transform_list);
938
939 let mut input_u16 = Vec::with_capacity(data.len() / 2);
941 for chunk in data.chunks_exact(2) {
942 input_u16.push(u16::from_le_bytes(chunk.try_into().unwrap()));
943 }
944
945 let mut decoder = RansDecoder::new(&mut input_u16);
946 let mut transformed = Vec::with_capacity(transformed_len);
947 let loom = self.loom.as_mut().unwrap();
948
949 for _ in 0..transformed_len {
950 let model = loom.predict(&transformed);
951 let sym = decoder.decode(&model, &mut input_u16);
952
953 loom.weave(&transformed, sym);
955 transformed.push(sym);
956 }
957
958 Ok(chain.inverse(transformed, bwt_indices))
959 }
960}