qr_reader 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +16 -0
- data/LICENSE.txt +21 -0
- data/NOTICE +14 -0
- data/README.md +222 -0
- data/exe/qr_reader +66 -0
- data/lib/qr_reader/binarizer.rb +170 -0
- data/lib/qr_reader/bit_matrix.rb +120 -0
- data/lib/qr_reader/decoder/bitstream_parser.rb +307 -0
- data/lib/qr_reader/decoder/character_set.rb +163 -0
- data/lib/qr_reader/decoder/codewords.rb +164 -0
- data/lib/qr_reader/decoder/data_mask.rb +57 -0
- data/lib/qr_reader/decoder/format_reader.rb +204 -0
- data/lib/qr_reader/decoder/function_patterns.rb +62 -0
- data/lib/qr_reader/decoder/galois_field.rb +69 -0
- data/lib/qr_reader/decoder/reed_solomon.rb +312 -0
- data/lib/qr_reader/decoder.rb +241 -0
- data/lib/qr_reader/detector/alignment_grid.rb +127 -0
- data/lib/qr_reader/detector/alignment_pattern_finder.rb +191 -0
- data/lib/qr_reader/detector/candidate/dimension.rb +237 -0
- data/lib/qr_reader/detector/candidate/strokes.rb +126 -0
- data/lib/qr_reader/detector/candidate.rb +436 -0
- data/lib/qr_reader/detector/finder_pattern.rb +63 -0
- data/lib/qr_reader/detector/finder_pattern_finder.rb +199 -0
- data/lib/qr_reader/detector/geometry.rb +118 -0
- data/lib/qr_reader/detector/grid_sampler.rb +350 -0
- data/lib/qr_reader/detector/pattern_sets.rb +235 -0
- data/lib/qr_reader/detector/perspective_transform.rb +117 -0
- data/lib/qr_reader/detector/pure_detector.rb +197 -0
- data/lib/qr_reader/detector.rb +215 -0
- data/lib/qr_reader/errors.rb +14 -0
- data/lib/qr_reader/format_information.rb +79 -0
- data/lib/qr_reader/formats/bmp.rb +434 -0
- data/lib/qr_reader/formats/ccitt.rb +81 -0
- data/lib/qr_reader/formats/gif.rb +272 -0
- data/lib/qr_reader/formats/jpeg.rb +152 -0
- data/lib/qr_reader/formats/pdf.rb +2935 -0
- data/lib/qr_reader/formats/png.rb +311 -0
- data/lib/qr_reader/formats/pnm.rb +286 -0
- data/lib/qr_reader/formats/tiff.rb +1345 -0
- data/lib/qr_reader/formats.rb +279 -0
- data/lib/qr_reader/image.rb +231 -0
- data/lib/qr_reader/kernels/binarizer.rb +475 -0
- data/lib/qr_reader/kernels/ccitt.rb +554 -0
- data/lib/qr_reader/kernels/detector.rb +587 -0
- data/lib/qr_reader/kernels/image.rb +65 -0
- data/lib/qr_reader/kernels/jpeg.rb +3421 -0
- data/lib/qr_reader/kernels/lzw.rb +173 -0
- data/lib/qr_reader/kernels/pixels.rb +116 -0
- data/lib/qr_reader/kernels/png.rb +1138 -0
- data/lib/qr_reader/kernels.rb +14 -0
- data/lib/qr_reader/native_kernels.rb +130 -0
- data/lib/qr_reader/reader.rb +348 -0
- data/lib/qr_reader/result.rb +49 -0
- data/lib/qr_reader/symbol_version.rb +173 -0
- data/lib/qr_reader/version.rb +5 -0
- data/lib/qr_reader.rb +77 -0
- metadata +103 -0
|
@@ -0,0 +1,1138 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../errors"
|
|
4
|
+
require_relative "pixels"
|
|
5
|
+
|
|
6
|
+
# PNG kernels: row unfiltering fused with the conversion to luminance.
|
|
7
|
+
# Plain Ruby in the subset Spinel compiles (see DESIGN.md, "Kernels").
|
|
8
|
+
#
|
|
9
|
+
# Rows are unfiltered into a fresh String per row (appending with String#<<
|
|
10
|
+
# is much cheaper than setbyte under YJIT). PNG filters work on bytes, each
|
|
11
|
+
# byte predicted from the same byte of the pixel to the left, above and
|
|
12
|
+
# above-left, so the bytes of every sample position form independent lanes:
|
|
13
|
+
# for 16-bit samples only the high-byte lanes are unfiltered (the low bytes
|
|
14
|
+
# never reach the luminance), which halves the work. 8 and 16-bit gray,
|
|
15
|
+
# gray+alpha, RGB and RGBA rows are unfiltered and converted in one pass by
|
|
16
|
+
# fused helpers (one per filter type and layout); rows whose filtered bytes
|
|
17
|
+
# are all zero with the Up filter (flat areas: margins, backgrounds) are
|
|
18
|
+
# the previous row again and cost a memcmp.
|
|
19
|
+
#
|
|
20
|
+
# Each helper is a small method called once per row: YJIT compiles methods,
|
|
21
|
+
# not loops, and keeping dispatch out of the loops means that one layout
|
|
22
|
+
# never makes another one's loop side-exit. Methods with a byte loop
|
|
23
|
+
# reference no constants and call nothing that does (tables are passed in
|
|
24
|
+
# as arguments, buffers come from png_buf): with YJIT (Ruby 4.0), when a
|
|
25
|
+
# constant cache in the loop method or in a method it calls is invalidated
|
|
26
|
+
# after compilation - e.g. Encoding::BINARY once any constant named BINARY
|
|
27
|
+
# is defined anywhere (decoder/character_set.rb, Zlib) - the loop keeps
|
|
28
|
+
# running at interpreter speed, 5-7x slower, for the rest of the process.
|
|
29
|
+
module QrReaderKernels
|
|
30
|
+
# Paeth predictor without branches: with da = b - c and db = a - c (a =
|
|
31
|
+
# left, b = up, c = up-left) the predictor is c + PNG_PAETH[...], where
|
|
32
|
+
# the table holds db (choose a), da (choose b) or 0 (choose c), indexed
|
|
33
|
+
# by (da + 255) * 511 + (db + 255). For fixed da the choice is a function
|
|
34
|
+
# of db made of a few intervals (derived from |da| <= |db|,
|
|
35
|
+
# |da| <= |da + db| and |db| <= |da + db|), so each row of the table is
|
|
36
|
+
# built from C-level fills instead of 261k iterations.
|
|
37
|
+
def self.png_paeth_table
|
|
38
|
+
base = Array.new(511, 0)
|
|
39
|
+
i = 0
|
|
40
|
+
while i < 511
|
|
41
|
+
base[i] = i - 255 # choose a: predictor offset db
|
|
42
|
+
i += 1
|
|
43
|
+
end
|
|
44
|
+
table = []
|
|
45
|
+
da = -255
|
|
46
|
+
while da <= 255
|
|
47
|
+
row = base.dup
|
|
48
|
+
if da > 0
|
|
49
|
+
# b for ceil(-d/2) <= db < d, c for -2d < db < ceil(-d/2)
|
|
50
|
+
split = -(da / 2)
|
|
51
|
+
png_paeth_fill(row, 0, -2 * da + 1, split - 1)
|
|
52
|
+
png_paeth_fill(row, da, split, da - 1)
|
|
53
|
+
elsif da < 0
|
|
54
|
+
# b for -d < db <= floor(d/2), c for floor(d/2) < db < 2d
|
|
55
|
+
d = -da
|
|
56
|
+
png_paeth_fill(row, da, -d + 1, d / 2)
|
|
57
|
+
png_paeth_fill(row, 0, d / 2 + 1, 2 * d - 1)
|
|
58
|
+
end
|
|
59
|
+
table.concat(row)
|
|
60
|
+
da += 1
|
|
61
|
+
end
|
|
62
|
+
table
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def self.png_paeth_fill(row, value, from, to)
|
|
66
|
+
from = -255 if from < -255
|
|
67
|
+
to = 255 if to > 255
|
|
68
|
+
row.fill(value, from + 255, to - from + 1) if to >= from
|
|
69
|
+
row
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Byte -> its 8, 4 or 2 samples of +depth+ bits (MSB first) as bytes.
|
|
73
|
+
def self.png_unpack_table(depth)
|
|
74
|
+
mask = (1 << depth) - 1
|
|
75
|
+
table = []
|
|
76
|
+
b = 0
|
|
77
|
+
while b < 256
|
|
78
|
+
s = png_buf(8)
|
|
79
|
+
shift = 8 - depth
|
|
80
|
+
while shift >= 0
|
|
81
|
+
s << ((b >> shift) & mask)
|
|
82
|
+
shift -= depth
|
|
83
|
+
end
|
|
84
|
+
table << s
|
|
85
|
+
b += 1
|
|
86
|
+
end
|
|
87
|
+
table
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# An empty binary String with room for +size+ bytes. (String.new without
|
|
91
|
+
# a source String is binary; naming Encoding::BINARY here would put a
|
|
92
|
+
# constant cache into every row helper's call path, see the module
|
|
93
|
+
# comment.)
|
|
94
|
+
def self.png_buf(size)
|
|
95
|
+
String.new(capacity: size)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
PNG_PAETH = png_paeth_table.freeze
|
|
99
|
+
PNG_UNPACK1 = png_unpack_table(1).freeze
|
|
100
|
+
PNG_UNPACK2 = png_unpack_table(2).freeze
|
|
101
|
+
PNG_UNPACK4 = png_unpack_table(4).freeze
|
|
102
|
+
# Adam7 passes: x0, y0, dx, dy.
|
|
103
|
+
PNG_ADAM7 = [0, 0, 8, 8, 4, 0, 8, 8, 0, 4, 4, 8, 2, 0, 4, 4, 0, 2, 2, 4, 1, 0, 2, 2, 0, 1, 1, 2].freeze
|
|
104
|
+
|
|
105
|
+
# Unfilters +rows+ rows of +width+ pixels (+channels+ samples of +depth+
|
|
106
|
+
# bits each), stored from byte +pos+ of +raw+ as a filter type byte
|
|
107
|
+
# followed by the filtered row, and converts them:
|
|
108
|
+
#
|
|
109
|
+
# * gray/palette of depth <= 8 (+channels+ 1): the unfiltered rows as
|
|
110
|
+
# they are, (width * depth + 7) / 8 bytes each (sample values, packed
|
|
111
|
+
# below 8 bits), for the caller to map;
|
|
112
|
+
# * otherwise: width bytes of luminance per row, alpha over white, 16-bit
|
|
113
|
+
# samples by their high byte. +key+ is the tRNS colour key as raw sample
|
|
114
|
+
# bytes (16-bit gray, 8/16-bit RGB): matching pixels become white; ""
|
|
115
|
+
# for none.
|
|
116
|
+
#
|
|
117
|
+
# +prev+ is the previous row as this kernel returned it ("" at the top of
|
|
118
|
+
# an image or pass). Returns the converted rows followed by the last
|
|
119
|
+
# unfiltered row (the +prev+ of the next call when an image is decoded in
|
|
120
|
+
# slices; its size depends on the layout).
|
|
121
|
+
# spinel-sample: QrReaderKernels.png_rows("\x01\x10\x20\x03\x01\x02".b, 0, 2, 2, 8, 1, "".b, "".b)
|
|
122
|
+
def self.png_rows(raw, pos, rows, width, depth, channels, prev, key)
|
|
123
|
+
t = PNG_PAETH
|
|
124
|
+
if channels == 1 && depth <= 8
|
|
125
|
+
png_rows_bytes(raw, pos, rows, (width * depth + 7) >> 3, prev, t)
|
|
126
|
+
elsif key.bytesize > 0
|
|
127
|
+
png_rows_keyed(raw, pos, rows, width, depth, channels, prev, key, t)
|
|
128
|
+
elsif channels == 2 || channels == 4
|
|
129
|
+
png_rows_alpha(raw, pos, rows, width, channels, depth >> 3, prev, t)
|
|
130
|
+
else
|
|
131
|
+
png_rows_luma(raw, pos, rows, width, channels, depth >> 3, prev, t)
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def self.png_check(raw, pos, rows, len)
|
|
136
|
+
raise QrReader::ImageError, "truncated PNG image data" if pos + rows * (len + 1) > raw.bytesize
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def self.png_bad_filter(filter)
|
|
140
|
+
raise QrReader::ImageError, "invalid PNG filter type #{filter}"
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# +len+ zero bytes.
|
|
144
|
+
def self.png_zeros(len)
|
|
145
|
+
"\0".b * len
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# True if the +len+ filtered bytes at +ri+ are all zero (cheap rejection
|
|
149
|
+
# first: noisy rows rarely start and end with zero).
|
|
150
|
+
def self.png_zero_row(raw, ri, len, zeros)
|
|
151
|
+
raw.getbyte(ri) == 0 && raw.getbyte(ri + len - 1) == 0 && raw.byteslice(ri, len) == zeros
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# Rows of +len+ one-byte samples (gray or palette, any depth <= 8).
|
|
155
|
+
def self.png_rows_bytes(raw, pos, rows, len, prev, t)
|
|
156
|
+
png_check(raw, pos, rows, len)
|
|
157
|
+
prev = png_zeros(len) if prev.bytesize < len
|
|
158
|
+
zeros = png_zeros(len)
|
|
159
|
+
out = png_buf(rows * len + len)
|
|
160
|
+
y = 0
|
|
161
|
+
while y < rows
|
|
162
|
+
filter = raw.getbyte(pos)
|
|
163
|
+
ri = pos + 1
|
|
164
|
+
cur =
|
|
165
|
+
if filter == 0
|
|
166
|
+
raw.byteslice(ri, len) || ""
|
|
167
|
+
elsif filter == 2 && png_zero_row(raw, ri, len, zeros)
|
|
168
|
+
prev
|
|
169
|
+
else
|
|
170
|
+
png_bad_filter(filter) if filter > 4
|
|
171
|
+
png_gray_row(raw, ri, prev, len, 1, filter, t)
|
|
172
|
+
end
|
|
173
|
+
out << cur
|
|
174
|
+
prev = cur
|
|
175
|
+
pos = ri + len
|
|
176
|
+
y += 1
|
|
177
|
+
end
|
|
178
|
+
out << prev
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# Rows of 8 or 16-bit (+sw+ = 1 or 2 bytes per sample) gray or RGB
|
|
182
|
+
# without colour key: luminance. +prev+ / the returned last row hold the
|
|
183
|
+
# high bytes only (width * channels bytes).
|
|
184
|
+
def self.png_rows_luma(raw, pos, rows, width, channels, sw, prev, t)
|
|
185
|
+
clen = width * channels
|
|
186
|
+
rlen = clen * sw
|
|
187
|
+
png_check(raw, pos, rows, rlen)
|
|
188
|
+
prev = png_zeros(clen) if prev.bytesize < clen
|
|
189
|
+
zeros = png_zeros(rlen)
|
|
190
|
+
out = png_buf(rows * width + clen)
|
|
191
|
+
prev_lum = "" # luminance of prev, computed when first needed
|
|
192
|
+
y = 0
|
|
193
|
+
while y < rows
|
|
194
|
+
filter = raw.getbyte(pos)
|
|
195
|
+
ri = pos + 1
|
|
196
|
+
if filter == 2 && png_zero_row(raw, ri, rlen, zeros)
|
|
197
|
+
cur = prev
|
|
198
|
+
prev_lum = png_luma(prev, width, channels) if prev_lum.bytesize == 0
|
|
199
|
+
lum = prev_lum
|
|
200
|
+
elsif filter == 0
|
|
201
|
+
cur = sw == 1 ? (raw.byteslice(ri, clen) || "") : px_gray_row(raw, ri, clen, 0, 2)
|
|
202
|
+
lum = png_luma(cur, width, channels)
|
|
203
|
+
else
|
|
204
|
+
png_bad_filter(filter) if filter > 4
|
|
205
|
+
cur = png_fused_row(raw, ri, prev, width, channels, sw, filter, t)
|
|
206
|
+
lum = channels == 1 ? cur : (cur.byteslice(clen, width) || "")
|
|
207
|
+
end
|
|
208
|
+
out << lum
|
|
209
|
+
prev = cur
|
|
210
|
+
prev_lum = lum
|
|
211
|
+
pos = ri + rlen
|
|
212
|
+
y += 1
|
|
213
|
+
end
|
|
214
|
+
out << (prev.byteslice(0, clen) || "")
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
# Rows of 8 or 16-bit gray+alpha or RGBA: luminance over white. Rows
|
|
218
|
+
# that are entirely opaque (alpha 255, the common case) are tracked as
|
|
219
|
+
# such: while the row above is opaque, the next one is unfiltered by the
|
|
220
|
+
# opaque helpers, which skip the alpha lane (it stays 255 exactly when its
|
|
221
|
+
# filtered bytes are 0, the first pixel aside) and keep the colour
|
|
222
|
+
# samples only. A row that turns out not to be opaque is redone by the
|
|
223
|
+
# full helpers, which report whether their row was opaque. The returned
|
|
224
|
+
# last row has width * channels bytes, or width * (channels - 1) when
|
|
225
|
+
# opaque (that is how +prev+ tells).
|
|
226
|
+
def self.png_rows_alpha(raw, pos, rows, width, channels, sw, prev, t)
|
|
227
|
+
clen = width * channels
|
|
228
|
+
olen = clen - width
|
|
229
|
+
rlen = clen * sw
|
|
230
|
+
png_check(raw, pos, rows, rlen)
|
|
231
|
+
opaque = prev.bytesize == olen
|
|
232
|
+
prev = png_zeros(clen) if !opaque && prev.bytesize < clen
|
|
233
|
+
zeros = png_zeros(rlen)
|
|
234
|
+
out = png_buf(rows * width + clen)
|
|
235
|
+
prev_lum = "" # luminance of prev, computed when first needed
|
|
236
|
+
y = 0
|
|
237
|
+
while y < rows
|
|
238
|
+
filter = raw.getbyte(pos)
|
|
239
|
+
ri = pos + 1
|
|
240
|
+
if filter == 2 && png_zero_row(raw, ri, rlen, zeros)
|
|
241
|
+
cur = prev
|
|
242
|
+
prev_lum = png_alpha_luma(prev, width, channels, opaque) if prev_lum.bytesize == 0
|
|
243
|
+
lum = prev_lum
|
|
244
|
+
elsif filter == 0
|
|
245
|
+
cur = sw == 1 ? (raw.byteslice(ri, clen) || "") : px_gray_row(raw, ri, clen, 0, 2)
|
|
246
|
+
lum = png_luma(cur, width, channels)
|
|
247
|
+
opaque = false
|
|
248
|
+
else
|
|
249
|
+
png_bad_filter(filter) if filter > 4
|
|
250
|
+
cur = ""
|
|
251
|
+
if opaque
|
|
252
|
+
cur = png_opaque_row(raw, ri, prev, width, channels, sw, filter, t)
|
|
253
|
+
if cur.bytesize == 0
|
|
254
|
+
prev = png_unopaque(prev, width, channels)
|
|
255
|
+
opaque = false
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
if opaque
|
|
259
|
+
lum = channels == 2 ? cur : (cur.byteslice(olen, width) || "")
|
|
260
|
+
else
|
|
261
|
+
cur = png_fused_row(raw, ri, prev, width, channels, sw, filter, t)
|
|
262
|
+
lum = cur.byteslice(clen, width) || ""
|
|
263
|
+
if cur.getbyte(clen + width) == 255
|
|
264
|
+
opaque = true
|
|
265
|
+
cur = channels == 2 ? lum : png_rgb_lanes(cur, width)
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
out << lum
|
|
270
|
+
prev = cur
|
|
271
|
+
prev_lum = lum
|
|
272
|
+
pos = ri + rlen
|
|
273
|
+
y += 1
|
|
274
|
+
end
|
|
275
|
+
out << (prev.byteslice(0, opaque ? olen : clen) || "")
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
# Luminance of a previous row (opaque: colour samples only).
|
|
279
|
+
def self.png_alpha_luma(prev, width, channels, opaque)
|
|
280
|
+
if opaque
|
|
281
|
+
channels == 2 ? (prev.byteslice(0, width) || "") : px_rgb_row(prev, 0, width, 0, 1, 2, 3)
|
|
282
|
+
else
|
|
283
|
+
png_luma(prev, width, channels)
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
# An opaque row (colour samples only) with its alpha samples (255) back.
|
|
288
|
+
def self.png_unopaque(prev, width, channels)
|
|
289
|
+
row = png_buf(width * channels)
|
|
290
|
+
k = 0
|
|
291
|
+
if channels == 2
|
|
292
|
+
while k < width
|
|
293
|
+
row << prev.getbyte(k) << 255
|
|
294
|
+
k += 1
|
|
295
|
+
end
|
|
296
|
+
else
|
|
297
|
+
stop = 3 * width
|
|
298
|
+
while k < stop
|
|
299
|
+
row << prev.getbyte(k) << prev.getbyte(k + 1) << prev.getbyte(k + 2) << 255
|
|
300
|
+
k += 3
|
|
301
|
+
end
|
|
302
|
+
end
|
|
303
|
+
row
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
# The RGB samples of an RGBA row.
|
|
307
|
+
def self.png_rgb_lanes(cur, width)
|
|
308
|
+
row = png_buf(3 * width)
|
|
309
|
+
k = 0
|
|
310
|
+
stop = 4 * width
|
|
311
|
+
while k < stop
|
|
312
|
+
row << cur.getbyte(k) << cur.getbyte(k + 1) << cur.getbyte(k + 2)
|
|
313
|
+
k += 4
|
|
314
|
+
end
|
|
315
|
+
row
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
# Rows with a tRNS colour key (16-bit gray, 8/16-bit RGB): the key
|
|
319
|
+
# compares whole samples, so every byte is unfiltered.
|
|
320
|
+
def self.png_rows_keyed(raw, pos, rows, width, depth, channels, prev, key, t)
|
|
321
|
+
len = (width * channels * depth + 7) >> 3
|
|
322
|
+
bpp = (channels * depth + 7) >> 3
|
|
323
|
+
png_check(raw, pos, rows, len)
|
|
324
|
+
prev = png_zeros(len) if prev.bytesize < len
|
|
325
|
+
out = png_buf(rows * width + len)
|
|
326
|
+
sw = depth >> 3
|
|
327
|
+
y = 0
|
|
328
|
+
while y < rows
|
|
329
|
+
filter = raw.getbyte(pos)
|
|
330
|
+
png_bad_filter(filter) if filter > 4
|
|
331
|
+
ri = pos + 1
|
|
332
|
+
cur = png_unfilter_row(raw, ri, prev, len, bpp, filter, t)
|
|
333
|
+
lum = channels == 1 ? px_gray_row(cur, 0, width, 0, sw) : px_rgb_row(cur, 0, width, 0, sw, 2 * sw, 3 * sw)
|
|
334
|
+
out << png_key_row(cur, lum, width, key)
|
|
335
|
+
prev = cur
|
|
336
|
+
pos = ri + len
|
|
337
|
+
y += 1
|
|
338
|
+
end
|
|
339
|
+
out << prev
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
# Luminance of a row of 8-bit samples (high bytes) of +channels+ each.
|
|
343
|
+
def self.png_luma(cur, width, channels)
|
|
344
|
+
case channels
|
|
345
|
+
when 1 then cur.byteslice(0, width) || ""
|
|
346
|
+
when 2 then px_gray_alpha_row(cur, 0, width, 0, 1, 2)
|
|
347
|
+
when 3 then px_rgb_row(cur, 0, width, 0, 1, 2, 3)
|
|
348
|
+
else px_rgba_row(cur, 0, width, 0, 1, 2, 3, 4)
|
|
349
|
+
end
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
# Decodes a whole Adam7-interlaced image: the seven passes stored one
|
|
353
|
+
# after the other in +raw+ (as png_rows reads them), then scattered into
|
|
354
|
+
# +width+ x +height+ output bytes (luminance or sample values, see
|
|
355
|
+
# png_rows; samples below 8 bits come out one per byte).
|
|
356
|
+
# spinel-sample: QrReaderKernels.png_adam7("\x00\x10".b, 1, 1, 8, 1, "".b)
|
|
357
|
+
def self.png_adam7(raw, width, height, depth, channels, key)
|
|
358
|
+
passes = png_buf(width * height)
|
|
359
|
+
starts = []
|
|
360
|
+
widths = []
|
|
361
|
+
pos = 0
|
|
362
|
+
p = 0
|
|
363
|
+
while p < 7
|
|
364
|
+
x0 = PNG_ADAM7[4 * p]
|
|
365
|
+
y0 = PNG_ADAM7[4 * p + 1]
|
|
366
|
+
dx = PNG_ADAM7[4 * p + 2]
|
|
367
|
+
dy = PNG_ADAM7[4 * p + 3]
|
|
368
|
+
pw = width > x0 ? (width - x0 + dx - 1) / dx : 0
|
|
369
|
+
ph = height > y0 ? (height - y0 + dy - 1) / dy : 0
|
|
370
|
+
pw = 0 if ph == 0
|
|
371
|
+
starts << passes.bytesize
|
|
372
|
+
widths << pw
|
|
373
|
+
if pw > 0
|
|
374
|
+
res = png_rows(raw, pos, ph, pw, depth, channels, "", key)
|
|
375
|
+
len = (pw * channels * depth + 7) >> 3
|
|
376
|
+
if channels == 1 && depth < 8
|
|
377
|
+
passes << png_unpack_rows(res, ph, pw, len, depth)
|
|
378
|
+
else
|
|
379
|
+
passes << (res.byteslice(0, pw * ph) || "")
|
|
380
|
+
end
|
|
381
|
+
pos += ph * (1 + len)
|
|
382
|
+
end
|
|
383
|
+
p += 1
|
|
384
|
+
end
|
|
385
|
+
out = png_buf(width * height)
|
|
386
|
+
y = 0
|
|
387
|
+
while y < height
|
|
388
|
+
if (y & 1) == 1
|
|
389
|
+
# odd rows are pass 7 rows, complete
|
|
390
|
+
start = starts[6] + (y >> 1) * width
|
|
391
|
+
out << (passes.byteslice(start, width) || "")
|
|
392
|
+
else
|
|
393
|
+
out << png_adam7_row(passes, starts, widths, y, width, PNG_ADAM7)
|
|
394
|
+
end
|
|
395
|
+
y += 1
|
|
396
|
+
end
|
|
397
|
+
out
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
# Samples of 1, 2 or 4 bits of +rows+ packed rows (+len+ bytes each)
|
|
401
|
+
# -> one byte per sample, +width+ per row.
|
|
402
|
+
# spinel-sample: QrReaderKernels.png_unpack_rows("\xE4\x1B".b, 2, 3, 1, 2)
|
|
403
|
+
def self.png_unpack_rows(packed, rows, width, len, depth)
|
|
404
|
+
table = depth == 1 ? PNG_UNPACK1 : (depth == 2 ? PNG_UNPACK2 : PNG_UNPACK4)
|
|
405
|
+
out = png_buf(rows * width)
|
|
406
|
+
y = 0
|
|
407
|
+
while y < rows
|
|
408
|
+
out << png_unpack_row(packed, y * len, width, depth, table)
|
|
409
|
+
y += 1
|
|
410
|
+
end
|
|
411
|
+
out
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
def self.png_unpack_row(packed, off, width, depth, table)
|
|
415
|
+
per = 8 / depth
|
|
416
|
+
full = width / per
|
|
417
|
+
row = png_buf(width + per)
|
|
418
|
+
i = off
|
|
419
|
+
stop = off + full
|
|
420
|
+
while i < stop
|
|
421
|
+
row << table[packed.getbyte(i)]
|
|
422
|
+
i += 1
|
|
423
|
+
end
|
|
424
|
+
rest = width - full * per
|
|
425
|
+
row << (table[packed.getbyte(stop)].byteslice(0, rest) || "") if rest > 0
|
|
426
|
+
row
|
|
427
|
+
end
|
|
428
|
+
|
|
429
|
+
# Even output row +y+ of an interlaced image from the decoded passes.
|
|
430
|
+
def self.png_adam7_row(passes, starts, widths, y, width, a7)
|
|
431
|
+
row = png_zeros(width)
|
|
432
|
+
p = 0
|
|
433
|
+
while p < 6
|
|
434
|
+
pw = widths[p]
|
|
435
|
+
y0 = a7[4 * p + 1]
|
|
436
|
+
dy = a7[4 * p + 3]
|
|
437
|
+
if pw > 0 && y >= y0 && (y - y0) % dy == 0
|
|
438
|
+
png_scatter(row, passes, starts[p] + (y - y0) / dy * pw, pw, a7[4 * p], a7[4 * p + 2])
|
|
439
|
+
end
|
|
440
|
+
p += 1
|
|
441
|
+
end
|
|
442
|
+
row
|
|
443
|
+
end
|
|
444
|
+
|
|
445
|
+
# Copies +n+ bytes from +src+ at +from+ to every +dx+-th byte of +row+
|
|
446
|
+
# starting at +x+ (+row+ is the caller's own String).
|
|
447
|
+
def self.png_scatter(row, src, from, n, x, dx)
|
|
448
|
+
stop = from + n
|
|
449
|
+
while from < stop
|
|
450
|
+
row.setbyte(x, src.getbyte(from))
|
|
451
|
+
x += dx
|
|
452
|
+
from += 1
|
|
453
|
+
end
|
|
454
|
+
row
|
|
455
|
+
end
|
|
456
|
+
|
|
457
|
+
# --- generic unfiltering (any pixel size, all bytes) -----------------------
|
|
458
|
+
|
|
459
|
+
# The unfiltered row of +len+ bytes.
|
|
460
|
+
def self.png_unfilter_row(raw, ri, prev, len, bpp, filter, t)
|
|
461
|
+
case filter
|
|
462
|
+
when 0 then raw.byteslice(ri, len) || ""
|
|
463
|
+
when 1 then png_sub(raw, ri, len, bpp)
|
|
464
|
+
when 2 then png_g_up(raw, ri, prev, len, 1)
|
|
465
|
+
when 3 then png_avg(raw, ri, prev, len, bpp)
|
|
466
|
+
else png_paeth(raw, ri, prev, len, bpp, t)
|
|
467
|
+
end
|
|
468
|
+
end
|
|
469
|
+
|
|
470
|
+
# +lum+ with the pixels of +cur+ that equal the colour +key+ made white.
|
|
471
|
+
def self.png_key_row(cur, lum, width, key)
|
|
472
|
+
size = key.bytesize
|
|
473
|
+
row = png_buf(width)
|
|
474
|
+
i = 0
|
|
475
|
+
o = 0
|
|
476
|
+
while i < width
|
|
477
|
+
j = 0
|
|
478
|
+
while j < size && cur.getbyte(o + j) == key.getbyte(j)
|
|
479
|
+
j += 1
|
|
480
|
+
end
|
|
481
|
+
row << (j == size ? 255 : lum.getbyte(i))
|
|
482
|
+
o += size
|
|
483
|
+
i += 1
|
|
484
|
+
end
|
|
485
|
+
row
|
|
486
|
+
end
|
|
487
|
+
|
|
488
|
+
def self.png_sub(raw, ri, len, bpp)
|
|
489
|
+
# (A byteslice of +raw+ would be a UTF-8 String in the native build,
|
|
490
|
+
# where << then appends characters, not bytes: build a binary one.)
|
|
491
|
+
cur = png_buf(len)
|
|
492
|
+
k = 0
|
|
493
|
+
while k < bpp && k < len
|
|
494
|
+
cur << raw.getbyte(ri + k)
|
|
495
|
+
k += 1
|
|
496
|
+
end
|
|
497
|
+
while k < len
|
|
498
|
+
cur << ((raw.getbyte(ri + k) + cur.getbyte(k - bpp)) & 0xff)
|
|
499
|
+
k += 1
|
|
500
|
+
end
|
|
501
|
+
cur
|
|
502
|
+
end
|
|
503
|
+
|
|
504
|
+
def self.png_avg(raw, ri, prev, len, bpp)
|
|
505
|
+
cur = png_buf(len)
|
|
506
|
+
k = 0
|
|
507
|
+
while k < bpp && k < len
|
|
508
|
+
cur << ((raw.getbyte(ri + k) + (prev.getbyte(k) >> 1)) & 0xff)
|
|
509
|
+
k += 1
|
|
510
|
+
end
|
|
511
|
+
while k < len
|
|
512
|
+
cur << ((raw.getbyte(ri + k) + ((cur.getbyte(k - bpp) + prev.getbyte(k)) >> 1)) & 0xff)
|
|
513
|
+
k += 1
|
|
514
|
+
end
|
|
515
|
+
cur
|
|
516
|
+
end
|
|
517
|
+
|
|
518
|
+
def self.png_paeth(raw, ri, prev, len, bpp, t)
|
|
519
|
+
cur = png_buf(len)
|
|
520
|
+
k = 0
|
|
521
|
+
while k < bpp && k < len
|
|
522
|
+
cur << ((raw.getbyte(ri + k) + prev.getbyte(k)) & 0xff)
|
|
523
|
+
k += 1
|
|
524
|
+
end
|
|
525
|
+
while k < len
|
|
526
|
+
c = prev.getbyte(k - bpp)
|
|
527
|
+
cur << ((raw.getbyte(ri + k) + c + t[(prev.getbyte(k) - c) * 511 + cur.getbyte(k - bpp) - c + 130_560]) & 0xff)
|
|
528
|
+
k += 1
|
|
529
|
+
end
|
|
530
|
+
cur
|
|
531
|
+
end
|
|
532
|
+
|
|
533
|
+
# --- fused: unfilter (+ luminance) per layout --------------------------------
|
|
534
|
+
# Arguments: the filtered row starts at byte +i+ of +raw+, +n+ pixels,
|
|
535
|
+
# +sw+ bytes per sample (only the first, high byte of each is read);
|
|
536
|
+
# +prev+ holds the previous row's (high) sample bytes. Gray helpers return
|
|
537
|
+
# the unfiltered row (which is its luminance); the others return the
|
|
538
|
+
# unfiltered row followed by the row's luminance, and those with alpha
|
|
539
|
+
# then one more byte: 255 if every alpha sample was 255. (Dispatchers return a
|
|
540
|
+
# String on every branch, never nil: in the native build a nilable String
|
|
541
|
+
# would make every byte loop that reads it polymorphic.)
|
|
542
|
+
|
|
543
|
+
def self.png_fused_row(raw, i, prev, n, channels, sw, filter, t)
|
|
544
|
+
case channels
|
|
545
|
+
when 1 then png_gray_row(raw, i, prev, n, sw, filter, t)
|
|
546
|
+
when 2
|
|
547
|
+
case filter
|
|
548
|
+
when 1 then png_ga_sub(raw, i, n, sw)
|
|
549
|
+
when 2 then png_ga_up(raw, i, prev, n, sw)
|
|
550
|
+
when 3 then png_ga_avg(raw, i, prev, n, sw)
|
|
551
|
+
else png_ga_paeth(raw, i, prev, n, sw, t)
|
|
552
|
+
end
|
|
553
|
+
when 3
|
|
554
|
+
case filter
|
|
555
|
+
when 1 then png_rgb_sub(raw, i, n, sw)
|
|
556
|
+
when 2 then png_rgb_up(raw, i, prev, n, sw)
|
|
557
|
+
when 3 then png_rgb_avg(raw, i, prev, n, sw)
|
|
558
|
+
else png_rgb_paeth(raw, i, prev, n, sw, t)
|
|
559
|
+
end
|
|
560
|
+
else
|
|
561
|
+
case filter
|
|
562
|
+
when 1 then png_rgba_sub(raw, i, n, sw)
|
|
563
|
+
when 2 then png_rgba_up(raw, i, prev, n, sw)
|
|
564
|
+
when 3 then png_rgba_avg(raw, i, prev, n, sw)
|
|
565
|
+
else png_rgba_paeth(raw, i, prev, n, sw, t)
|
|
566
|
+
end
|
|
567
|
+
end
|
|
568
|
+
end
|
|
569
|
+
|
|
570
|
+
# One gray sample per pixel (also packed low-depth and palette rows,
|
|
571
|
+
# byte by byte: +n+ bytes, +sw+ 1). Average and Paeth, the filters that
|
|
572
|
+
# noisy scans use most, do two bytes per iteration when +sw+ is 1 (10%
|
|
573
|
+
# faster with YJIT).
|
|
574
|
+
def self.png_gray_row(raw, i, prev, n, sw, filter, t)
|
|
575
|
+
case filter
|
|
576
|
+
when 1 then png_g_sub(raw, i, n, sw)
|
|
577
|
+
when 2 then png_g_up(raw, i, prev, n, sw)
|
|
578
|
+
when 3 then png_g_avg(raw, i, prev, n, sw)
|
|
579
|
+
else png_g_paeth(raw, i, prev, n, sw, t)
|
|
580
|
+
end
|
|
581
|
+
end
|
|
582
|
+
|
|
583
|
+
def self.png_g_sub(raw, i, n, sw)
|
|
584
|
+
cur = png_buf(n)
|
|
585
|
+
v = 0
|
|
586
|
+
stop = i + n * sw
|
|
587
|
+
while i < stop
|
|
588
|
+
v = (raw.getbyte(i) + v) & 0xff
|
|
589
|
+
cur << v
|
|
590
|
+
i += sw
|
|
591
|
+
end
|
|
592
|
+
cur
|
|
593
|
+
end
|
|
594
|
+
|
|
595
|
+
def self.png_g_up(raw, i, prev, n, sw)
|
|
596
|
+
cur = png_buf(n)
|
|
597
|
+
k = 0
|
|
598
|
+
while k < n
|
|
599
|
+
cur << ((raw.getbyte(i) + prev.getbyte(k)) & 0xff)
|
|
600
|
+
i += sw
|
|
601
|
+
k += 1
|
|
602
|
+
end
|
|
603
|
+
cur
|
|
604
|
+
end
|
|
605
|
+
|
|
606
|
+
def self.png_g_avg(raw, i, prev, n, sw)
|
|
607
|
+
cur = png_buf(n)
|
|
608
|
+
v = 0
|
|
609
|
+
k = 0
|
|
610
|
+
if sw == 1
|
|
611
|
+
stop = n - 1
|
|
612
|
+
while k < stop
|
|
613
|
+
v = (raw.getbyte(i) + ((v + prev.getbyte(k)) >> 1)) & 0xff
|
|
614
|
+
w = (raw.getbyte(i + 1) + ((v + prev.getbyte(k + 1)) >> 1)) & 0xff
|
|
615
|
+
cur << v << w
|
|
616
|
+
v = w
|
|
617
|
+
i += 2
|
|
618
|
+
k += 2
|
|
619
|
+
end
|
|
620
|
+
end
|
|
621
|
+
while k < n
|
|
622
|
+
v = (raw.getbyte(i) + ((v + prev.getbyte(k)) >> 1)) & 0xff
|
|
623
|
+
cur << v
|
|
624
|
+
i += sw
|
|
625
|
+
k += 1
|
|
626
|
+
end
|
|
627
|
+
cur
|
|
628
|
+
end
|
|
629
|
+
|
|
630
|
+
def self.png_g_paeth(raw, i, prev, n, sw, t)
|
|
631
|
+
cur = png_buf(n)
|
|
632
|
+
v = c = 0
|
|
633
|
+
k = 0
|
|
634
|
+
if sw == 1
|
|
635
|
+
stop = n - 1
|
|
636
|
+
while k < stop
|
|
637
|
+
u = prev.getbyte(k)
|
|
638
|
+
v = (raw.getbyte(i) + c + t[(u - c) * 511 + v - c + 130_560]) & 0xff
|
|
639
|
+
c = prev.getbyte(k + 1)
|
|
640
|
+
w = (raw.getbyte(i + 1) + u + t[(c - u) * 511 + v - u + 130_560]) & 0xff
|
|
641
|
+
cur << v << w
|
|
642
|
+
v = w
|
|
643
|
+
i += 2
|
|
644
|
+
k += 2
|
|
645
|
+
end
|
|
646
|
+
end
|
|
647
|
+
while k < n
|
|
648
|
+
u = prev.getbyte(k)
|
|
649
|
+
v = (raw.getbyte(i) + c + t[(u - c) * 511 + v - c + 130_560]) & 0xff
|
|
650
|
+
cur << v
|
|
651
|
+
c = u
|
|
652
|
+
i += sw
|
|
653
|
+
k += 1
|
|
654
|
+
end
|
|
655
|
+
cur
|
|
656
|
+
end
|
|
657
|
+
|
|
658
|
+
# --- opaque rows: colour lanes only ----------------------------------------
|
|
659
|
+
# The row above is opaque and given by its colour samples. Each helper
|
|
660
|
+
# returns "" as soon as an alpha byte shows that this row is not opaque
|
|
661
|
+
# (alpha 255 needs filtered alpha 0, except for the first pixel: 255 for
|
|
662
|
+
# Sub, whose left neighbour is 0, and 128 for Average, (0 + 255) >> 1 =
|
|
663
|
+
# 127), otherwise the gray samples (= the luminance) or the RGB samples
|
|
664
|
+
# followed by the luminance.
|
|
665
|
+
|
|
666
|
+
def self.png_opaque_row(raw, i, prev, n, channels, sw, filter, t)
|
|
667
|
+
if channels == 2
|
|
668
|
+
case filter
|
|
669
|
+
when 1 then png_gao_sub(raw, i, n, sw)
|
|
670
|
+
when 2 then png_gao_up(raw, i, prev, n, sw)
|
|
671
|
+
when 3 then png_gao_avg(raw, i, prev, n, sw)
|
|
672
|
+
else png_gao_paeth(raw, i, prev, n, sw, t)
|
|
673
|
+
end
|
|
674
|
+
else
|
|
675
|
+
case filter
|
|
676
|
+
when 1 then png_rgbo_sub(raw, i, n, sw)
|
|
677
|
+
when 2 then png_rgbo_up(raw, i, prev, n, sw)
|
|
678
|
+
when 3 then png_rgbo_avg(raw, i, prev, n, sw)
|
|
679
|
+
else png_rgbo_paeth(raw, i, prev, n, sw, t)
|
|
680
|
+
end
|
|
681
|
+
end
|
|
682
|
+
end
|
|
683
|
+
|
|
684
|
+
def self.png_gao_sub(raw, i, n, sw)
|
|
685
|
+
return "" if raw.getbyte(i + sw) != 255
|
|
686
|
+
|
|
687
|
+
cur = png_buf(n)
|
|
688
|
+
v = raw.getbyte(i)
|
|
689
|
+
cur << v
|
|
690
|
+
step = 2 * sw
|
|
691
|
+
i += step
|
|
692
|
+
stop = i + (n - 1) * step
|
|
693
|
+
while i < stop
|
|
694
|
+
return "" if raw.getbyte(i + sw) != 0
|
|
695
|
+
|
|
696
|
+
v = (raw.getbyte(i) + v) & 0xff
|
|
697
|
+
cur << v
|
|
698
|
+
i += step
|
|
699
|
+
end
|
|
700
|
+
cur
|
|
701
|
+
end
|
|
702
|
+
|
|
703
|
+
def self.png_gao_up(raw, i, prev, n, sw)
|
|
704
|
+
cur = png_buf(n)
|
|
705
|
+
step = 2 * sw
|
|
706
|
+
k = 0
|
|
707
|
+
while k < n
|
|
708
|
+
return "" if raw.getbyte(i + sw) != 0
|
|
709
|
+
|
|
710
|
+
cur << ((raw.getbyte(i) + prev.getbyte(k)) & 0xff)
|
|
711
|
+
i += step
|
|
712
|
+
k += 1
|
|
713
|
+
end
|
|
714
|
+
cur
|
|
715
|
+
end
|
|
716
|
+
|
|
717
|
+
def self.png_gao_avg(raw, i, prev, n, sw)
|
|
718
|
+
return "" if raw.getbyte(i + sw) != 128
|
|
719
|
+
|
|
720
|
+
cur = png_buf(n)
|
|
721
|
+
v = (raw.getbyte(i) + (prev.getbyte(0) >> 1)) & 0xff
|
|
722
|
+
cur << v
|
|
723
|
+
step = 2 * sw
|
|
724
|
+
i += step
|
|
725
|
+
k = 1
|
|
726
|
+
while k < n
|
|
727
|
+
return "" if raw.getbyte(i + sw) != 0
|
|
728
|
+
|
|
729
|
+
v = (raw.getbyte(i) + ((v + prev.getbyte(k)) >> 1)) & 0xff
|
|
730
|
+
cur << v
|
|
731
|
+
i += step
|
|
732
|
+
k += 1
|
|
733
|
+
end
|
|
734
|
+
cur
|
|
735
|
+
end
|
|
736
|
+
|
|
737
|
+
def self.png_gao_paeth(raw, i, prev, n, sw, t)
|
|
738
|
+
cur = png_buf(n)
|
|
739
|
+
v = c = 0
|
|
740
|
+
step = 2 * sw
|
|
741
|
+
k = 0
|
|
742
|
+
while k < n
|
|
743
|
+
return "" if raw.getbyte(i + sw) != 0
|
|
744
|
+
|
|
745
|
+
u = prev.getbyte(k)
|
|
746
|
+
v = (raw.getbyte(i) + c + t[(u - c) * 511 + v - c + 130_560]) & 0xff
|
|
747
|
+
cur << v
|
|
748
|
+
c = u
|
|
749
|
+
i += step
|
|
750
|
+
k += 1
|
|
751
|
+
end
|
|
752
|
+
cur
|
|
753
|
+
end
|
|
754
|
+
|
|
755
|
+
def self.png_rgbo_sub(raw, i, n, sw)
|
|
756
|
+
s2 = 2 * sw
|
|
757
|
+
s3 = 3 * sw
|
|
758
|
+
return "" if raw.getbyte(i + s3) != 255
|
|
759
|
+
|
|
760
|
+
cur = png_buf(4 * n)
|
|
761
|
+
lum = png_buf(n)
|
|
762
|
+
r = raw.getbyte(i)
|
|
763
|
+
g = raw.getbyte(i + sw)
|
|
764
|
+
b = raw.getbyte(i + s2)
|
|
765
|
+
cur << r << g << b
|
|
766
|
+
lum << ((306 * r + 601 * g + 117 * b + 0x200) >> 10)
|
|
767
|
+
step = 4 * sw
|
|
768
|
+
i += step
|
|
769
|
+
stop = i + (n - 1) * step
|
|
770
|
+
while i < stop
|
|
771
|
+
return "" if raw.getbyte(i + s3) != 0
|
|
772
|
+
|
|
773
|
+
r = (raw.getbyte(i) + r) & 0xff
|
|
774
|
+
g = (raw.getbyte(i + sw) + g) & 0xff
|
|
775
|
+
b = (raw.getbyte(i + s2) + b) & 0xff
|
|
776
|
+
cur << r << g << b
|
|
777
|
+
lum << ((306 * r + 601 * g + 117 * b + 0x200) >> 10)
|
|
778
|
+
i += step
|
|
779
|
+
end
|
|
780
|
+
cur << lum
|
|
781
|
+
end
|
|
782
|
+
|
|
783
|
+
def self.png_rgbo_up(raw, i, prev, n, sw)
|
|
784
|
+
cur = png_buf(4 * n)
|
|
785
|
+
lum = png_buf(n)
|
|
786
|
+
s2 = 2 * sw
|
|
787
|
+
s3 = 3 * sw
|
|
788
|
+
step = 4 * sw
|
|
789
|
+
k = 0
|
|
790
|
+
stop = 3 * n
|
|
791
|
+
while k < stop
|
|
792
|
+
return "" if raw.getbyte(i + s3) != 0
|
|
793
|
+
|
|
794
|
+
r = (raw.getbyte(i) + prev.getbyte(k)) & 0xff
|
|
795
|
+
g = (raw.getbyte(i + sw) + prev.getbyte(k + 1)) & 0xff
|
|
796
|
+
b = (raw.getbyte(i + s2) + prev.getbyte(k + 2)) & 0xff
|
|
797
|
+
cur << r << g << b
|
|
798
|
+
lum << ((306 * r + 601 * g + 117 * b + 0x200) >> 10)
|
|
799
|
+
i += step
|
|
800
|
+
k += 3
|
|
801
|
+
end
|
|
802
|
+
cur << lum
|
|
803
|
+
end
|
|
804
|
+
|
|
805
|
+
def self.png_rgbo_avg(raw, i, prev, n, sw)
|
|
806
|
+
s2 = 2 * sw
|
|
807
|
+
s3 = 3 * sw
|
|
808
|
+
return "" if raw.getbyte(i + s3) != 128
|
|
809
|
+
|
|
810
|
+
cur = png_buf(4 * n)
|
|
811
|
+
lum = png_buf(n)
|
|
812
|
+
r = (raw.getbyte(i) + (prev.getbyte(0) >> 1)) & 0xff
|
|
813
|
+
g = (raw.getbyte(i + sw) + (prev.getbyte(1) >> 1)) & 0xff
|
|
814
|
+
b = (raw.getbyte(i + s2) + (prev.getbyte(2) >> 1)) & 0xff
|
|
815
|
+
cur << r << g << b
|
|
816
|
+
lum << ((306 * r + 601 * g + 117 * b + 0x200) >> 10)
|
|
817
|
+
step = 4 * sw
|
|
818
|
+
i += step
|
|
819
|
+
k = 3
|
|
820
|
+
stop = 3 * n
|
|
821
|
+
while k < stop
|
|
822
|
+
return "" if raw.getbyte(i + s3) != 0
|
|
823
|
+
|
|
824
|
+
r = (raw.getbyte(i) + ((r + prev.getbyte(k)) >> 1)) & 0xff
|
|
825
|
+
g = (raw.getbyte(i + sw) + ((g + prev.getbyte(k + 1)) >> 1)) & 0xff
|
|
826
|
+
b = (raw.getbyte(i + s2) + ((b + prev.getbyte(k + 2)) >> 1)) & 0xff
|
|
827
|
+
cur << r << g << b
|
|
828
|
+
lum << ((306 * r + 601 * g + 117 * b + 0x200) >> 10)
|
|
829
|
+
i += step
|
|
830
|
+
k += 3
|
|
831
|
+
end
|
|
832
|
+
cur << lum
|
|
833
|
+
end
|
|
834
|
+
|
|
835
|
+
def self.png_rgbo_paeth(raw, i, prev, n, sw, t)
|
|
836
|
+
cur = png_buf(4 * n)
|
|
837
|
+
lum = png_buf(n)
|
|
838
|
+
r = g = b = cr = cg = cb = 0
|
|
839
|
+
s2 = 2 * sw
|
|
840
|
+
s3 = 3 * sw
|
|
841
|
+
step = 4 * sw
|
|
842
|
+
k = 0
|
|
843
|
+
stop = 3 * n
|
|
844
|
+
while k < stop
|
|
845
|
+
return "" if raw.getbyte(i + s3) != 0
|
|
846
|
+
|
|
847
|
+
u = prev.getbyte(k)
|
|
848
|
+
r = (raw.getbyte(i) + cr + t[(u - cr) * 511 + r - cr + 130_560]) & 0xff
|
|
849
|
+
cr = u
|
|
850
|
+
u = prev.getbyte(k + 1)
|
|
851
|
+
g = (raw.getbyte(i + sw) + cg + t[(u - cg) * 511 + g - cg + 130_560]) & 0xff
|
|
852
|
+
cg = u
|
|
853
|
+
u = prev.getbyte(k + 2)
|
|
854
|
+
b = (raw.getbyte(i + s2) + cb + t[(u - cb) * 511 + b - cb + 130_560]) & 0xff
|
|
855
|
+
cb = u
|
|
856
|
+
cur << r << g << b
|
|
857
|
+
lum << ((306 * r + 601 * g + 117 * b + 0x200) >> 10)
|
|
858
|
+
i += step
|
|
859
|
+
k += 3
|
|
860
|
+
end
|
|
861
|
+
cur << lum
|
|
862
|
+
end
|
|
863
|
+
|
|
864
|
+
# --- gray + alpha -------------------------------------------------------------
|
|
865
|
+
|
|
866
|
+
def self.png_ga_sub(raw, i, n, sw)
|
|
867
|
+
cur = png_buf(3 * n + 1)
|
|
868
|
+
lum = png_buf(n)
|
|
869
|
+
m = 255
|
|
870
|
+
v = a = 0
|
|
871
|
+
step = 2 * sw
|
|
872
|
+
stop = i + n * step
|
|
873
|
+
while i < stop
|
|
874
|
+
v = (raw.getbyte(i) + v) & 0xff
|
|
875
|
+
a = (raw.getbyte(i + sw) + a) & 0xff
|
|
876
|
+
cur << v << a
|
|
877
|
+
lum << ((((v - 255) * a + 65_152) * 0x8081) >> 23)
|
|
878
|
+
m &= a
|
|
879
|
+
i += step
|
|
880
|
+
end
|
|
881
|
+
cur << lum << m
|
|
882
|
+
end
|
|
883
|
+
|
|
884
|
+
def self.png_ga_up(raw, i, prev, n, sw)
|
|
885
|
+
cur = png_buf(3 * n + 1)
|
|
886
|
+
lum = png_buf(n)
|
|
887
|
+
m = 255
|
|
888
|
+
step = 2 * sw
|
|
889
|
+
k = 0
|
|
890
|
+
stop = 2 * n
|
|
891
|
+
while k < stop
|
|
892
|
+
v = (raw.getbyte(i) + prev.getbyte(k)) & 0xff
|
|
893
|
+
a = (raw.getbyte(i + sw) + prev.getbyte(k + 1)) & 0xff
|
|
894
|
+
cur << v << a
|
|
895
|
+
lum << ((((v - 255) * a + 65_152) * 0x8081) >> 23)
|
|
896
|
+
m &= a
|
|
897
|
+
i += step
|
|
898
|
+
k += 2
|
|
899
|
+
end
|
|
900
|
+
cur << lum << m
|
|
901
|
+
end
|
|
902
|
+
|
|
903
|
+
def self.png_ga_avg(raw, i, prev, n, sw)
|
|
904
|
+
cur = png_buf(3 * n + 1)
|
|
905
|
+
lum = png_buf(n)
|
|
906
|
+
m = 255
|
|
907
|
+
v = a = 0
|
|
908
|
+
step = 2 * sw
|
|
909
|
+
k = 0
|
|
910
|
+
stop = 2 * n
|
|
911
|
+
while k < stop
|
|
912
|
+
v = (raw.getbyte(i) + ((v + prev.getbyte(k)) >> 1)) & 0xff
|
|
913
|
+
a = (raw.getbyte(i + sw) + ((a + prev.getbyte(k + 1)) >> 1)) & 0xff
|
|
914
|
+
cur << v << a
|
|
915
|
+
lum << ((((v - 255) * a + 65_152) * 0x8081) >> 23)
|
|
916
|
+
m &= a
|
|
917
|
+
i += step
|
|
918
|
+
k += 2
|
|
919
|
+
end
|
|
920
|
+
cur << lum << m
|
|
921
|
+
end
|
|
922
|
+
|
|
923
|
+
def self.png_ga_paeth(raw, i, prev, n, sw, t)
|
|
924
|
+
cur = png_buf(3 * n + 1)
|
|
925
|
+
lum = png_buf(n)
|
|
926
|
+
m = 255
|
|
927
|
+
v = a = cv = ca = 0
|
|
928
|
+
step = 2 * sw
|
|
929
|
+
k = 0
|
|
930
|
+
stop = 2 * n
|
|
931
|
+
while k < stop
|
|
932
|
+
u = prev.getbyte(k)
|
|
933
|
+
v = (raw.getbyte(i) + cv + t[(u - cv) * 511 + v - cv + 130_560]) & 0xff
|
|
934
|
+
cv = u
|
|
935
|
+
u = prev.getbyte(k + 1)
|
|
936
|
+
a = (raw.getbyte(i + sw) + ca + t[(u - ca) * 511 + a - ca + 130_560]) & 0xff
|
|
937
|
+
ca = u
|
|
938
|
+
cur << v << a
|
|
939
|
+
lum << ((((v - 255) * a + 65_152) * 0x8081) >> 23)
|
|
940
|
+
m &= a
|
|
941
|
+
i += step
|
|
942
|
+
k += 2
|
|
943
|
+
end
|
|
944
|
+
cur << lum << m
|
|
945
|
+
end
|
|
946
|
+
|
|
947
|
+
# --- RGB ------------------------------------------------------------------------
|
|
948
|
+
|
|
949
|
+
def self.png_rgb_sub(raw, i, n, sw)
|
|
950
|
+
cur = png_buf(4 * n)
|
|
951
|
+
lum = png_buf(n)
|
|
952
|
+
r = g = b = 0
|
|
953
|
+
s2 = 2 * sw
|
|
954
|
+
step = 3 * sw
|
|
955
|
+
stop = i + n * step
|
|
956
|
+
while i < stop
|
|
957
|
+
r = (raw.getbyte(i) + r) & 0xff
|
|
958
|
+
g = (raw.getbyte(i + sw) + g) & 0xff
|
|
959
|
+
b = (raw.getbyte(i + s2) + b) & 0xff
|
|
960
|
+
cur << r << g << b
|
|
961
|
+
lum << ((306 * r + 601 * g + 117 * b + 0x200) >> 10)
|
|
962
|
+
i += step
|
|
963
|
+
end
|
|
964
|
+
cur << lum
|
|
965
|
+
end
|
|
966
|
+
|
|
967
|
+
def self.png_rgb_up(raw, i, prev, n, sw)
|
|
968
|
+
cur = png_buf(4 * n)
|
|
969
|
+
lum = png_buf(n)
|
|
970
|
+
s2 = 2 * sw
|
|
971
|
+
step = 3 * sw
|
|
972
|
+
k = 0
|
|
973
|
+
stop = 3 * n
|
|
974
|
+
while k < stop
|
|
975
|
+
r = (raw.getbyte(i) + prev.getbyte(k)) & 0xff
|
|
976
|
+
g = (raw.getbyte(i + sw) + prev.getbyte(k + 1)) & 0xff
|
|
977
|
+
b = (raw.getbyte(i + s2) + prev.getbyte(k + 2)) & 0xff
|
|
978
|
+
cur << r << g << b
|
|
979
|
+
lum << ((306 * r + 601 * g + 117 * b + 0x200) >> 10)
|
|
980
|
+
i += step
|
|
981
|
+
k += 3
|
|
982
|
+
end
|
|
983
|
+
cur << lum
|
|
984
|
+
end
|
|
985
|
+
|
|
986
|
+
def self.png_rgb_avg(raw, i, prev, n, sw)
|
|
987
|
+
cur = png_buf(4 * n)
|
|
988
|
+
lum = png_buf(n)
|
|
989
|
+
r = g = b = 0
|
|
990
|
+
s2 = 2 * sw
|
|
991
|
+
step = 3 * sw
|
|
992
|
+
k = 0
|
|
993
|
+
stop = 3 * n
|
|
994
|
+
while k < stop
|
|
995
|
+
r = (raw.getbyte(i) + ((r + prev.getbyte(k)) >> 1)) & 0xff
|
|
996
|
+
g = (raw.getbyte(i + sw) + ((g + prev.getbyte(k + 1)) >> 1)) & 0xff
|
|
997
|
+
b = (raw.getbyte(i + s2) + ((b + prev.getbyte(k + 2)) >> 1)) & 0xff
|
|
998
|
+
cur << r << g << b
|
|
999
|
+
lum << ((306 * r + 601 * g + 117 * b + 0x200) >> 10)
|
|
1000
|
+
i += step
|
|
1001
|
+
k += 3
|
|
1002
|
+
end
|
|
1003
|
+
cur << lum
|
|
1004
|
+
end
|
|
1005
|
+
|
|
1006
|
+
def self.png_rgb_paeth(raw, i, prev, n, sw, t)
|
|
1007
|
+
cur = png_buf(4 * n)
|
|
1008
|
+
lum = png_buf(n)
|
|
1009
|
+
r = g = b = cr = cg = cb = 0
|
|
1010
|
+
s2 = 2 * sw
|
|
1011
|
+
step = 3 * sw
|
|
1012
|
+
k = 0
|
|
1013
|
+
stop = 3 * n
|
|
1014
|
+
while k < stop
|
|
1015
|
+
u = prev.getbyte(k)
|
|
1016
|
+
r = (raw.getbyte(i) + cr + t[(u - cr) * 511 + r - cr + 130_560]) & 0xff
|
|
1017
|
+
cr = u
|
|
1018
|
+
u = prev.getbyte(k + 1)
|
|
1019
|
+
g = (raw.getbyte(i + sw) + cg + t[(u - cg) * 511 + g - cg + 130_560]) & 0xff
|
|
1020
|
+
cg = u
|
|
1021
|
+
u = prev.getbyte(k + 2)
|
|
1022
|
+
b = (raw.getbyte(i + s2) + cb + t[(u - cb) * 511 + b - cb + 130_560]) & 0xff
|
|
1023
|
+
cb = u
|
|
1024
|
+
cur << r << g << b
|
|
1025
|
+
lum << ((306 * r + 601 * g + 117 * b + 0x200) >> 10)
|
|
1026
|
+
i += step
|
|
1027
|
+
k += 3
|
|
1028
|
+
end
|
|
1029
|
+
cur << lum
|
|
1030
|
+
end
|
|
1031
|
+
|
|
1032
|
+
# --- RGBA -----------------------------------------------------------------------
|
|
1033
|
+
|
|
1034
|
+
def self.png_rgba_sub(raw, i, n, sw)
|
|
1035
|
+
cur = png_buf(5 * n + 1)
|
|
1036
|
+
lum = png_buf(n)
|
|
1037
|
+
m = 255
|
|
1038
|
+
r = g = b = a = 0
|
|
1039
|
+
s2 = 2 * sw
|
|
1040
|
+
s3 = 3 * sw
|
|
1041
|
+
step = 4 * sw
|
|
1042
|
+
stop = i + n * step
|
|
1043
|
+
while i < stop
|
|
1044
|
+
r = (raw.getbyte(i) + r) & 0xff
|
|
1045
|
+
g = (raw.getbyte(i + sw) + g) & 0xff
|
|
1046
|
+
b = (raw.getbyte(i + s2) + b) & 0xff
|
|
1047
|
+
a = (raw.getbyte(i + s3) + a) & 0xff
|
|
1048
|
+
cur << r << g << b << a
|
|
1049
|
+
v = (306 * r + 601 * g + 117 * b + 0x200) >> 10
|
|
1050
|
+
lum << ((((v - 255) * a + 65_152) * 0x8081) >> 23)
|
|
1051
|
+
m &= a
|
|
1052
|
+
i += step
|
|
1053
|
+
end
|
|
1054
|
+
cur << lum << m
|
|
1055
|
+
end
|
|
1056
|
+
|
|
1057
|
+
def self.png_rgba_up(raw, i, prev, n, sw)
|
|
1058
|
+
cur = png_buf(5 * n + 1)
|
|
1059
|
+
lum = png_buf(n)
|
|
1060
|
+
m = 255
|
|
1061
|
+
s2 = 2 * sw
|
|
1062
|
+
s3 = 3 * sw
|
|
1063
|
+
step = 4 * sw
|
|
1064
|
+
k = 0
|
|
1065
|
+
stop = 4 * n
|
|
1066
|
+
while k < stop
|
|
1067
|
+
r = (raw.getbyte(i) + prev.getbyte(k)) & 0xff
|
|
1068
|
+
g = (raw.getbyte(i + sw) + prev.getbyte(k + 1)) & 0xff
|
|
1069
|
+
b = (raw.getbyte(i + s2) + prev.getbyte(k + 2)) & 0xff
|
|
1070
|
+
a = (raw.getbyte(i + s3) + prev.getbyte(k + 3)) & 0xff
|
|
1071
|
+
cur << r << g << b << a
|
|
1072
|
+
v = (306 * r + 601 * g + 117 * b + 0x200) >> 10
|
|
1073
|
+
lum << ((((v - 255) * a + 65_152) * 0x8081) >> 23)
|
|
1074
|
+
m &= a
|
|
1075
|
+
i += step
|
|
1076
|
+
k += 4
|
|
1077
|
+
end
|
|
1078
|
+
cur << lum << m
|
|
1079
|
+
end
|
|
1080
|
+
|
|
1081
|
+
def self.png_rgba_avg(raw, i, prev, n, sw)
|
|
1082
|
+
cur = png_buf(5 * n + 1)
|
|
1083
|
+
lum = png_buf(n)
|
|
1084
|
+
m = 255
|
|
1085
|
+
r = g = b = a = 0
|
|
1086
|
+
s2 = 2 * sw
|
|
1087
|
+
s3 = 3 * sw
|
|
1088
|
+
step = 4 * sw
|
|
1089
|
+
k = 0
|
|
1090
|
+
stop = 4 * n
|
|
1091
|
+
while k < stop
|
|
1092
|
+
r = (raw.getbyte(i) + ((r + prev.getbyte(k)) >> 1)) & 0xff
|
|
1093
|
+
g = (raw.getbyte(i + sw) + ((g + prev.getbyte(k + 1)) >> 1)) & 0xff
|
|
1094
|
+
b = (raw.getbyte(i + s2) + ((b + prev.getbyte(k + 2)) >> 1)) & 0xff
|
|
1095
|
+
a = (raw.getbyte(i + s3) + ((a + prev.getbyte(k + 3)) >> 1)) & 0xff
|
|
1096
|
+
cur << r << g << b << a
|
|
1097
|
+
v = (306 * r + 601 * g + 117 * b + 0x200) >> 10
|
|
1098
|
+
lum << ((((v - 255) * a + 65_152) * 0x8081) >> 23)
|
|
1099
|
+
m &= a
|
|
1100
|
+
i += step
|
|
1101
|
+
k += 4
|
|
1102
|
+
end
|
|
1103
|
+
cur << lum << m
|
|
1104
|
+
end
|
|
1105
|
+
|
|
1106
|
+
def self.png_rgba_paeth(raw, i, prev, n, sw, t)
|
|
1107
|
+
cur = png_buf(5 * n + 1)
|
|
1108
|
+
lum = png_buf(n)
|
|
1109
|
+
m = 255
|
|
1110
|
+
r = g = b = a = cr = cg = cb = ca = 0
|
|
1111
|
+
s2 = 2 * sw
|
|
1112
|
+
s3 = 3 * sw
|
|
1113
|
+
step = 4 * sw
|
|
1114
|
+
k = 0
|
|
1115
|
+
stop = 4 * n
|
|
1116
|
+
while k < stop
|
|
1117
|
+
u = prev.getbyte(k)
|
|
1118
|
+
r = (raw.getbyte(i) + cr + t[(u - cr) * 511 + r - cr + 130_560]) & 0xff
|
|
1119
|
+
cr = u
|
|
1120
|
+
u = prev.getbyte(k + 1)
|
|
1121
|
+
g = (raw.getbyte(i + sw) + cg + t[(u - cg) * 511 + g - cg + 130_560]) & 0xff
|
|
1122
|
+
cg = u
|
|
1123
|
+
u = prev.getbyte(k + 2)
|
|
1124
|
+
b = (raw.getbyte(i + s2) + cb + t[(u - cb) * 511 + b - cb + 130_560]) & 0xff
|
|
1125
|
+
cb = u
|
|
1126
|
+
u = prev.getbyte(k + 3)
|
|
1127
|
+
a = (raw.getbyte(i + s3) + ca + t[(u - ca) * 511 + a - ca + 130_560]) & 0xff
|
|
1128
|
+
ca = u
|
|
1129
|
+
cur << r << g << b << a
|
|
1130
|
+
v = (306 * r + 601 * g + 117 * b + 0x200) >> 10
|
|
1131
|
+
lum << ((((v - 255) * a + 65_152) * 0x8081) >> 23)
|
|
1132
|
+
m &= a
|
|
1133
|
+
i += step
|
|
1134
|
+
k += 4
|
|
1135
|
+
end
|
|
1136
|
+
cur << lum << m
|
|
1137
|
+
end
|
|
1138
|
+
end
|