1use crate::error::{WasmError, WasmResult};
188use serde::{Deserialize, Serialize};
189use std::collections::HashMap;
190#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
192pub enum CompressionAlgorithm {
193 None,
195 Rle,
197 Delta,
199 Huffman,
201 Lz77,
203}
204#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
206pub struct CompressionStats {
207 pub original_size: usize,
209 pub compressed_size: usize,
211 pub ratio: f64,
213 pub compression_time_ms: f64,
215}
216impl CompressionStats {
217 pub const fn new(
219 original_size: usize,
220 compressed_size: usize,
221 compression_time_ms: f64,
222 ) -> Self {
223 let ratio = if original_size > 0 {
224 compressed_size as f64 / original_size as f64
225 } else {
226 0.0
227 };
228 Self {
229 original_size,
230 compressed_size,
231 ratio,
232 compression_time_ms,
233 }
234 }
235 pub const fn space_saved(&self) -> usize {
237 self.original_size.saturating_sub(self.compressed_size)
238 }
239 pub fn space_saved_percent(&self) -> f64 {
241 if self.original_size == 0 {
242 0.0
243 } else {
244 ((self.original_size - self.compressed_size) as f64 / self.original_size as f64) * 100.0
245 }
246 }
247}
248pub struct RleCompressor;
250impl RleCompressor {
251 pub fn compress(data: &[u8]) -> Vec<u8> {
253 if data.is_empty() {
254 return Vec::new();
255 }
256 let mut compressed = Vec::new();
257 let mut i = 0;
258 while i < data.len() {
259 let current = data[i];
260 let mut count = 1u8;
261 while i + (count as usize) < data.len()
262 && data[i + (count as usize)] == current
263 && count < 255
264 {
265 count += 1;
266 }
267 compressed.push(count);
268 compressed.push(current);
269 i += count as usize;
270 }
271 compressed
272 }
273 pub fn decompress(data: &[u8]) -> WasmResult<Vec<u8>> {
275 if !data.len().is_multiple_of(2) {
276 return Err(WasmError::InvalidOperation {
277 operation: "RLE decompress".to_string(),
278 reason: "Data length must be even".to_string(),
279 });
280 }
281 let mut decompressed = Vec::new();
282 for chunk in data.chunks_exact(2) {
283 let count = chunk[0];
284 let value = chunk[1];
285 decompressed.resize(decompressed.len() + count as usize, value);
286 }
287 Ok(decompressed)
288 }
289}
290pub struct DeltaCompressor;
292impl DeltaCompressor {
293 pub fn compress(data: &[u8]) -> Vec<u8> {
295 if data.is_empty() {
296 return Vec::new();
297 }
298 let mut compressed = Vec::with_capacity(data.len());
299 compressed.push(data[0]);
300 for i in 1..data.len() {
301 let delta = data[i].wrapping_sub(data[i - 1]);
302 compressed.push(delta);
303 }
304 compressed
305 }
306 pub fn decompress(data: &[u8]) -> WasmResult<Vec<u8>> {
308 if data.is_empty() {
309 return Ok(Vec::new());
310 }
311 let mut decompressed = Vec::with_capacity(data.len());
312 decompressed.push(data[0]);
313 for i in 1..data.len() {
314 let prev = *decompressed.last().expect("decompressed is not empty");
315 let value = prev.wrapping_add(data[i]);
316 decompressed.push(value);
317 }
318 Ok(decompressed)
319 }
320 pub fn compress_2d(data: &[u8], width: usize) -> Vec<u8> {
322 if data.is_empty() || width == 0 {
323 return Vec::new();
324 }
325 let height = data.len() / width;
326 let mut compressed = Vec::with_capacity(data.len());
327 if !data.is_empty() {
328 compressed.push(data[0]);
329 for x in 1..width.min(data.len()) {
330 let delta = data[x].wrapping_sub(data[x - 1]);
331 compressed.push(delta);
332 }
333 }
334 for y in 1..height {
335 for x in 0..width {
336 let idx = y * width + x;
337 if idx < data.len() {
338 let delta = data[idx].wrapping_sub(data[idx - width]);
339 compressed.push(delta);
340 }
341 }
342 }
343 compressed
344 }
345 pub fn decompress_2d(data: &[u8], width: usize) -> WasmResult<Vec<u8>> {
347 if data.is_empty() || width == 0 {
348 return Ok(Vec::new());
349 }
350 let height = data.len() / width;
351 let mut decompressed = Vec::with_capacity(data.len());
352 if !data.is_empty() {
353 decompressed.push(data[0]);
354 for x in 1..width.min(data.len()) {
355 let prev = decompressed[x - 1];
356 let value = prev.wrapping_add(data[x]);
357 decompressed.push(value);
358 }
359 }
360 for y in 1..height {
361 for x in 0..width {
362 let idx = y * width + x;
363 if idx < data.len() {
364 let above = decompressed[idx - width];
365 let value = above.wrapping_add(data[idx]);
366 decompressed.push(value);
367 }
368 }
369 }
370 Ok(decompressed)
371 }
372}
373#[derive(Debug, Clone)]
375enum HuffmanNode {
376 Leaf {
377 symbol: u8,
378 frequency: u32,
379 },
380 Internal {
381 frequency: u32,
382 left: Box<HuffmanNode>,
383 right: Box<HuffmanNode>,
384 },
385}
386impl HuffmanNode {
387 fn frequency(&self) -> u32 {
388 match self {
389 Self::Leaf { frequency, .. } => *frequency,
390 Self::Internal { frequency, .. } => *frequency,
391 }
392 }
393 fn min_symbol(&self) -> u8 {
400 match self {
401 Self::Leaf { symbol, .. } => *symbol,
402 Self::Internal { left, right, .. } => left.min_symbol().min(right.min_symbol()),
403 }
404 }
405}
406pub struct HuffmanCompressor;
408impl HuffmanCompressor {
409 fn build_frequency_table(data: &[u8]) -> HashMap<u8, u32> {
411 let mut frequencies = HashMap::new();
412 for &byte in data {
413 *frequencies.entry(byte).or_insert(0) += 1;
414 }
415 frequencies
416 }
417 fn build_tree(frequencies: &HashMap<u8, u32>) -> Option<HuffmanNode> {
419 if frequencies.is_empty() {
420 return None;
421 }
422 let mut nodes: Vec<HuffmanNode> = frequencies
423 .iter()
424 .map(|(&symbol, &frequency)| HuffmanNode::Leaf { symbol, frequency })
425 .collect();
426 while nodes.len() > 1 {
427 nodes.sort_by(|a, b| {
432 b.frequency()
433 .cmp(&a.frequency())
434 .then_with(|| b.min_symbol().cmp(&a.min_symbol()))
435 });
436 let right = nodes.pop()?;
437 let left = nodes.pop()?;
438 let internal = HuffmanNode::Internal {
439 frequency: left.frequency() + right.frequency(),
440 left: Box::new(left),
441 right: Box::new(right),
442 };
443 nodes.push(internal);
444 }
445 nodes.pop()
446 }
447 fn generate_codes(node: &HuffmanNode, prefix: Vec<bool>, codes: &mut HashMap<u8, Vec<bool>>) {
449 match node {
450 HuffmanNode::Leaf { symbol, .. } => {
451 codes.insert(*symbol, prefix);
452 }
453 HuffmanNode::Internal { left, right, .. } => {
454 let mut left_prefix = prefix.clone();
455 left_prefix.push(false);
456 Self::generate_codes(left, left_prefix, codes);
457 let mut right_prefix = prefix;
458 right_prefix.push(true);
459 Self::generate_codes(right, right_prefix, codes);
460 }
461 }
462 }
463 pub fn compress(data: &[u8]) -> Vec<u8> {
471 if data.is_empty() {
472 return Vec::new();
473 }
474 let frequencies = Self::build_frequency_table(data);
475 let tree = match Self::build_tree(&frequencies) {
476 Some(t) => t,
477 None => return Vec::new(),
478 };
479 let mut codes = HashMap::new();
480 Self::generate_codes(&tree, Vec::new(), &mut codes);
481
482 let num_symbols = frequencies.len() as u16;
484 let mut compressed = Vec::new();
485 compressed.extend_from_slice(&num_symbols.to_le_bytes());
486
487 let mut sorted_symbols: Vec<(u8, u32)> = frequencies.into_iter().collect();
489 sorted_symbols.sort_by_key(|(sym, _)| *sym);
490 for (symbol, freq) in &sorted_symbols {
491 compressed.push(*symbol);
492 compressed.extend_from_slice(&freq.to_le_bytes());
493 }
494
495 compressed.extend_from_slice(&(data.len() as u64).to_le_bytes());
497
498 let mut bit_buffer = 0u8;
500 let mut bit_count = 0u8;
501 for &byte in data {
502 if let Some(code) = codes.get(&byte) {
503 for &bit in code {
504 if bit {
505 bit_buffer |= 1 << (7 - bit_count);
506 }
507 bit_count += 1;
508 if bit_count == 8 {
509 compressed.push(bit_buffer);
510 bit_buffer = 0;
511 bit_count = 0;
512 }
513 }
514 }
515 }
516 if bit_count > 0 {
517 compressed.push(bit_buffer);
518 }
519 compressed
520 }
521
522 pub fn decompress(compressed: &[u8]) -> WasmResult<Vec<u8>> {
527 if compressed.is_empty() {
528 return Ok(Vec::new());
529 }
530
531 if compressed.len() < 2 {
533 return Err(WasmError::InvalidOperation {
534 operation: "Huffman decompression".to_string(),
535 reason: "Data too short to contain symbol count header".to_string(),
536 });
537 }
538 let num_symbols = u16::from_le_bytes([compressed[0], compressed[1]]) as usize;
539 let mut pos = 2usize;
540
541 let symbol_table_size = num_symbols * 5;
543 if pos + symbol_table_size + 8 > compressed.len() {
544 return Err(WasmError::InvalidOperation {
545 operation: "Huffman decompression".to_string(),
546 reason: "Data too short to contain symbol table and length header".to_string(),
547 });
548 }
549
550 let mut frequencies: HashMap<u8, u32> = HashMap::new();
551 for _ in 0..num_symbols {
552 let symbol = compressed[pos];
553 pos += 1;
554 let freq = u32::from_le_bytes([
555 compressed[pos],
556 compressed[pos + 1],
557 compressed[pos + 2],
558 compressed[pos + 3],
559 ]);
560 pos += 4;
561 frequencies.insert(symbol, freq);
562 }
563
564 let original_len = u64::from_le_bytes([
566 compressed[pos],
567 compressed[pos + 1],
568 compressed[pos + 2],
569 compressed[pos + 3],
570 compressed[pos + 4],
571 compressed[pos + 5],
572 compressed[pos + 6],
573 compressed[pos + 7],
574 ]) as usize;
575 pos += 8;
576
577 if original_len == 0 {
578 return Ok(Vec::new());
579 }
580
581 let tree = Self::build_tree(&frequencies).ok_or_else(|| WasmError::InvalidOperation {
583 operation: "Huffman decompression".to_string(),
584 reason: "Failed to reconstruct Huffman tree from frequency table".to_string(),
585 })?;
586
587 let bitstream = &compressed[pos..];
589 let mut result = Vec::with_capacity(original_len);
590 let mut node = &tree;
591
592 'outer: for &byte in bitstream {
593 for bit_idx in (0..8).rev() {
594 let bit = (byte >> bit_idx) & 1;
595 node = match node {
596 HuffmanNode::Internal { left, right, .. } => {
597 if bit == 0 {
598 left
599 } else {
600 right
601 }
602 }
603 HuffmanNode::Leaf { .. } => {
604 return Err(WasmError::InvalidOperation {
606 operation: "Huffman decompression".to_string(),
607 reason: "Unexpected leaf mid-traversal in bit stream".to_string(),
608 });
609 }
610 };
611
612 if let HuffmanNode::Leaf { symbol, .. } = node {
613 result.push(*symbol);
614 if result.len() == original_len {
615 break 'outer;
616 }
617 node = &tree;
619 }
620 }
621 }
622
623 if result.is_empty()
625 && original_len > 0
626 && let HuffmanNode::Leaf { symbol, .. } = &tree
627 {
628 result.resize(original_len, *symbol);
629 }
630
631 if result.len() != original_len {
632 return Err(WasmError::InvalidOperation {
633 operation: "Huffman decompression".to_string(),
634 reason: format!(
635 "Decoded {} bytes but expected {}",
636 result.len(),
637 original_len
638 ),
639 });
640 }
641
642 Ok(result)
643 }
644}
645pub struct Lz77Compressor;
647impl Lz77Compressor {
648 const WINDOW_SIZE: usize = 4096;
650 const MAX_MATCH_LENGTH: usize = 18;
652 const MIN_MATCH_LENGTH: usize = 3;
654 fn find_longest_match(data: &[u8], pos: usize) -> Option<(usize, usize)> {
656 let window_start = pos.saturating_sub(Self::WINDOW_SIZE);
657 let mut best_match = None;
658 let mut best_length = 0;
659 for start in window_start..pos {
660 let mut length = 0;
661 while length < Self::MAX_MATCH_LENGTH
662 && pos + length < data.len()
663 && data[start + length] == data[pos + length]
664 {
665 length += 1;
666 }
667 if length >= Self::MIN_MATCH_LENGTH && length > best_length {
668 best_length = length;
669 best_match = Some((pos - start, length));
670 }
671 }
672 best_match
673 }
674 pub fn compress(data: &[u8]) -> Vec<u8> {
676 if data.is_empty() {
677 return Vec::new();
678 }
679 let mut compressed = Vec::new();
680 let mut pos = 0;
681 while pos < data.len() {
682 if let Some((distance, length)) = Self::find_longest_match(data, pos) {
683 compressed.push(1);
684 compressed.push((distance >> 8) as u8);
685 compressed.push((distance & 0xFF) as u8);
686 compressed.push(length as u8);
687 pos += length;
688 } else {
689 compressed.push(0);
690 compressed.push(data[pos]);
691 pos += 1;
692 }
693 }
694 compressed
695 }
696 pub fn decompress(data: &[u8]) -> WasmResult<Vec<u8>> {
698 let mut decompressed = Vec::new();
699 let mut i = 0;
700 while i < data.len() {
701 let flag = data[i];
702 i += 1;
703 if flag == 1 {
704 if i + 3 > data.len() {
705 return Err(WasmError::InvalidOperation {
706 operation: "LZ77 decompress".to_string(),
707 reason: "Unexpected end of data".to_string(),
708 });
709 }
710 let distance = ((data[i] as usize) << 8) | (data[i + 1] as usize);
711 let length = data[i + 2] as usize;
712 i += 3;
713 let start = decompressed.len().saturating_sub(distance);
714 for j in 0..length {
715 if start + j < decompressed.len() {
716 let byte = decompressed[start + j];
717 decompressed.push(byte);
718 }
719 }
720 } else {
721 if i >= data.len() {
722 return Err(WasmError::InvalidOperation {
723 operation: "LZ77 decompress".to_string(),
724 reason: "Unexpected end of data".to_string(),
725 });
726 }
727 decompressed.push(data[i]);
728 i += 1;
729 }
730 }
731 Ok(decompressed)
732 }
733}
734pub struct TileCompressor {
736 algorithm: CompressionAlgorithm,
738}
739impl TileCompressor {
740 pub const fn new(algorithm: CompressionAlgorithm) -> Self {
742 Self { algorithm }
743 }
744 pub fn compress(&self, data: &[u8]) -> (Vec<u8>, CompressionStats) {
746 #[cfg(target_arch = "wasm32")]
747 let start = js_sys::Date::now();
748 #[cfg(not(target_arch = "wasm32"))]
749 let start = std::time::Instant::now();
750
751 let original_size = data.len();
752 let compressed = match self.algorithm {
753 CompressionAlgorithm::None => data.to_vec(),
754 CompressionAlgorithm::Rle => RleCompressor::compress(data),
755 CompressionAlgorithm::Delta => DeltaCompressor::compress(data),
756 CompressionAlgorithm::Huffman => HuffmanCompressor::compress(data),
757 CompressionAlgorithm::Lz77 => Lz77Compressor::compress(data),
758 };
759 let compressed_size = compressed.len();
760
761 #[cfg(target_arch = "wasm32")]
762 let elapsed = js_sys::Date::now() - start;
763 #[cfg(not(target_arch = "wasm32"))]
764 let elapsed = start.elapsed().as_secs_f64() * 1000.0;
765
766 let stats = CompressionStats::new(original_size, compressed_size, elapsed);
767 (compressed, stats)
768 }
769 pub fn decompress(&self, data: &[u8]) -> WasmResult<Vec<u8>> {
771 match self.algorithm {
772 CompressionAlgorithm::None => Ok(data.to_vec()),
773 CompressionAlgorithm::Rle => RleCompressor::decompress(data),
774 CompressionAlgorithm::Delta => DeltaCompressor::decompress(data),
775 CompressionAlgorithm::Huffman => Ok(data.to_vec()),
776 CompressionAlgorithm::Lz77 => Lz77Compressor::decompress(data),
777 }
778 }
779 pub fn compress_2d(&self, data: &[u8], width: usize) -> (Vec<u8>, CompressionStats) {
781 #[cfg(target_arch = "wasm32")]
782 let start = js_sys::Date::now();
783 #[cfg(not(target_arch = "wasm32"))]
784 let start = std::time::Instant::now();
785
786 let original_size = data.len();
787 let compressed = match self.algorithm {
788 CompressionAlgorithm::Delta => DeltaCompressor::compress_2d(data, width),
789 _ => self.compress(data).0,
790 };
791 let compressed_size = compressed.len();
792
793 #[cfg(target_arch = "wasm32")]
794 let elapsed = js_sys::Date::now() - start;
795 #[cfg(not(target_arch = "wasm32"))]
796 let elapsed = start.elapsed().as_secs_f64() * 1000.0;
797
798 let stats = CompressionStats::new(original_size, compressed_size, elapsed);
799 (compressed, stats)
800 }
801 pub fn decompress_2d(&self, data: &[u8], width: usize) -> WasmResult<Vec<u8>> {
803 match self.algorithm {
804 CompressionAlgorithm::Delta => DeltaCompressor::decompress_2d(data, width),
805 _ => self.decompress(data),
806 }
807 }
808}
809pub struct CompressionBenchmark;
811impl CompressionBenchmark {
812 pub fn benchmark_all(data: &[u8]) -> Vec<(CompressionAlgorithm, CompressionStats)> {
814 let algorithms = [
815 CompressionAlgorithm::None,
816 CompressionAlgorithm::Rle,
817 CompressionAlgorithm::Delta,
818 CompressionAlgorithm::Huffman,
819 CompressionAlgorithm::Lz77,
820 ];
821 let mut results = Vec::new();
822 for &algorithm in &algorithms {
823 let compressor = TileCompressor::new(algorithm);
824 let (_compressed, stats) = compressor.compress(data);
825 results.push((algorithm, stats));
826 }
827 results
828 }
829 pub fn find_best(data: &[u8]) -> CompressionAlgorithm {
831 let results = Self::benchmark_all(data);
832 results
833 .into_iter()
834 .min_by(|a, b| {
835 a.1.compressed_size.cmp(&b.1.compressed_size).then_with(|| {
836 a.1.compression_time_ms
837 .partial_cmp(&b.1.compression_time_ms)
838 .unwrap_or(std::cmp::Ordering::Equal)
839 })
840 })
841 .map(|(algo, _)| algo)
842 .unwrap_or(CompressionAlgorithm::None)
843 }
844}
845#[derive(Debug, Clone)]
847pub struct CompressionSelector {
848 history: Vec<(CompressionAlgorithm, CompressionStats)>,
850 max_history: usize,
852}
853impl CompressionSelector {
854 pub fn new() -> Self {
856 Self {
857 history: Vec::new(),
858 max_history: 10,
859 }
860 }
861 pub fn select_best(&mut self, data: &[u8]) -> WasmResult<CompressionAlgorithm> {
863 let best = CompressionBenchmark::find_best(data);
864 let compressor = TileCompressor::new(best);
865 let (_compressed, stats) = compressor.compress(data);
866 self.history.push((best, stats));
867 if self.history.len() > self.max_history {
868 self.history.remove(0);
869 }
870 Ok(best)
871 }
872 pub fn history(&self) -> &[(CompressionAlgorithm, CompressionStats)] {
874 &self.history
875 }
876 pub fn clear_history(&mut self) {
878 self.history.clear();
879 }
880}
881impl Default for CompressionSelector {
882 fn default() -> Self {
883 Self::new()
884 }
885}
886#[cfg(test)]
887mod tests {
888 use super::*;
889 #[test]
890 fn test_rle_compress_decompress() {
891 let data = vec![1, 1, 1, 2, 2, 3, 3, 3, 3];
892 let compressed = RleCompressor::compress(&data);
893 let decompressed = RleCompressor::decompress(&compressed).expect("Decompress failed");
894 assert_eq!(data, decompressed);
895 assert!(compressed.len() < data.len());
896 }
897 #[test]
898 fn test_delta_compress_decompress() {
899 let data = vec![10, 15, 20, 25, 30, 35, 40];
900 let compressed = DeltaCompressor::compress(&data);
901 let decompressed = DeltaCompressor::decompress(&compressed).expect("Decompress failed");
902 assert_eq!(data, decompressed);
903 }
904 #[test]
905 fn test_delta_2d() {
906 let data = vec![10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120];
907 let width = 3;
908 let compressed = DeltaCompressor::compress_2d(&data, width);
909 let decompressed =
910 DeltaCompressor::decompress_2d(&compressed, width).expect("Decompress 2D failed");
911 assert_eq!(data, decompressed);
912 }
913 #[test]
914 fn test_lz77_compress_decompress() {
915 let data = b"ABCABCABCABC";
916 let compressed = Lz77Compressor::compress(data);
917 let decompressed = Lz77Compressor::decompress(&compressed).expect("Decompress failed");
918 assert_eq!(data.to_vec(), decompressed);
919 }
920 #[test]
921 fn test_compression_stats() {
922 let stats = CompressionStats::new(1000, 500, 10.0);
923 assert_eq!(stats.space_saved(), 500);
924 assert_eq!(stats.space_saved_percent(), 50.0);
925 assert_eq!(stats.ratio, 0.5);
926 }
927 #[test]
928 fn test_tile_compressor() {
929 let compressor = TileCompressor::new(CompressionAlgorithm::Rle);
930 let data = vec![1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3];
931 let (compressed, stats) = compressor.compress(&data);
932 assert!(stats.compressed_size <= data.len());
933 let decompressed = compressor
934 .decompress(&compressed)
935 .expect("Decompress failed");
936 assert_eq!(data, decompressed);
937 }
938 #[test]
939 #[ignore]
940 fn test_compression_benchmark() {
941 let data = vec![1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5];
942 let results = CompressionBenchmark::benchmark_all(&data);
943 assert_eq!(results.len(), 5);
944 let algorithms: Vec<_> = results.iter().map(|(algo, _)| *algo).collect();
945 assert!(algorithms.contains(&CompressionAlgorithm::None));
946 assert!(algorithms.contains(&CompressionAlgorithm::Rle));
947 }
948 #[test]
949 #[ignore]
950 fn test_find_best_compression() {
951 let data = vec![1, 1, 1, 1, 2, 2, 2, 2];
952 let best = CompressionBenchmark::find_best(&data);
953 assert_ne!(best, CompressionAlgorithm::None);
954 }
955}