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() % 2 != 0 {
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}
394pub struct HuffmanCompressor;
396impl HuffmanCompressor {
397 fn build_frequency_table(data: &[u8]) -> HashMap<u8, u32> {
399 let mut frequencies = HashMap::new();
400 for &byte in data {
401 *frequencies.entry(byte).or_insert(0) += 1;
402 }
403 frequencies
404 }
405 fn build_tree(frequencies: &HashMap<u8, u32>) -> Option<HuffmanNode> {
407 if frequencies.is_empty() {
408 return None;
409 }
410 let mut nodes: Vec<HuffmanNode> = frequencies
411 .iter()
412 .map(|(&symbol, &frequency)| HuffmanNode::Leaf { symbol, frequency })
413 .collect();
414 while nodes.len() > 1 {
415 nodes.sort_by_key(|n| std::cmp::Reverse(n.frequency()));
416 let right = nodes.pop()?;
417 let left = nodes.pop()?;
418 let internal = HuffmanNode::Internal {
419 frequency: left.frequency() + right.frequency(),
420 left: Box::new(left),
421 right: Box::new(right),
422 };
423 nodes.push(internal);
424 }
425 nodes.pop()
426 }
427 fn generate_codes(node: &HuffmanNode, prefix: Vec<bool>, codes: &mut HashMap<u8, Vec<bool>>) {
429 match node {
430 HuffmanNode::Leaf { symbol, .. } => {
431 codes.insert(*symbol, prefix);
432 }
433 HuffmanNode::Internal { left, right, .. } => {
434 let mut left_prefix = prefix.clone();
435 left_prefix.push(false);
436 Self::generate_codes(left, left_prefix, codes);
437 let mut right_prefix = prefix;
438 right_prefix.push(true);
439 Self::generate_codes(right, right_prefix, codes);
440 }
441 }
442 }
443 pub fn compress(data: &[u8]) -> Vec<u8> {
451 if data.is_empty() {
452 return Vec::new();
453 }
454 let frequencies = Self::build_frequency_table(data);
455 let tree = match Self::build_tree(&frequencies) {
456 Some(t) => t,
457 None => return Vec::new(),
458 };
459 let mut codes = HashMap::new();
460 Self::generate_codes(&tree, Vec::new(), &mut codes);
461
462 let num_symbols = frequencies.len() as u16;
464 let mut compressed = Vec::new();
465 compressed.extend_from_slice(&num_symbols.to_le_bytes());
466
467 let mut sorted_symbols: Vec<(u8, u32)> = frequencies.into_iter().collect();
469 sorted_symbols.sort_by_key(|(sym, _)| *sym);
470 for (symbol, freq) in &sorted_symbols {
471 compressed.push(*symbol);
472 compressed.extend_from_slice(&freq.to_le_bytes());
473 }
474
475 compressed.extend_from_slice(&(data.len() as u64).to_le_bytes());
477
478 let mut bit_buffer = 0u8;
480 let mut bit_count = 0u8;
481 for &byte in data {
482 if let Some(code) = codes.get(&byte) {
483 for &bit in code {
484 if bit {
485 bit_buffer |= 1 << (7 - bit_count);
486 }
487 bit_count += 1;
488 if bit_count == 8 {
489 compressed.push(bit_buffer);
490 bit_buffer = 0;
491 bit_count = 0;
492 }
493 }
494 }
495 }
496 if bit_count > 0 {
497 compressed.push(bit_buffer);
498 }
499 compressed
500 }
501
502 pub fn decompress(compressed: &[u8]) -> WasmResult<Vec<u8>> {
507 if compressed.is_empty() {
508 return Ok(Vec::new());
509 }
510
511 if compressed.len() < 2 {
513 return Err(WasmError::InvalidOperation {
514 operation: "Huffman decompression".to_string(),
515 reason: "Data too short to contain symbol count header".to_string(),
516 });
517 }
518 let num_symbols = u16::from_le_bytes([compressed[0], compressed[1]]) as usize;
519 let mut pos = 2usize;
520
521 let symbol_table_size = num_symbols * 5;
523 if pos + symbol_table_size + 8 > compressed.len() {
524 return Err(WasmError::InvalidOperation {
525 operation: "Huffman decompression".to_string(),
526 reason: "Data too short to contain symbol table and length header".to_string(),
527 });
528 }
529
530 let mut frequencies: HashMap<u8, u32> = HashMap::new();
531 for _ in 0..num_symbols {
532 let symbol = compressed[pos];
533 pos += 1;
534 let freq = u32::from_le_bytes([
535 compressed[pos],
536 compressed[pos + 1],
537 compressed[pos + 2],
538 compressed[pos + 3],
539 ]);
540 pos += 4;
541 frequencies.insert(symbol, freq);
542 }
543
544 let original_len = u64::from_le_bytes([
546 compressed[pos],
547 compressed[pos + 1],
548 compressed[pos + 2],
549 compressed[pos + 3],
550 compressed[pos + 4],
551 compressed[pos + 5],
552 compressed[pos + 6],
553 compressed[pos + 7],
554 ]) as usize;
555 pos += 8;
556
557 if original_len == 0 {
558 return Ok(Vec::new());
559 }
560
561 let tree = Self::build_tree(&frequencies).ok_or_else(|| WasmError::InvalidOperation {
563 operation: "Huffman decompression".to_string(),
564 reason: "Failed to reconstruct Huffman tree from frequency table".to_string(),
565 })?;
566
567 let bitstream = &compressed[pos..];
569 let mut result = Vec::with_capacity(original_len);
570 let mut node = &tree;
571
572 'outer: for &byte in bitstream {
573 for bit_idx in (0..8).rev() {
574 let bit = (byte >> bit_idx) & 1;
575 node = match node {
576 HuffmanNode::Internal { left, right, .. } => {
577 if bit == 0 {
578 left
579 } else {
580 right
581 }
582 }
583 HuffmanNode::Leaf { .. } => {
584 return Err(WasmError::InvalidOperation {
586 operation: "Huffman decompression".to_string(),
587 reason: "Unexpected leaf mid-traversal in bit stream".to_string(),
588 });
589 }
590 };
591
592 if let HuffmanNode::Leaf { symbol, .. } = node {
593 result.push(*symbol);
594 if result.len() == original_len {
595 break 'outer;
596 }
597 node = &tree;
599 }
600 }
601 }
602
603 if result.is_empty() && original_len > 0 {
605 if let HuffmanNode::Leaf { symbol, .. } = &tree {
606 result.resize(original_len, *symbol);
607 }
608 }
609
610 if result.len() != original_len {
611 return Err(WasmError::InvalidOperation {
612 operation: "Huffman decompression".to_string(),
613 reason: format!(
614 "Decoded {} bytes but expected {}",
615 result.len(),
616 original_len
617 ),
618 });
619 }
620
621 Ok(result)
622 }
623}
624pub struct Lz77Compressor;
626impl Lz77Compressor {
627 const WINDOW_SIZE: usize = 4096;
629 const MAX_MATCH_LENGTH: usize = 18;
631 const MIN_MATCH_LENGTH: usize = 3;
633 fn find_longest_match(data: &[u8], pos: usize) -> Option<(usize, usize)> {
635 let window_start = pos.saturating_sub(Self::WINDOW_SIZE);
636 let mut best_match = None;
637 let mut best_length = 0;
638 for start in window_start..pos {
639 let mut length = 0;
640 while length < Self::MAX_MATCH_LENGTH
641 && pos + length < data.len()
642 && data[start + length] == data[pos + length]
643 {
644 length += 1;
645 }
646 if length >= Self::MIN_MATCH_LENGTH && length > best_length {
647 best_length = length;
648 best_match = Some((pos - start, length));
649 }
650 }
651 best_match
652 }
653 pub fn compress(data: &[u8]) -> Vec<u8> {
655 if data.is_empty() {
656 return Vec::new();
657 }
658 let mut compressed = Vec::new();
659 let mut pos = 0;
660 while pos < data.len() {
661 if let Some((distance, length)) = Self::find_longest_match(data, pos) {
662 compressed.push(1);
663 compressed.push((distance >> 8) as u8);
664 compressed.push((distance & 0xFF) as u8);
665 compressed.push(length as u8);
666 pos += length;
667 } else {
668 compressed.push(0);
669 compressed.push(data[pos]);
670 pos += 1;
671 }
672 }
673 compressed
674 }
675 pub fn decompress(data: &[u8]) -> WasmResult<Vec<u8>> {
677 let mut decompressed = Vec::new();
678 let mut i = 0;
679 while i < data.len() {
680 let flag = data[i];
681 i += 1;
682 if flag == 1 {
683 if i + 3 > data.len() {
684 return Err(WasmError::InvalidOperation {
685 operation: "LZ77 decompress".to_string(),
686 reason: "Unexpected end of data".to_string(),
687 });
688 }
689 let distance = ((data[i] as usize) << 8) | (data[i + 1] as usize);
690 let length = data[i + 2] as usize;
691 i += 3;
692 let start = decompressed.len().saturating_sub(distance);
693 for j in 0..length {
694 if start + j < decompressed.len() {
695 let byte = decompressed[start + j];
696 decompressed.push(byte);
697 }
698 }
699 } else {
700 if i >= data.len() {
701 return Err(WasmError::InvalidOperation {
702 operation: "LZ77 decompress".to_string(),
703 reason: "Unexpected end of data".to_string(),
704 });
705 }
706 decompressed.push(data[i]);
707 i += 1;
708 }
709 }
710 Ok(decompressed)
711 }
712}
713pub struct TileCompressor {
715 algorithm: CompressionAlgorithm,
717}
718impl TileCompressor {
719 pub const fn new(algorithm: CompressionAlgorithm) -> Self {
721 Self { algorithm }
722 }
723 pub fn compress(&self, data: &[u8]) -> (Vec<u8>, CompressionStats) {
725 #[cfg(target_arch = "wasm32")]
726 let start = js_sys::Date::now();
727 #[cfg(not(target_arch = "wasm32"))]
728 let start = std::time::Instant::now();
729
730 let original_size = data.len();
731 let compressed = match self.algorithm {
732 CompressionAlgorithm::None => data.to_vec(),
733 CompressionAlgorithm::Rle => RleCompressor::compress(data),
734 CompressionAlgorithm::Delta => DeltaCompressor::compress(data),
735 CompressionAlgorithm::Huffman => HuffmanCompressor::compress(data),
736 CompressionAlgorithm::Lz77 => Lz77Compressor::compress(data),
737 };
738 let compressed_size = compressed.len();
739
740 #[cfg(target_arch = "wasm32")]
741 let elapsed = js_sys::Date::now() - start;
742 #[cfg(not(target_arch = "wasm32"))]
743 let elapsed = start.elapsed().as_secs_f64() * 1000.0;
744
745 let stats = CompressionStats::new(original_size, compressed_size, elapsed);
746 (compressed, stats)
747 }
748 pub fn decompress(&self, data: &[u8]) -> WasmResult<Vec<u8>> {
750 match self.algorithm {
751 CompressionAlgorithm::None => Ok(data.to_vec()),
752 CompressionAlgorithm::Rle => RleCompressor::decompress(data),
753 CompressionAlgorithm::Delta => DeltaCompressor::decompress(data),
754 CompressionAlgorithm::Huffman => Ok(data.to_vec()),
755 CompressionAlgorithm::Lz77 => Lz77Compressor::decompress(data),
756 }
757 }
758 pub fn compress_2d(&self, data: &[u8], width: usize) -> (Vec<u8>, CompressionStats) {
760 #[cfg(target_arch = "wasm32")]
761 let start = js_sys::Date::now();
762 #[cfg(not(target_arch = "wasm32"))]
763 let start = std::time::Instant::now();
764
765 let original_size = data.len();
766 let compressed = match self.algorithm {
767 CompressionAlgorithm::Delta => DeltaCompressor::compress_2d(data, width),
768 _ => self.compress(data).0,
769 };
770 let compressed_size = compressed.len();
771
772 #[cfg(target_arch = "wasm32")]
773 let elapsed = js_sys::Date::now() - start;
774 #[cfg(not(target_arch = "wasm32"))]
775 let elapsed = start.elapsed().as_secs_f64() * 1000.0;
776
777 let stats = CompressionStats::new(original_size, compressed_size, elapsed);
778 (compressed, stats)
779 }
780 pub fn decompress_2d(&self, data: &[u8], width: usize) -> WasmResult<Vec<u8>> {
782 match self.algorithm {
783 CompressionAlgorithm::Delta => DeltaCompressor::decompress_2d(data, width),
784 _ => self.decompress(data),
785 }
786 }
787}
788pub struct CompressionBenchmark;
790impl CompressionBenchmark {
791 pub fn benchmark_all(data: &[u8]) -> Vec<(CompressionAlgorithm, CompressionStats)> {
793 let algorithms = [
794 CompressionAlgorithm::None,
795 CompressionAlgorithm::Rle,
796 CompressionAlgorithm::Delta,
797 CompressionAlgorithm::Huffman,
798 CompressionAlgorithm::Lz77,
799 ];
800 let mut results = Vec::new();
801 for &algorithm in &algorithms {
802 let compressor = TileCompressor::new(algorithm);
803 let (_compressed, stats) = compressor.compress(data);
804 results.push((algorithm, stats));
805 }
806 results
807 }
808 pub fn find_best(data: &[u8]) -> CompressionAlgorithm {
810 let results = Self::benchmark_all(data);
811 results
812 .into_iter()
813 .min_by(|a, b| {
814 a.1.compressed_size.cmp(&b.1.compressed_size).then_with(|| {
815 a.1.compression_time_ms
816 .partial_cmp(&b.1.compression_time_ms)
817 .unwrap_or(std::cmp::Ordering::Equal)
818 })
819 })
820 .map(|(algo, _)| algo)
821 .unwrap_or(CompressionAlgorithm::None)
822 }
823}
824#[derive(Debug, Clone)]
826pub struct CompressionSelector {
827 history: Vec<(CompressionAlgorithm, CompressionStats)>,
829 max_history: usize,
831}
832impl CompressionSelector {
833 pub fn new() -> Self {
835 Self {
836 history: Vec::new(),
837 max_history: 10,
838 }
839 }
840 pub fn select_best(&mut self, data: &[u8]) -> WasmResult<CompressionAlgorithm> {
842 let best = CompressionBenchmark::find_best(data);
843 let compressor = TileCompressor::new(best);
844 let (_compressed, stats) = compressor.compress(data);
845 self.history.push((best, stats));
846 if self.history.len() > self.max_history {
847 self.history.remove(0);
848 }
849 Ok(best)
850 }
851 pub fn history(&self) -> &[(CompressionAlgorithm, CompressionStats)] {
853 &self.history
854 }
855 pub fn clear_history(&mut self) {
857 self.history.clear();
858 }
859}
860impl Default for CompressionSelector {
861 fn default() -> Self {
862 Self::new()
863 }
864}
865#[cfg(test)]
866mod tests {
867 use super::*;
868 #[test]
869 fn test_rle_compress_decompress() {
870 let data = vec![1, 1, 1, 2, 2, 3, 3, 3, 3];
871 let compressed = RleCompressor::compress(&data);
872 let decompressed = RleCompressor::decompress(&compressed).expect("Decompress failed");
873 assert_eq!(data, decompressed);
874 assert!(compressed.len() < data.len());
875 }
876 #[test]
877 fn test_delta_compress_decompress() {
878 let data = vec![10, 15, 20, 25, 30, 35, 40];
879 let compressed = DeltaCompressor::compress(&data);
880 let decompressed = DeltaCompressor::decompress(&compressed).expect("Decompress failed");
881 assert_eq!(data, decompressed);
882 }
883 #[test]
884 fn test_delta_2d() {
885 let data = vec![10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120];
886 let width = 3;
887 let compressed = DeltaCompressor::compress_2d(&data, width);
888 let decompressed =
889 DeltaCompressor::decompress_2d(&compressed, width).expect("Decompress 2D failed");
890 assert_eq!(data, decompressed);
891 }
892 #[test]
893 fn test_lz77_compress_decompress() {
894 let data = b"ABCABCABCABC";
895 let compressed = Lz77Compressor::compress(data);
896 let decompressed = Lz77Compressor::decompress(&compressed).expect("Decompress failed");
897 assert_eq!(data.to_vec(), decompressed);
898 }
899 #[test]
900 fn test_compression_stats() {
901 let stats = CompressionStats::new(1000, 500, 10.0);
902 assert_eq!(stats.space_saved(), 500);
903 assert_eq!(stats.space_saved_percent(), 50.0);
904 assert_eq!(stats.ratio, 0.5);
905 }
906 #[test]
907 fn test_tile_compressor() {
908 let compressor = TileCompressor::new(CompressionAlgorithm::Rle);
909 let data = vec![1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3];
910 let (compressed, stats) = compressor.compress(&data);
911 assert!(stats.compressed_size <= data.len());
912 let decompressed = compressor
913 .decompress(&compressed)
914 .expect("Decompress failed");
915 assert_eq!(data, decompressed);
916 }
917 #[test]
918 #[ignore]
919 fn test_compression_benchmark() {
920 let data = vec![1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5];
921 let results = CompressionBenchmark::benchmark_all(&data);
922 assert_eq!(results.len(), 5);
923 let algorithms: Vec<_> = results.iter().map(|(algo, _)| *algo).collect();
924 assert!(algorithms.contains(&CompressionAlgorithm::None));
925 assert!(algorithms.contains(&CompressionAlgorithm::Rle));
926 }
927 #[test]
928 #[ignore]
929 fn test_find_best_compression() {
930 let data = vec![1, 1, 1, 1, 2, 2, 2, 2];
931 let best = CompressionBenchmark::find_best(&data);
932 assert_ne!(best, CompressionAlgorithm::None);
933 }
934}