SHOW:
|
|
- or go back to the newest paste.
1 | #!/usr/bin/perl -ws | |
2 | # jpegrescan by Loren Merritt | |
3 | # Last updated: 2008-11-29 / 2013-03-19 | |
4 | # This code is public domain. | |
5 | ||
6 | use File::Slurp; | |
7 | use File::Temp qw/ tempfile /; | |
8 | use IPC::Run qw(start pump finish); | |
9 | ||
10 | @ARGV==2 or die "usage: jpegrescan in.jpg out.jpg | |
11 | tries various progressive scan orders | |
12 | switches: | |
13 | -s strip from all extra markers (`jpegtran -copy none` otherwise `jpegtran -copy all`) | |
14 | -v verbose output | |
15 | -q supress all output | |
16 | -a use arithmetic coding (unsupported by most software) | |
17 | "; | |
18 | $fin = $ARGV[0]; | |
19 | $fout = $ARGV[1]; | |
20 | (undef, $ftmp) = tempfile(SUFFIX => ".scan"); | |
21 | $jtmp = $fout; | |
22 | $verbose = $v; | |
23 | $quiet = $q; | |
24 | @strip = $s ? ("-copy","none") : ("-copy","all"); | |
25 | @arith = $a ? ("-arithmetic") : (); | |
26 | undef $_ for $v,$q,$s,$a; | |
27 | undef $/; | |
28 | $|=1; | |
29 | ||
30 | # convert the input to baseline, just to make all the other conversions faster | |
31 | # FIXME there's still a bunch of redundant computation in separate calls to jpegtran | |
32 | open $OLDERR, ">&", STDERR; | |
33 | open STDERR, ">", $ftmp; | |
34 | - | my $handle=start \("jpegtran", "-v", @strip, "-optimize", $fin),\$in,\$out,\$err; |
34 | + | my $handle=start \("jpegtran", "-v", @strip, "-optimize", $fin),\$in; |
35 | - | $in=$jtmp; |
35 | + | open FILE,$jtmp; |
36 | $in=join "",<FILE>; | |
37 | - | finish $h or die "something is really wrong"; |
37 | + | close FILE; |
38 | pump $handle while length($in); | |
39 | finish $handle or die "something is really wrong"; | |
40 | ||
41 | open STDERR, ">&", $OLDERR; | |
42 | ||
43 | $type = read_file($ftmp); | |
44 | $type =~ /components=(\d+)/ or die; | |
45 | $rgb = $1==3 ? 1 : $1==1 ? 0 : die "not RGB nor gray\n"; | |
46 | ||
47 | # FIXME optimize order for either progressive transfer or decoding speed | |
48 | sub canonize { | |
49 | my $txt = $prefix.$suffix.shift; | |
50 | $txt =~ s/\s*;\s*/;\n/g; | |
51 | $txt =~ s/^\s*//; | |
52 | $txt =~ s/ +/ /g; | |
53 | $txt =~ s/: (\d+) (\d+)/sprintf ": %2d %2d", $1, $2/ge; | |
54 | # treat u and v identically. I shouldn't need to do this, but with jpegtran overhead it saves 9% speed. cost: .008% bitrate. | |
55 | $txt =~ s/^2:.*\n//gm; | |
56 | $txt =~ s/^1:(.+)\n/1:$1\n2:$1\n/gm; | |
57 | # dc before ac, coarse before fine | |
58 | my @txt = sort {"$a\n$b" =~ /: *(\d+) .* (\d);\n.*: *(\d+) .* (\d);/ or die; !$3 <=> !$1 or $4 <=> $2 or $a cmp $b;} split /\n/, $txt; | |
59 | return join "\n", @txt; | |
60 | } | |
61 | ||
62 | sub try { | |
63 | my $txt = canonize(shift); | |
64 | return $memo{$txt} if $memo{$txt}; | |
65 | write_file($ftmp, $txt); | |
66 | open TRAN, "-|", "jpegtran", @arith, @strip, "-scans", $ftmp, $jtmp or die; | |
67 | $data = <TRAN>; | |
68 | close TRAN; | |
69 | my $s = length $data; | |
70 | $s or die; | |
71 | $memo{$txt} = $s; | |
72 | !$quiet && print $verbose ? "$txt\n$s\n\n" : "."; | |
73 | return $s; | |
74 | } | |
75 | ||
76 | sub triesn { | |
77 | my($bmode, $bsize); | |
78 | my ($limit, @modes) = @_; | |
79 | my $overshoot = 0; | |
80 | for(@modes) { | |
81 | my $s = try($_); | |
82 | if(!$bsize || $s < $bsize) { | |
83 | $bsize = $s; | |
84 | $bmode = $_; | |
85 | $overshoot = 0; | |
86 | } elsif(++$overshoot >= $limit) { | |
87 | last; | |
88 | } | |
89 | } | |
90 | return $bmode; | |
91 | } | |
92 | ||
93 | sub tries { triesn(99, @_); } | |
94 | ||
95 | $prefix = ""; | |
96 | $suffix = ""; | |
97 | ||
98 | if($rgb) { | |
99 | # 012 helps very little | |
100 | # 0/12 and 0/1/2 are pretty evenly matched in frequency, but 0/12 wins in total size if every image had to use the same mode | |
101 | # dc refinement passes never help | |
102 | $dc = tries( | |
103 | # "0: 0 0 0 0; 1 2: 0 0 0 0;", # two scans expose a bug in Opera <= 11.61 | |
104 | "0: 0 0 0 0; 1: 0 0 0 0; 2: 0 0 0 0;"); | |
105 | # jpegtran won't let me omit dc entirely, but I can at least quantize it away to make the rest of the tests faster. | |
106 | $prefix = "0 1 2: 0 0 0 9;"; | |
107 | } else { | |
108 | $dc = "0: 0 0 0 0;"; | |
109 | $prefix = "0: 0 0 0 9;"; | |
110 | } | |
111 | ||
112 | # luma can make use of up to 3 refinement passes. | |
113 | # chroma can make use of up to 2 refinement passes. | |
114 | # refinement passes have some chance of being split (luma: 4%,4%,4%. chroma: 20%,8%) but the total bit gain is negligible. | |
115 | # msb pass should almost always be split (luma: 87%, chroma: 81%). | |
116 | # I have no theoretical reason for this list of split positions, they're just the most common in practice. | |
117 | # splitting into 3 ections is often slightly better, but the total number of bits saved is negligible. | |
118 | # FIXME: penalize lots of refinement passes because it's slower to decode. if so, then also force overwrite if bigger than the input. | |
119 | sub try_splits { | |
120 | my $str = shift; | |
121 | my %n = map {$_ => sprintf "$c: 1 %d $str; $c: %d 63 $str;", $_, $_+1} 2,5,8,12,18; | |
122 | my $mode = triesn(3, "$c: 1 63 $str;", @n{2,8,5}); | |
123 | return $mode if $mode ne $n{8}; | |
124 | return triesn(1, $mode, @n{12,18}); | |
125 | } | |
126 | ||
127 | foreach $c (0..$rgb) { | |
128 | my @modes; | |
129 | my $ml = ""; | |
130 | for(0..($c?2:3)) { | |
131 | push @modes, "$c: 1 8 0 $_; $c: 9 63 0 $_;".$ml; | |
132 | $ml .= sprintf("$c: 1 63 %d %d;", $_+1, $_); | |
133 | } | |
134 | my $refine = triesn(1, @modes); | |
135 | $refine =~ s/.* (0 \d);//; | |
136 | $ac .= $refine . try_splits($1); | |
137 | } | |
138 | ||
139 | $prefix = ""; | |
140 | undef %memo; | |
141 | $mode = canonize($dc.$ac); | |
142 | try($mode); | |
143 | $size = $memo{$mode}; | |
144 | !$quiet && print "\n$mode\n$size\n"; | |
145 | $old_size = -s $fin; | |
146 | !$quiet && printf "%+.2f%%\n", ($size/$old_size-1)*100; | |
147 | if($size <= $old_size) { | |
148 | write_file($fout, $data); | |
149 | } | |
150 | unlink $ftmp; |