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,173 @@
1
+ # frozen_string_literal: true
2
+
3
+ # LZW kernel shared by TIFF (compression 5) and PDF (LZWDecode). Kernel
4
+ # files are plain Ruby in the subset that Spinel compiles (see DESIGN.md,
5
+ # "Kernels").
6
+ #
7
+ # The code table holds no Strings: every entry is a stretch of the output
8
+ # decoded so far (the previous code's output plus the first byte of the
9
+ # output that followed it), so an entry is an offset and a length, and
10
+ # decoding a code copies that stretch.
11
+ module QrReaderKernels
12
+ # Codes decoded per lzw_batch call (YJIT compiles methods, not loops).
13
+ LZW_BATCH = 4096
14
+ # Entries up to this length are copied byte by byte, longer ones with
15
+ # byteslice (cheaper than allocating a slice for a few bytes).
16
+ LZW_SHORT = 3
17
+
18
+ # Decodes LZW data (codes of 9 to 12 bits, Clear 256, EOI 257) into at
19
+ # most +limit+ bytes. +early_change+ is 1 when the code width grows one
20
+ # code early (TIFF, PDF's default EarlyChange 1) or 0; +lsb_first+ reads
21
+ # codes least significant bit first (the old TIFF variant). Decoding
22
+ # stops at EOI, at the end of the data, at +limit+ bytes and at the
23
+ # first invalid code, keeping what was decoded before.
24
+ # spinel-sample: QrReaderKernels.lzw_decode("\x80\x0b\x60\x50\x22\x0c\x0c\x85\x01".b, 100, 1, false)
25
+ def self.lzw_decode(data, limit, early_change, lsb_first)
26
+ out = lzw_buffer
27
+ return out if limit <= 0
28
+
29
+ src = data + "\0\0\0\0"
30
+ offsets = Array.new(4096, 0)
31
+ lengths = Array.new(4096, 0)
32
+ # [bit position, next byte, bit buffer, bits in the buffer, code
33
+ # width, next code, previous output's offset and length (0: no
34
+ # previous code), 1 when done]
35
+ state = [0, 0, 0, 0, 9, 258, 0, 0, 0]
36
+ nbits = data.bytesize * 8
37
+ while state[8] == 0 && out.bytesize < limit
38
+ out << lzw_batch(src, nbits, out, offsets, lengths, state, early_change, lsb_first, limit - out.bytesize)
39
+ end
40
+ out.bytesize > limit ? out.byteslice(0, limit) : out
41
+ end
42
+
43
+ # An empty binary String (a helper: YJIT side-exits on the keyword call).
44
+ def self.lzw_buffer
45
+ String.new(capacity: 256, encoding: Encoding::BINARY)
46
+ end
47
+
48
+ # Decodes up to LZW_BATCH codes and returns their output. +out+ is the
49
+ # output so far (read only: table entries point into it), +state+ is
50
+ # updated in place.
51
+ def self.lzw_batch(src, nbits, out, offsets, lengths, state, early, lsb_first, room)
52
+ chunk = lzw_buffer
53
+ base = out.bytesize
54
+ pos = state[0]
55
+ i = state[1]
56
+ bits = state[2]
57
+ nb = state[3]
58
+ width = state[4]
59
+ nxt = state[5]
60
+ prev_off = state[6]
61
+ prev_len = state[7]
62
+ mask = (1 << width) - 1
63
+ done = 0
64
+ n = LZW_BATCH
65
+ while n > 0
66
+ if pos + width > nbits || chunk.bytesize >= room
67
+ done = 1
68
+ break
69
+ end
70
+ if lsb_first
71
+ while nb < width
72
+ bits |= src.getbyte(i) << nb
73
+ i += 1
74
+ nb += 8
75
+ end
76
+ code = bits & mask
77
+ bits >>= width
78
+ else
79
+ while nb < width
80
+ bits = ((bits << 8) | src.getbyte(i)) & 0xFFFFFF
81
+ i += 1
82
+ nb += 8
83
+ end
84
+ code = (bits >> (nb - width)) & mask
85
+ end
86
+ nb -= width
87
+ pos += width
88
+ if code == 256
89
+ nxt = 258
90
+ width = 9
91
+ mask = 511
92
+ prev_len = 0
93
+ elsif code == 257
94
+ done = 1
95
+ break
96
+ else
97
+ start = base + chunk.bytesize
98
+ if code < 256
99
+ chunk << code
100
+ else
101
+ if prev_len == 0 || code > nxt
102
+ # A code that is not in the table (yet).
103
+ done = 1
104
+ break
105
+ end
106
+ if code < nxt
107
+ off = offsets[code]
108
+ len = lengths[code]
109
+ extra = -1
110
+ else
111
+ # The code being defined: the previous output plus its first byte.
112
+ off = prev_off
113
+ len = prev_len
114
+ extra = off >= base ? chunk.getbyte(off - base) : out.getbyte(off)
115
+ end
116
+ # Copy the entry's stretch of output: +out+, then +chunk+ from
117
+ # offset +base+ on.
118
+ if off >= base
119
+ j = off - base
120
+ if len <= LZW_SHORT
121
+ stop = j + len
122
+ while j < stop
123
+ chunk << chunk.getbyte(j)
124
+ j += 1
125
+ end
126
+ else
127
+ # (A slice reaching the end of a String that grows later
128
+ # shares its buffer, and the next append copies it all.)
129
+ chunk << chunk.byteslice(j, len - 1) << chunk.getbyte(j + len - 1)
130
+ end
131
+ elsif off + len <= base
132
+ if len <= LZW_SHORT
133
+ stop = off + len
134
+ while off < stop
135
+ chunk << out.getbyte(off)
136
+ off += 1
137
+ end
138
+ else
139
+ chunk << out.byteslice(off, len - 1) << out.getbyte(off + len - 1)
140
+ end
141
+ else
142
+ head = base - off
143
+ chunk << out.byteslice(off, head - 1) << out.getbyte(base - 1)
144
+ chunk << chunk.byteslice(0, len - head - 1) << chunk.getbyte(len - head - 1)
145
+ end
146
+ chunk << extra if extra >= 0
147
+ end
148
+ if prev_len > 0 && nxt < 4096
149
+ offsets[nxt] = prev_off
150
+ lengths[nxt] = prev_len + 1
151
+ nxt += 1
152
+ end
153
+ prev_off = start
154
+ prev_len = base + chunk.bytesize - start
155
+ if width < 12 && nxt + early > mask
156
+ width += 1
157
+ mask = (1 << width) - 1
158
+ end
159
+ end
160
+ n -= 1
161
+ end
162
+ state[0] = pos
163
+ state[1] = i
164
+ state[2] = bits
165
+ state[3] = nb
166
+ state[4] = width
167
+ state[5] = nxt
168
+ state[6] = prev_off
169
+ state[7] = prev_len
170
+ state[8] = done
171
+ chunk
172
+ end
173
+ end
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../errors"
4
+
5
+ # Pixel conversion kernels: interleaved 8-bit samples to luminance, with
6
+ # alpha composited over white. Plain Ruby in the subset Spinel compiles (see
7
+ # DESIGN.md, "Kernels"); the per-row helpers are shared by the image
8
+ # decoders' kernels (kernels/png.rb).
9
+ #
10
+ # Luminance is Rec. 601 with 10-bit fixed point weights (Image.luma):
11
+ # (306 * r + 601 * g + 117 * b + 0x200) >> 10
12
+ # and a sample v with alpha a over white is
13
+ # (v * a + 255 * (255 - a) + 127) / 255 == ((v - 255) * a + 65_152) / 255
14
+ # computed as (((v - 255) * a + 65_152) * 0x8081) >> 23: x / 255 equals
15
+ # (x * 0x8081) >> 23 for every 0 <= x < 65_536, and the numerator is
16
+ # 127..65_152 (a multiplication instead of a division call under YJIT).
17
+ #
18
+ # The row helpers reference no constants and allocate through px_buf (see
19
+ # kernels/png.rb for why that matters to YJIT).
20
+ module QrReaderKernels
21
+ # Luminance of +height+ rows of +width+ pixels of +stride+ bytes each.
22
+ # Row y starts at byte +offset+ + y * +pitch+ of +data+ (a negative pitch
23
+ # walks bottom-up rows; padding between rows is skipped). Channel byte
24
+ # offsets within a pixel: red +ro+, green +go+, blue +bo+, alpha +ao+;
25
+ # +go+ < 0 means a single gray sample at +ro+, +ao+ < 0 means opaque.
26
+ # Returns width * height luminance bytes.
27
+ # spinel-sample: QrReaderKernels.pixels_luma("\x01\x02\x03\x04".b, 0, 4, 1, 1, 4, 0, 1, 2, 3)
28
+ def self.pixels_luma(data, offset, pitch, width, height, stride, ro, go, bo, ao)
29
+ last = offset + (height - 1) * pitch
30
+ first = last < offset ? last : offset
31
+ if width < 1 || height < 1 || stride < 1 || first < 0 || (last > offset ? last : offset) + width * stride > data.bytesize
32
+ raise QrReader::ImageError, "pixel data too short"
33
+ end
34
+
35
+ out = px_buf(width * height)
36
+ off = offset
37
+ y = 0
38
+ while y < height
39
+ out << px_row(data, off, width, stride, ro, go, bo, ao)
40
+ off += pitch
41
+ y += 1
42
+ end
43
+ out
44
+ end
45
+
46
+ # An empty binary String with room for +size+ bytes.
47
+ def self.px_buf(size)
48
+ String.new(capacity: size)
49
+ end
50
+
51
+ # One row of pixels_luma (dispatch only: the loops live in helpers that
52
+ # YJIT compiles separately).
53
+ def self.px_row(src, off, n, stride, ro, go, bo, ao)
54
+ if go < 0
55
+ if ao < 0
56
+ stride == 1 ? (src.byteslice(off + ro, n) || "") : px_gray_row(src, off, n, ro, stride)
57
+ else
58
+ px_gray_alpha_row(src, off, n, ro, ao, stride)
59
+ end
60
+ elsif ao < 0
61
+ px_rgb_row(src, off, n, ro, go, bo, stride)
62
+ else
63
+ px_rgba_row(src, off, n, ro, go, bo, ao, stride)
64
+ end
65
+ end
66
+
67
+ # Every +stride+-th byte starting at +off+ + +vo+ (e.g. the high bytes of
68
+ # 16-bit samples).
69
+ def self.px_gray_row(src, off, n, vo, stride)
70
+ row = px_buf(n)
71
+ i = off + vo
72
+ stop = i + n * stride
73
+ while i < stop
74
+ row << src.getbyte(i)
75
+ i += stride
76
+ end
77
+ row
78
+ end
79
+
80
+ def self.px_gray_alpha_row(src, off, n, vo, ao, stride)
81
+ row = px_buf(n)
82
+ i = off
83
+ stop = off + n * stride
84
+ while i < stop
85
+ a = src.getbyte(i + ao)
86
+ v = src.getbyte(i + vo)
87
+ row << (a == 255 ? v : (((v - 255) * a + 65_152) * 0x8081) >> 23)
88
+ i += stride
89
+ end
90
+ row
91
+ end
92
+
93
+ def self.px_rgb_row(src, off, n, ro, go, bo, stride)
94
+ row = px_buf(n)
95
+ i = off
96
+ stop = off + n * stride
97
+ while i < stop
98
+ row << ((306 * src.getbyte(i + ro) + 601 * src.getbyte(i + go) + 117 * src.getbyte(i + bo) + 0x200) >> 10)
99
+ i += stride
100
+ end
101
+ row
102
+ end
103
+
104
+ def self.px_rgba_row(src, off, n, ro, go, bo, ao, stride)
105
+ row = px_buf(n)
106
+ i = off
107
+ stop = off + n * stride
108
+ while i < stop
109
+ v = (306 * src.getbyte(i + ro) + 601 * src.getbyte(i + go) + 117 * src.getbyte(i + bo) + 0x200) >> 10
110
+ a = src.getbyte(i + ao)
111
+ row << (a == 255 ? v : (((v - 255) * a + 65_152) * 0x8081) >> 23)
112
+ i += stride
113
+ end
114
+ row
115
+ end
116
+ end