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,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
|