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.
Files changed (58) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +16 -0
  3. data/LICENSE.txt +21 -0
  4. data/NOTICE +14 -0
  5. data/README.md +222 -0
  6. data/exe/qr_reader +66 -0
  7. data/lib/qr_reader/binarizer.rb +170 -0
  8. data/lib/qr_reader/bit_matrix.rb +120 -0
  9. data/lib/qr_reader/decoder/bitstream_parser.rb +307 -0
  10. data/lib/qr_reader/decoder/character_set.rb +163 -0
  11. data/lib/qr_reader/decoder/codewords.rb +164 -0
  12. data/lib/qr_reader/decoder/data_mask.rb +57 -0
  13. data/lib/qr_reader/decoder/format_reader.rb +204 -0
  14. data/lib/qr_reader/decoder/function_patterns.rb +62 -0
  15. data/lib/qr_reader/decoder/galois_field.rb +69 -0
  16. data/lib/qr_reader/decoder/reed_solomon.rb +312 -0
  17. data/lib/qr_reader/decoder.rb +241 -0
  18. data/lib/qr_reader/detector/alignment_grid.rb +127 -0
  19. data/lib/qr_reader/detector/alignment_pattern_finder.rb +191 -0
  20. data/lib/qr_reader/detector/candidate/dimension.rb +237 -0
  21. data/lib/qr_reader/detector/candidate/strokes.rb +126 -0
  22. data/lib/qr_reader/detector/candidate.rb +436 -0
  23. data/lib/qr_reader/detector/finder_pattern.rb +63 -0
  24. data/lib/qr_reader/detector/finder_pattern_finder.rb +199 -0
  25. data/lib/qr_reader/detector/geometry.rb +118 -0
  26. data/lib/qr_reader/detector/grid_sampler.rb +350 -0
  27. data/lib/qr_reader/detector/pattern_sets.rb +235 -0
  28. data/lib/qr_reader/detector/perspective_transform.rb +117 -0
  29. data/lib/qr_reader/detector/pure_detector.rb +197 -0
  30. data/lib/qr_reader/detector.rb +215 -0
  31. data/lib/qr_reader/errors.rb +14 -0
  32. data/lib/qr_reader/format_information.rb +79 -0
  33. data/lib/qr_reader/formats/bmp.rb +434 -0
  34. data/lib/qr_reader/formats/ccitt.rb +81 -0
  35. data/lib/qr_reader/formats/gif.rb +272 -0
  36. data/lib/qr_reader/formats/jpeg.rb +152 -0
  37. data/lib/qr_reader/formats/pdf.rb +2935 -0
  38. data/lib/qr_reader/formats/png.rb +311 -0
  39. data/lib/qr_reader/formats/pnm.rb +286 -0
  40. data/lib/qr_reader/formats/tiff.rb +1345 -0
  41. data/lib/qr_reader/formats.rb +279 -0
  42. data/lib/qr_reader/image.rb +231 -0
  43. data/lib/qr_reader/kernels/binarizer.rb +475 -0
  44. data/lib/qr_reader/kernels/ccitt.rb +554 -0
  45. data/lib/qr_reader/kernels/detector.rb +587 -0
  46. data/lib/qr_reader/kernels/image.rb +65 -0
  47. data/lib/qr_reader/kernels/jpeg.rb +3421 -0
  48. data/lib/qr_reader/kernels/lzw.rb +173 -0
  49. data/lib/qr_reader/kernels/pixels.rb +116 -0
  50. data/lib/qr_reader/kernels/png.rb +1138 -0
  51. data/lib/qr_reader/kernels.rb +14 -0
  52. data/lib/qr_reader/native_kernels.rb +130 -0
  53. data/lib/qr_reader/reader.rb +348 -0
  54. data/lib/qr_reader/result.rb +49 -0
  55. data/lib/qr_reader/symbol_version.rb +173 -0
  56. data/lib/qr_reader/version.rb +5 -0
  57. data/lib/qr_reader.rb +77 -0
  58. 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