zuper_image_decoder.pl 2.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192
  1. #!/usr/bin/perl
  2. # Author: Trizen
  3. # Date: 26 November 2022
  4. # https://github.com/trizen
  5. # A decoder for the Zuper (ZPR) image format, generating PNG images.
  6. use 5.020;
  7. use warnings;
  8. use Imager;
  9. use experimental qw(signatures);
  10. use IO::Uncompress::UnZstd qw(unzstd $UnZstdError);
  11. sub zpr_decoder ($bytes) {
  12. my sub invalid() {
  13. die "Not a ZPR image";
  14. }
  15. my $index = 0;
  16. pack('C4', map { $bytes->[$index++] } 1 .. 4) eq 'zprf' or invalid();
  17. my $width = unpack('N', pack('C4', map { $bytes->[$index++] } 1 .. 4));
  18. my $height = unpack('N', pack('C4', map { $bytes->[$index++] } 1 .. 4));
  19. my $channels = $bytes->[$index++];
  20. my $colorspace = $bytes->[$index++];
  21. ($width > 0 and $height > 0) or invalid();
  22. ($channels > 0 and $channels <= 4) or invalid();
  23. ($colorspace == 0 or $colorspace == 1) or invalid();
  24. pop(@$bytes) == 0x01 or invalid();
  25. for (1 .. 7) {
  26. pop(@$bytes) == 0x00 or invalid();
  27. }
  28. say "[$width, $height, $channels, $colorspace]";
  29. my $len = unpack('N', pack('C4', map { $bytes->[$index++] } 1 .. 4));
  30. scalar(@$bytes) - $index == $len or invalid();
  31. splice(@$bytes, 0, $index);
  32. my $z = pack('C' . $len, @$bytes);
  33. unzstd(\$z, \my $all_channels)
  34. or die "unzstd failed: $UnZstdError\n";
  35. my $img = 'Imager'->new(
  36. xsize => $width,
  37. ysize => $height,
  38. channels => $channels,
  39. );
  40. my @channels = unpack(sprintf("(a%d)%d", $width * $height, $channels), $all_channels);
  41. my $diff = 4 - $channels;
  42. foreach my $y (0 .. $height - 1) {
  43. my $row = '';
  44. foreach my $x (1 .. $width) {
  45. $row .= substr($_, 0, 1, '') for @channels;
  46. $row .= chr(0) x $diff if $diff;
  47. }
  48. $img->setscanline(y => $y, pixels => $row);
  49. }
  50. return $img;
  51. }
  52. @ARGV || do {
  53. say STDERR "usage: $0 [input.zpr] [output.png]";
  54. exit(2);
  55. };
  56. my $in_file = $ARGV[0];
  57. my $out_file = $ARGV[1] // "$in_file.png";
  58. my @bytes = do {
  59. open(my $fh, '<:raw', $in_file)
  60. or die "Can't open file <<$in_file>> for reading: $!";
  61. local $/;
  62. unpack("C*", scalar <$fh>);
  63. };
  64. my $img = zpr_decoder(\@bytes);
  65. $img->write(file => $out_file, type => 'png');