line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Image::ThumbHash::PP; |
2
|
1
|
|
|
1
|
|
13
|
use v5.10.0; # // |
|
1
|
|
|
|
|
3
|
|
3
|
1
|
|
|
1
|
|
5
|
use strict; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
23
|
|
4
|
1
|
|
|
1
|
|
5
|
use warnings qw(all FATAL uninitialized); |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
69
|
|
5
|
|
|
|
|
|
|
|
6
|
1
|
|
|
1
|
|
6
|
use Exporter 5.57 qw(import); |
|
1
|
|
|
|
|
12
|
|
|
1
|
|
|
|
|
39
|
|
7
|
1
|
|
|
1
|
|
5
|
use Carp qw(croak); |
|
1
|
|
|
|
|
13
|
|
|
1
|
|
|
|
|
49
|
|
8
|
1
|
|
|
1
|
|
10
|
use List::Util qw(min max); |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
101
|
|
9
|
1
|
|
|
1
|
|
523
|
use MIME::Base64 (); |
|
1
|
|
|
|
|
728
|
|
|
1
|
|
|
|
|
45
|
|
10
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
use constant { |
12
|
1
|
|
|
|
|
3209
|
PI => 4 * atan2(1, 1), |
13
|
1
|
|
|
1
|
|
6
|
}; |
|
1
|
|
|
|
|
2
|
|
14
|
|
|
|
|
|
|
|
15
|
|
|
|
|
|
|
our $VERSION = '0.01'; |
16
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
our @EXPORT_OK = qw( |
18
|
|
|
|
|
|
|
rgba_to_thumb_hash |
19
|
|
|
|
|
|
|
rgba_to_png |
20
|
|
|
|
|
|
|
rgba_to_data_url |
21
|
|
|
|
|
|
|
thumb_hash_to_rgba |
22
|
|
|
|
|
|
|
thumb_hash_to_average_rgba |
23
|
|
|
|
|
|
|
thumb_hash_to_approximate_aspect_ratio |
24
|
|
|
|
|
|
|
thumb_hash_to_data_url |
25
|
|
|
|
|
|
|
); |
26
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
sub _assert_w_h_rgba { |
28
|
15
|
|
|
15
|
|
39
|
my ($width, $height, $rgba, $sub) = @_; |
29
|
15
|
|
33
|
|
|
172
|
$sub //= (caller 1)[3]; |
30
|
|
|
|
|
|
|
|
31
|
15
|
50
|
33
|
|
|
70
|
0 <= $width && $width <= 100 |
32
|
|
|
|
|
|
|
or croak "$sub: width is not in range [0, 100]: $width"; |
33
|
15
|
50
|
33
|
|
|
59
|
0 <= $height && $height <= 100 |
34
|
|
|
|
|
|
|
or croak "$sub: height is not in range [0, 100]: $height"; |
35
|
15
|
50
|
|
|
|
45
|
length($rgba) == $width * $height * 4 |
36
|
|
|
|
|
|
|
or croak "$sub: rgba length does not match " . ($width * $height * 4) . ": " . length($rgba); |
37
|
|
|
|
|
|
|
} |
38
|
|
|
|
|
|
|
|
39
|
|
|
|
|
|
|
sub _assert_thumb_hash { |
40
|
19
|
|
|
19
|
|
53
|
my ($hash, $sub) = @_; |
41
|
19
|
|
33
|
|
|
209
|
$sub //= (caller 1)[3]; |
42
|
|
|
|
|
|
|
|
43
|
19
|
100
|
|
|
|
341
|
length($hash) >= 5 |
44
|
|
|
|
|
|
|
or croak "$sub: thumb hash length is less than 5: " . length($hash); |
45
|
|
|
|
|
|
|
} |
46
|
|
|
|
|
|
|
|
47
|
|
|
|
|
|
|
sub rgba_to_thumb_hash { |
48
|
0
|
|
|
0
|
0
|
0
|
my ($width, $height, $rgba) = @_; |
49
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
# Encoding an image larger than 100x100 is slow with no benefit |
51
|
0
|
|
|
|
|
0
|
_assert_w_h_rgba $width, $height, $rgba; |
52
|
|
|
|
|
|
|
|
53
|
|
|
|
|
|
|
# Determine the average color |
54
|
0
|
|
|
|
|
0
|
my ($avg_r, $avg_g, $avg_b, $avg_a) = (0, 0, 0, 0); |
55
|
0
|
|
|
|
|
0
|
for my $pixel (unpack '(a4)*', $rgba) { |
56
|
0
|
|
|
|
|
0
|
my ($pr, $pg, $pb, $pa) = unpack 'C*', $pixel; |
57
|
0
|
|
|
|
|
0
|
my $alpha = $pa / 255; |
58
|
0
|
|
|
|
|
0
|
my $alpha_f = $alpha / 255; |
59
|
0
|
|
|
|
|
0
|
$avg_r += $alpha_f * $pr; |
60
|
0
|
|
|
|
|
0
|
$avg_g += $alpha_f * $pg; |
61
|
0
|
|
|
|
|
0
|
$avg_b += $alpha_f * $pb; |
62
|
0
|
|
|
|
|
0
|
$avg_a += $alpha; |
63
|
|
|
|
|
|
|
} |
64
|
0
|
0
|
|
|
|
0
|
if ($avg_a > 0) { |
65
|
0
|
|
|
|
|
0
|
$_ /= $avg_a for $avg_r, $avg_g, $avg_b; |
66
|
|
|
|
|
|
|
} |
67
|
|
|
|
|
|
|
|
68
|
0
|
|
|
|
|
0
|
my $has_alpha = $avg_a < $width * $height; |
69
|
0
|
0
|
|
|
|
0
|
my $l_limit = $has_alpha ? 5 : 7; # Use fewer luminance bits if there's alpha |
70
|
0
|
|
|
|
|
0
|
my $max_w_h = max $width, $height; |
71
|
0
|
|
|
|
|
0
|
my $lx = max 1, int($l_limit * $width / $max_w_h + 0.5); |
72
|
0
|
|
|
|
|
0
|
my $ly = max 1, int($l_limit * $height / $max_w_h + 0.5); |
73
|
|
|
|
|
|
|
my ( |
74
|
0
|
|
|
|
|
0
|
@l, # luminance |
75
|
|
|
|
|
|
|
@p, # yellow - blue |
76
|
|
|
|
|
|
|
@q, # red - green |
77
|
|
|
|
|
|
|
@a, # alpha |
78
|
|
|
|
|
|
|
); |
79
|
|
|
|
|
|
|
|
80
|
|
|
|
|
|
|
# Convert the image from RGBA to LPQA (composite atop the average color) |
81
|
0
|
|
|
|
|
0
|
for my $pixel (unpack '(a4)*', $rgba) { |
82
|
0
|
|
|
|
|
0
|
my ($pr, $pg, $pb, $pa) = unpack 'C*', $pixel; |
83
|
0
|
|
|
|
|
0
|
my $alpha = $pa / 255; |
84
|
0
|
|
|
|
|
0
|
my $alpha_f = $alpha / 255; |
85
|
0
|
|
|
|
|
0
|
my $r = $avg_r * (1 - $alpha) + $alpha_f * $pr; |
86
|
0
|
|
|
|
|
0
|
my $g = $avg_g * (1 - $alpha) + $alpha_f * $pg; |
87
|
0
|
|
|
|
|
0
|
my $b = $avg_b * (1 - $alpha) + $alpha_f * $pb; |
88
|
0
|
|
|
|
|
0
|
push @l, ($r + $g + $b) / 3; |
89
|
0
|
|
|
|
|
0
|
push @p, ($r + $g) / 2 - $b; |
90
|
0
|
|
|
|
|
0
|
push @q, $r - $g; |
91
|
0
|
|
|
|
|
0
|
push @a, $alpha; |
92
|
|
|
|
|
|
|
} |
93
|
|
|
|
|
|
|
|
94
|
|
|
|
|
|
|
# Encode using the DCT into DC (constant) and normalized AC (varying) terms |
95
|
|
|
|
|
|
|
my $encode_channel = sub { |
96
|
0
|
|
|
0
|
|
0
|
my ($channel, $nx, $ny) = @_; |
97
|
0
|
|
|
|
|
0
|
my $dc = 0; |
98
|
0
|
|
|
|
|
0
|
my @ac; |
99
|
0
|
|
|
|
|
0
|
my $scale = 0; |
100
|
0
|
|
|
|
|
0
|
for my $cy (0 .. $ny - 1) { |
101
|
0
|
|
|
|
|
0
|
for (my $cx = 0; $cx * $ny < $nx * ($ny - $cy); $cx++) { |
102
|
0
|
|
|
|
|
0
|
my @fx = map cos(PI / $width * $cx * ($_ + 0.5)), 0 .. $width - 1; |
103
|
0
|
|
|
|
|
0
|
my $f = 0; |
104
|
0
|
|
|
|
|
0
|
for my $y (0 .. $height - 1) { |
105
|
0
|
|
|
|
|
0
|
my $fy = cos(PI / $height * $cy * ($y + 0.5)); |
106
|
0
|
|
|
|
|
0
|
for my $x (0 .. $width - 1) { |
107
|
0
|
|
|
|
|
0
|
$f += $channel->[$x + $y * $width] * $fx[$x] * $fy; |
108
|
|
|
|
|
|
|
} |
109
|
|
|
|
|
|
|
} |
110
|
0
|
|
|
|
|
0
|
$f /= $width * $height; |
111
|
0
|
0
|
0
|
|
|
0
|
if ($cx || $cy) { |
112
|
0
|
|
|
|
|
0
|
push @ac, $f; |
113
|
0
|
|
|
|
|
0
|
$scale = max $scale, abs $f; |
114
|
|
|
|
|
|
|
} else { |
115
|
0
|
|
|
|
|
0
|
$dc = $f; |
116
|
|
|
|
|
|
|
} |
117
|
|
|
|
|
|
|
} |
118
|
|
|
|
|
|
|
} |
119
|
0
|
0
|
|
|
|
0
|
if ($scale) { |
120
|
0
|
|
|
|
|
0
|
for my $ac (@ac) { |
121
|
0
|
|
|
|
|
0
|
$ac = 0.5 + 0.5 / $scale * $ac; |
122
|
|
|
|
|
|
|
} |
123
|
|
|
|
|
|
|
} |
124
|
0
|
|
|
|
|
0
|
($dc, \@ac, $scale) |
125
|
0
|
|
|
|
|
0
|
}; |
126
|
0
|
|
|
|
|
0
|
my ($l_dc, $l_ac, $l_scale) = $encode_channel->(\@l, max(3, $lx), max(3, $ly)); |
127
|
0
|
|
|
|
|
0
|
my ($p_dc, $p_ac, $p_scale) = $encode_channel->(\@p, 3, 3); |
128
|
0
|
|
|
|
|
0
|
my ($q_dc, $q_ac, $q_scale) = $encode_channel->(\@q, 3, 3); |
129
|
0
|
0
|
|
|
|
0
|
my ($a_dc, $a_ac, $a_scale) = $has_alpha ? $encode_channel->(\@a, 5, 5) : (1, [], 1); |
130
|
|
|
|
|
|
|
|
131
|
|
|
|
|
|
|
# Write the constants |
132
|
0
|
|
|
|
|
0
|
my $is_landscape = $width > $height; |
133
|
0
|
0
|
|
|
|
0
|
my $header24 = int(0.5 + 63 * $l_dc) |
134
|
|
|
|
|
|
|
| int(0.5 + 31.5 + 31.5 * $p_dc) << 6 |
135
|
|
|
|
|
|
|
| int(0.5 + 31.5 + 31.5 * $q_dc) << 12 |
136
|
|
|
|
|
|
|
| int(0.5 + 31 * $l_scale) << 18 |
137
|
|
|
|
|
|
|
| ($has_alpha ? 1 << 23 : 0); |
138
|
0
|
0
|
|
|
|
0
|
my $header16 = ($is_landscape ? $ly : $lx) |
|
|
0
|
|
|
|
|
|
139
|
|
|
|
|
|
|
| int(0.5 + 63 * $p_scale) << 3 |
140
|
|
|
|
|
|
|
| int(0.5 + 63 * $q_scale) << 9 |
141
|
|
|
|
|
|
|
| ($is_landscape ? 1 << 15 : 0); |
142
|
0
|
0
|
|
|
|
0
|
my $hash_const = pack 'C*', ( |
143
|
|
|
|
|
|
|
$header24 & 0xff, |
144
|
|
|
|
|
|
|
$header24 >> 8 & 0xff, |
145
|
|
|
|
|
|
|
$header24 >> 16, |
146
|
|
|
|
|
|
|
$header16 & 0xff, |
147
|
|
|
|
|
|
|
$header16 >> 8, |
148
|
|
|
|
|
|
|
$has_alpha |
149
|
|
|
|
|
|
|
? int(0.5 + 15 * $a_dc) | int(0.5 + 15 * $a_scale) << 4 |
150
|
|
|
|
|
|
|
: (), |
151
|
|
|
|
|
|
|
); |
152
|
|
|
|
|
|
|
|
153
|
|
|
|
|
|
|
# Write the varying factors |
154
|
0
|
|
|
|
|
0
|
my $ac_index = 0; |
155
|
0
|
|
|
|
|
0
|
my $hash_vary = ''; |
156
|
0
|
0
|
|
|
|
0
|
for my $ac ($l_ac, $p_ac, $q_ac, $has_alpha ? $a_ac : ()) { |
157
|
0
|
|
|
|
|
0
|
for my $f (@$ac) { |
158
|
0
|
|
|
|
|
0
|
vec($hash_vary, $ac_index++, 4) = int(0.5 + 15 * $f); |
159
|
|
|
|
|
|
|
} |
160
|
|
|
|
|
|
|
} |
161
|
|
|
|
|
|
|
|
162
|
0
|
|
|
|
|
0
|
$hash_const . $hash_vary |
163
|
|
|
|
|
|
|
} |
164
|
|
|
|
|
|
|
|
165
|
|
|
|
|
|
|
sub rgba_to_png { |
166
|
9
|
|
|
9
|
0
|
38
|
my ($width, $height, $rgba) = @_; |
167
|
9
|
|
|
|
|
28
|
_assert_w_h_rgba $width, $height, $rgba; |
168
|
|
|
|
|
|
|
|
169
|
9
|
|
|
|
|
19
|
my $row = $width * 4 + 1; |
170
|
9
|
|
|
|
|
20
|
my $idat = 6 + $height * (5 + $row); |
171
|
9
|
|
|
|
|
83
|
my @bytes = ( |
172
|
|
|
|
|
|
|
137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82, 0, 0, |
173
|
|
|
|
|
|
|
$width >> 8 & 0xff, $width & 0xff, 0, 0, $height >> 8 & 0xff, $height & 0xff, 8, 6, 0, 0, 0, 0, 0, 0, 0, |
174
|
|
|
|
|
|
|
$idat >> 24 & 0xff, $idat >> 16 & 0xff, $idat >> 8 & 0xff, $idat & 0xff, |
175
|
|
|
|
|
|
|
73, 68, 65, 84, 120, 1, |
176
|
|
|
|
|
|
|
); |
177
|
9
|
|
|
|
|
21
|
my $a = 1; |
178
|
9
|
|
|
|
|
23
|
my $b = 0; |
179
|
9
|
|
|
|
|
32
|
for my $y (0 .. $height - 1) { |
180
|
219
|
100
|
|
|
|
604
|
push @bytes, ( |
181
|
|
|
|
|
|
|
$y == $height - 1 ? 1 : 0, |
182
|
|
|
|
|
|
|
$row & 0xff, |
183
|
|
|
|
|
|
|
$row >> 8 & 0xff, |
184
|
|
|
|
|
|
|
$row & 0xff ^ 0xff, |
185
|
|
|
|
|
|
|
$row >> 8 & 0xff ^ 0xff, |
186
|
|
|
|
|
|
|
0, |
187
|
|
|
|
|
|
|
); |
188
|
219
|
|
|
|
|
293
|
$b = ($b + $a) % 65521; |
189
|
219
|
|
|
|
|
306
|
my $slice = ($row - 1) * $y; |
190
|
219
|
|
|
|
|
380
|
for my $i ($slice .. $slice + $row - 2) { |
191
|
28032
|
|
|
|
|
35426
|
my $u = vec $rgba, $i, 8; |
192
|
28032
|
|
|
|
|
36262
|
push @bytes, $u; |
193
|
28032
|
|
|
|
|
35465
|
$a = ($a + $u) % 65521; |
194
|
28032
|
|
|
|
|
39255
|
$b = ($b + $a) % 65521; |
195
|
|
|
|
|
|
|
} |
196
|
|
|
|
|
|
|
} |
197
|
9
|
|
|
|
|
122
|
push @bytes, ( |
198
|
|
|
|
|
|
|
$b >> 8, $b & 0xff, $a >> 8, $a & 0xff, 0, 0, 0, 0, |
199
|
|
|
|
|
|
|
0, 0, 0, 0, 73, 69, 78, 68, 174, 66, 96, 130, |
200
|
|
|
|
|
|
|
); |
201
|
9
|
|
|
|
|
38
|
my @table = ( |
202
|
|
|
|
|
|
|
0, 498536548, 997073096, 651767980, 1994146192, 1802195444, 1303535960, |
203
|
|
|
|
|
|
|
1342533948, 3988292384, 4027552580, 3604390888, 3412177804, 2607071920, |
204
|
|
|
|
|
|
|
2262029012, 2685067896, 3183342108, |
205
|
|
|
|
|
|
|
); |
206
|
9
|
|
|
|
|
40
|
for my $range ([12, 28], [37, 40 + $idat]) { |
207
|
18
|
|
|
|
|
37
|
my ($start, $end) = @$range; |
208
|
18
|
|
|
|
|
42
|
my $c = 0xffff_ffff; |
209
|
18
|
|
|
|
|
41
|
for my $i ($start .. $end) { |
210
|
29589
|
|
|
|
|
35201
|
$c ^= $bytes[$i]; |
211
|
29589
|
|
|
|
|
39418
|
$c = $c >> 4 ^ $table[$c & 0xf]; |
212
|
29589
|
|
|
|
|
42161
|
$c = $c >> 4 ^ $table[$c & 0xf]; |
213
|
|
|
|
|
|
|
} |
214
|
18
|
|
|
|
|
27
|
$c ^= 0xffff_ffff; |
215
|
18
|
|
|
|
|
46
|
$bytes[$end + 1] = $c >> 24 & 0xff; |
216
|
18
|
|
|
|
|
41
|
$bytes[$end + 2] = $c >> 16 & 0xff; |
217
|
18
|
|
|
|
|
29
|
$bytes[$end + 3] = $c >> 8 & 0xff; |
218
|
18
|
|
|
|
|
35
|
$bytes[$end + 4] = $c & 0xff; |
219
|
|
|
|
|
|
|
} |
220
|
9
|
|
|
|
|
1262
|
pack 'C*', @bytes |
221
|
|
|
|
|
|
|
} |
222
|
|
|
|
|
|
|
|
223
|
|
|
|
|
|
|
sub rgba_to_data_url { |
224
|
6
|
|
|
6
|
0
|
1967
|
my ($width, $height, $rgba) = @_; |
225
|
6
|
|
|
|
|
26
|
_assert_w_h_rgba $width, $height, $rgba; |
226
|
6
|
|
|
|
|
20
|
'data:image/png;base64,' . MIME::Base64::encode(rgba_to_png($width, $height, $rgba), '') |
227
|
|
|
|
|
|
|
} |
228
|
|
|
|
|
|
|
|
229
|
|
|
|
|
|
|
sub thumb_hash_to_rgba { |
230
|
8
|
|
|
8
|
0
|
7512
|
my ($hash) = @_; |
231
|
8
|
|
|
|
|
38
|
_assert_thumb_hash $hash; |
232
|
7
|
100
|
|
|
|
108
|
wantarray or croak "thumb_hash_to_rgba: must be called in list context"; |
233
|
|
|
|
|
|
|
|
234
|
|
|
|
|
|
|
# Read the constants |
235
|
6
|
|
|
|
|
26
|
my $header24 = vec($hash, 0, 8) | vec($hash, 1, 8) << 8 | vec($hash, 2, 8) << 16; |
236
|
6
|
|
|
|
|
17
|
my $header16 = vec($hash, 3, 8) | vec($hash, 4, 8) << 8; |
237
|
6
|
|
|
|
|
17
|
my $l_dc = ($header24 & 63) / 63; |
238
|
6
|
|
|
|
|
13
|
my $p_dc = ($header24 >> 6 & 63) / 31.5 - 1; |
239
|
6
|
|
|
|
|
13
|
my $q_dc = ($header24 >> 12 & 63) / 31.5 - 1; |
240
|
6
|
|
|
|
|
13
|
my $l_scale = ($header24 >> 18 & 31) / 31; |
241
|
6
|
|
|
|
|
11
|
my $has_alpha = $header24 >> 23; |
242
|
6
|
|
|
|
|
14
|
my $p_scale = ($header16 >> 3 & 63) / 63; |
243
|
6
|
|
|
|
|
16
|
my $q_scale = ($header16 >> 9 & 63) / 63; |
244
|
6
|
|
|
|
|
11
|
my $is_landscape = $header16 >> 15; |
245
|
6
|
100
|
|
|
|
28
|
my $l_max = $has_alpha ? 5 : 7; |
246
|
6
|
|
|
|
|
23
|
my $l_min = max(3, $header16 & 7); |
247
|
6
|
100
|
|
|
|
20
|
my ($lx, $ly) = $is_landscape |
248
|
|
|
|
|
|
|
? ($l_max, $l_min) |
249
|
|
|
|
|
|
|
: ($l_min, $l_max); |
250
|
6
|
100
|
|
|
|
27
|
my ($a_dc, $a_scale) = $has_alpha |
251
|
|
|
|
|
|
|
? (map vec($hash, $_, 4) / 15, |
252
|
|
|
|
|
|
|
10, 11) |
253
|
|
|
|
|
|
|
: (1, 1); |
254
|
|
|
|
|
|
|
|
255
|
|
|
|
|
|
|
# Read the varying factors (boost saturation by 1.25x to compensate for quantization) |
256
|
6
|
100
|
|
|
|
17
|
my $ac_index = $has_alpha ? 12 : 10; |
257
|
|
|
|
|
|
|
my $decode_channel = sub { |
258
|
20
|
|
|
20
|
|
39
|
my ($nx, $ny, $scale) = @_; |
259
|
20
|
|
|
|
|
33
|
my @ac; |
260
|
20
|
|
|
|
|
47
|
for my $cy (0 .. $ny - 1) { |
261
|
74
|
|
|
|
|
162
|
for (my $cx = !$cy; $cx * $ny < $nx * ($ny - $cy); $cx++) { |
262
|
196
|
|
|
|
|
508
|
push @ac, (vec($hash, $ac_index++, 4) / 7.5 - 1) * $scale; |
263
|
|
|
|
|
|
|
} |
264
|
|
|
|
|
|
|
} |
265
|
|
|
|
|
|
|
\@ac |
266
|
6
|
|
|
|
|
45
|
}; |
|
20
|
|
|
|
|
61
|
|
267
|
6
|
|
|
|
|
27
|
my $l_ac = $decode_channel->($lx, $ly, $l_scale); |
268
|
6
|
|
|
|
|
32
|
my $p_ac = $decode_channel->(3, 3, $p_scale * 1.25); |
269
|
6
|
|
|
|
|
25
|
my $q_ac = $decode_channel->(3, 3, $q_scale * 1.25); |
270
|
6
|
100
|
|
|
|
30
|
my $a_ac = $has_alpha ? $decode_channel->(5, 5, $a_scale) : []; |
271
|
|
|
|
|
|
|
|
272
|
|
|
|
|
|
|
# Decode using the DCT into RGB |
273
|
6
|
100
|
|
|
|
18
|
my $ratio = $is_landscape |
274
|
|
|
|
|
|
|
? $l_max / ($header16 & 7) |
275
|
|
|
|
|
|
|
: ($header16 & 7) / $l_max; |
276
|
6
|
100
|
|
|
|
33
|
my ($width, $height) = $ratio > 1 |
277
|
|
|
|
|
|
|
? (32, int(0.5 + 32 / $ratio)) |
278
|
|
|
|
|
|
|
: (int(0.5 + 32 * $ratio), 32); |
279
|
6
|
|
|
|
|
14
|
my $rgba = ''; |
280
|
6
|
|
|
|
|
11
|
my (@fx, @fy); |
281
|
6
|
|
|
|
|
13
|
for my $y (0 .. $height - 1) { |
282
|
146
|
|
|
|
|
317
|
for my $x (0 .. $width - 1) { |
283
|
4672
|
|
|
|
|
7038
|
my $l = $l_dc; |
284
|
4672
|
|
|
|
|
5727
|
my $p = $p_dc; |
285
|
4672
|
|
|
|
|
5905
|
my $q = $q_dc; |
286
|
4672
|
|
|
|
|
5728
|
my $a = $a_dc; |
287
|
|
|
|
|
|
|
|
288
|
|
|
|
|
|
|
# Precompute the coefficients |
289
|
4672
|
100
|
|
|
|
26116
|
my @fx = map cos(PI / $width * ($x + 0.5) * $_), 0 .. max($lx, $has_alpha ? 5 : 3) - 1; |
290
|
4672
|
100
|
|
|
|
22534
|
my @fy = map cos(PI / $height * ($y + 0.5) * $_), 0 .. max($ly, $has_alpha ? 5 : 3) - 1; |
291
|
|
|
|
|
|
|
|
292
|
|
|
|
|
|
|
# Decode L |
293
|
|
|
|
|
|
|
{ |
294
|
4672
|
|
|
|
|
5910
|
my $j = 0; |
295
|
4672
|
|
|
|
|
7454
|
for my $cy (0 .. $ly - 1) { |
296
|
22208
|
|
|
|
|
30234
|
my $fy2 = $fy[$cy] * 2; |
297
|
22208
|
|
|
|
|
42774
|
for (my $cx = !$cy; $cx * $ly < $lx * ($ly - $cy); $cx++) { |
298
|
81792
|
|
|
|
|
165105
|
$l += $l_ac->[$j++] * $fx[$cx] * $fy2; |
299
|
|
|
|
|
|
|
} |
300
|
|
|
|
|
|
|
} |
301
|
|
|
|
|
|
|
} |
302
|
|
|
|
|
|
|
|
303
|
|
|
|
|
|
|
# Decode P and Q |
304
|
|
|
|
|
|
|
{ |
305
|
4672
|
|
|
|
|
7392
|
my $j = 0; |
|
4672
|
|
|
|
|
5805
|
|
|
4672
|
|
|
|
|
5767
|
|
306
|
4672
|
|
|
|
|
6859
|
for my $cy (0 .. 2) { |
307
|
14016
|
|
|
|
|
18774
|
my $fy2 = $fy[$cy] * 2; |
308
|
14016
|
|
|
|
|
20659
|
for my $cx (!$cy .. 2 - $cy) { |
309
|
23360
|
|
|
|
|
31998
|
my $f = $fx[$cx] * $fy2; |
310
|
23360
|
|
|
|
|
30450
|
$p += $p_ac->[$j] * $f; |
311
|
23360
|
|
|
|
|
29586
|
$q += $q_ac->[$j] * $f; |
312
|
23360
|
|
|
|
|
34566
|
$j++; |
313
|
|
|
|
|
|
|
} |
314
|
|
|
|
|
|
|
} |
315
|
|
|
|
|
|
|
} |
316
|
|
|
|
|
|
|
|
317
|
|
|
|
|
|
|
# Decode A |
318
|
4672
|
100
|
|
|
|
8020
|
if ($has_alpha) { |
319
|
2048
|
|
|
|
|
2502
|
my $j = 0; |
320
|
2048
|
|
|
|
|
3010
|
for my $cy (0 .. 4) { |
321
|
10240
|
|
|
|
|
13376
|
my $fy2 = $fy[$cy] * 2; |
322
|
10240
|
|
|
|
|
14511
|
for my $cx (!$cy .. 4 - $cy) { |
323
|
28672
|
|
|
|
|
42734
|
$a += $a_ac->[$j++] * $fx[$cx] * $fy2; |
324
|
|
|
|
|
|
|
} |
325
|
|
|
|
|
|
|
} |
326
|
|
|
|
|
|
|
} |
327
|
|
|
|
|
|
|
|
328
|
|
|
|
|
|
|
# Convert to RGB |
329
|
4672
|
|
|
|
|
6595
|
my $b = $l - 2 / 3 * $p; |
330
|
4672
|
|
|
|
|
7012
|
my $r = (3 * $l - $b + $q) / 2; |
331
|
4672
|
|
|
|
|
6232
|
my $g = $r - $q; |
332
|
4672
|
|
|
|
|
27836
|
$rgba .= pack 'C*', map max(0, 255 * min(1, $_)), $r, $g, $b, $a; |
333
|
|
|
|
|
|
|
} |
334
|
|
|
|
|
|
|
} |
335
|
|
|
|
|
|
|
|
336
|
6
|
|
|
|
|
181
|
$width, $height, $rgba |
337
|
|
|
|
|
|
|
} |
338
|
|
|
|
|
|
|
|
339
|
|
|
|
|
|
|
sub thumb_hash_to_average_rgba { |
340
|
5
|
|
|
5
|
0
|
2926
|
my ($hash) = @_; |
341
|
5
|
|
|
|
|
17
|
_assert_thumb_hash $hash; |
342
|
4
|
100
|
|
|
|
87
|
wantarray or croak "thumb_hash_to_average_rgba: must be called in list context"; |
343
|
3
|
|
|
|
|
14
|
my $header = vec($hash, 0, 8) | vec($hash, 1, 8) << 8 | vec($hash, 2, 8) << 16; |
344
|
3
|
|
|
|
|
10
|
my $l = ($header & 63) / 63; |
345
|
3
|
|
|
|
|
11
|
my $p = ($header >> 6 & 63) / 31.5 - 1; |
346
|
3
|
|
|
|
|
8
|
my $q = ($header >> 12 & 63) / 31.5 - 1; |
347
|
3
|
|
|
|
|
6
|
my $has_alpha = $header >> 23; |
348
|
3
|
100
|
|
|
|
11
|
my $a = $has_alpha ? (vec($hash, 5, 8) & 15) / 15 : 1; |
349
|
3
|
|
|
|
|
9
|
my $b = $l - 2 / 3 * $p; |
350
|
3
|
|
|
|
|
10
|
my $r = (3 * $l - $b + $q) / 2; |
351
|
3
|
|
|
|
|
7
|
my $g = $r - $q; |
352
|
|
|
|
|
|
|
|
353
|
3
|
|
|
|
|
26
|
max(0, min(1, $r)), |
354
|
|
|
|
|
|
|
max(0, min(1, $g)), |
355
|
|
|
|
|
|
|
max(0, min(1, $b)), |
356
|
|
|
|
|
|
|
$a |
357
|
|
|
|
|
|
|
} |
358
|
|
|
|
|
|
|
|
359
|
|
|
|
|
|
|
sub thumb_hash_to_approximate_aspect_ratio { |
360
|
3
|
|
|
3
|
0
|
3356
|
my ($hash) = @_; |
361
|
3
|
|
|
|
|
10
|
_assert_thumb_hash $hash; |
362
|
3
|
|
|
|
|
13
|
my $has_alpha = vec($hash, 2, 8) & 0x80; |
363
|
3
|
|
|
|
|
7
|
my $is_landscape = vec($hash, 4, 8) & 0x80; |
364
|
3
|
100
|
|
|
|
9
|
my $l_max = $has_alpha ? 5 : 7; |
365
|
3
|
|
|
|
|
6
|
my $l_min = vec($hash, 3, 8) & 0x7; |
366
|
3
|
100
|
|
|
|
14
|
$is_landscape |
367
|
|
|
|
|
|
|
? $l_max / $l_min |
368
|
|
|
|
|
|
|
: $l_min / $l_max |
369
|
|
|
|
|
|
|
} |
370
|
|
|
|
|
|
|
|
371
|
|
|
|
|
|
|
sub thumb_hash_to_data_url { |
372
|
3
|
|
|
3
|
0
|
1556
|
my ($hash) = @_; |
373
|
3
|
|
|
|
|
13
|
_assert_thumb_hash $hash; |
374
|
3
|
|
|
|
|
11
|
rgba_to_data_url thumb_hash_to_rgba $hash |
375
|
|
|
|
|
|
|
} |
376
|
|
|
|
|
|
|
|
377
|
|
|
|
|
|
|
1 |
378
|
|
|
|
|
|
|
__END__ |