zuper_image_encoder.pl 2.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. #!/usr/bin/perl
  2. # Author: Trizen
  3. # Date: 26 November 2022
  4. # https://github.com/trizen
  5. # A very simple lossless image encoder, using Zstandard compression.
  6. # Pretty good at compressing computer-generated images.
  7. use 5.020;
  8. use warnings;
  9. use Imager;
  10. use experimental qw(signatures);
  11. use IO::Compress::Zstd qw(zstd $ZstdError);
  12. sub zuper_encoder ($img, $out_fh) {
  13. my $width = $img->getwidth;
  14. my $height = $img->getheight;
  15. my $channels = $img->getchannels;
  16. my $colorspace = 0;
  17. say "[$width, $height, $channels, $colorspace]";
  18. my @header = unpack('C*', 'zprf');
  19. push @header, unpack('C4', pack('N', $width));
  20. push @header, unpack('C4', pack('N', $height));
  21. push @header, $channels;
  22. push @header, $colorspace;
  23. my $index = 0;
  24. my @channels = map { "" } (1 .. $channels);
  25. foreach my $y (0 .. $height - 1) {
  26. my @line = split(//, scalar $img->getscanline(y => $y));
  27. my $line_len = scalar(@line);
  28. for (my $i = 0 ; $i < $line_len ; $i += 4) {
  29. my @px = splice(@line, 0, 4);
  30. foreach my $j (0 .. $channels - 1) {
  31. $channels[$j] .= $px[$j];
  32. }
  33. ++$index;
  34. }
  35. }
  36. my @footer;
  37. push(@footer, (0x00) x 7);
  38. push(@footer, 0x01);
  39. my $all_channels = '';
  40. foreach my $channel (@channels) {
  41. $all_channels .= $channel;
  42. }
  43. zstd(\$all_channels, \my $z)
  44. or die "zstd failed: $ZstdError\n";
  45. my $before = length($all_channels);
  46. my $after = length($z);
  47. say "Compression: $before -> $after (saved ", sprintf("%.2f%%", 100 - $after / $before * 100), ")";
  48. # Header
  49. print $out_fh pack('C*', @header);
  50. # Compressed data
  51. print $out_fh pack('N', $after);
  52. print $out_fh $z;
  53. # Footer
  54. print $out_fh pack('C*', @footer);
  55. }
  56. @ARGV || do {
  57. say STDERR "usage: $0 [input.png] [output.zpr]";
  58. exit(2);
  59. };
  60. my $in_file = $ARGV[0];
  61. my $out_file = $ARGV[1] // "$in_file.zpr";
  62. my $img = 'Imager'->new(file => $in_file)
  63. or die "Can't read image: $in_file";
  64. open(my $out_fh, '>:raw', $out_file)
  65. or die "Can't open file <<$out_file>> for writing: $!";
  66. zuper_encoder($img, $out_fh);