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,554 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../errors"
|
|
4
|
+
|
|
5
|
+
# CCITT fax kernel: Group 3 (ITU-T T.4, 1-D Modified Huffman and 2-D
|
|
6
|
+
# Modified READ) and Group 4 (T.6, MMR) decoding for TIFF, PDF and JBIG2's
|
|
7
|
+
# MMR regions. Kernel files are plain Ruby in the subset that Spinel
|
|
8
|
+
# compiles (see DESIGN.md, "Kernels").
|
|
9
|
+
#
|
|
10
|
+
# Codes are looked up in tables indexed by the next 13 (runs) or 7 (2-D
|
|
11
|
+
# modes) bits, read from adjacent 16-bit words of the data; changing
|
|
12
|
+
# elements live in Integer arrays; rows are built from cached runs and
|
|
13
|
+
# rows equal to the row above are appended as they are.
|
|
14
|
+
module QrReaderKernels
|
|
15
|
+
# Terminating codes for run lengths 0..63.
|
|
16
|
+
CCITT_WHITE_TERMINATING = %w[
|
|
17
|
+
00110101 000111 0111 1000 1011 1100 1110 1111 10011 10100 00111 01000 001000 000011 110100 110101
|
|
18
|
+
101010 101011 0100111 0001100 0001000 0010111 0000011 0000100 0101000 0101011 0010011 0100100
|
|
19
|
+
0011000 00000010 00000011 00011010 00011011 00010010 00010011 00010100 00010101 00010110 00010111
|
|
20
|
+
00101000 00101001 00101010 00101011 00101100 00101101 00000100 00000101 00001010 00001011 01010010
|
|
21
|
+
01010011 01010100 01010101 00100100 00100101 01011000 01011001 01011010 01011011 01001010 01001011
|
|
22
|
+
00110010 00110011 00110100
|
|
23
|
+
].freeze
|
|
24
|
+
CCITT_BLACK_TERMINATING = %w[
|
|
25
|
+
0000110111 010 11 10 011 0011 0010 00011 000101 000100 0000100 0000101 0000111 00000100 00000111
|
|
26
|
+
000011000 0000010111 0000011000 0000001000 00001100111 00001101000 00001101100 00000110111
|
|
27
|
+
00000101000 00000010111 00000011000 000011001010 000011001011 000011001100 000011001101
|
|
28
|
+
000001101000 000001101001 000001101010 000001101011 000011010010 000011010011 000011010100
|
|
29
|
+
000011010101 000011010110 000011010111 000001101100 000001101101 000011011010 000011011011
|
|
30
|
+
000001010100 000001010101 000001010110 000001010111 000001100100 000001100101 000001010010
|
|
31
|
+
000001010011 000000100100 000000110111 000000111000 000000100111 000000101000 000001011000
|
|
32
|
+
000001011001 000000101011 000000101100 000001011010 000001100110 000001100111
|
|
33
|
+
].freeze
|
|
34
|
+
# Make-up codes for run lengths 64, 128, ..., 1728.
|
|
35
|
+
CCITT_WHITE_MAKEUP = %w[
|
|
36
|
+
11011 10010 010111 0110111 00110110 00110111 01100100 01100101 01101000 01100111 011001100
|
|
37
|
+
011001101 011010010 011010011 011010100 011010101 011010110 011010111 011011000 011011001
|
|
38
|
+
011011010 011011011 010011000 010011001 010011010 011000 010011011
|
|
39
|
+
].freeze
|
|
40
|
+
CCITT_BLACK_MAKEUP = %w[
|
|
41
|
+
0000001111 000011001000 000011001001 000001011011 000000110011 000000110100 000000110101
|
|
42
|
+
0000001101100 0000001101101 0000001001010 0000001001011 0000001001100 0000001001101
|
|
43
|
+
0000001110010 0000001110011 0000001110100 0000001110101 0000001110110 0000001110111
|
|
44
|
+
0000001010010 0000001010011 0000001010100 0000001010101 0000001011010 0000001011011
|
|
45
|
+
0000001100100 0000001100101
|
|
46
|
+
].freeze
|
|
47
|
+
# Extended make-up codes shared by both colours: 1792, 1856, ..., 2560.
|
|
48
|
+
CCITT_EXTENDED_MAKEUP = %w[
|
|
49
|
+
00000001000 00000001100 00000001101 000000010010 000000010011 000000010100 000000010101
|
|
50
|
+
000000010110 000000010111 000000011100 000000011101 000000011110 000000011111
|
|
51
|
+
].freeze
|
|
52
|
+
# 2-D mode codes: vertical VL3..VR3 (0..6; a1 = b1 + mode - 3), pass (7),
|
|
53
|
+
# horizontal (8), extension (9).
|
|
54
|
+
CCITT_MODE_CODES = %w[0000010 000010 010 1 011 000011 0000011 0001 001 0000001].freeze
|
|
55
|
+
|
|
56
|
+
CCITT_PASS = 7
|
|
57
|
+
CCITT_HORIZONTAL = 8
|
|
58
|
+
# Seven zero bits (an EOL inside a row, or garbage).
|
|
59
|
+
CCITT_ZEROS = 10
|
|
60
|
+
|
|
61
|
+
# Row starts (CcittDecoder#start_row).
|
|
62
|
+
CCITT_ONE_D = 0
|
|
63
|
+
CCITT_TWO_D = 1
|
|
64
|
+
CCITT_DONE = 2
|
|
65
|
+
|
|
66
|
+
# Runs shorter than this come from cached Strings.
|
|
67
|
+
CCITT_CACHED_RUNS = 64
|
|
68
|
+
|
|
69
|
+
# Value of a binary digit String ("0110" -> 6).
|
|
70
|
+
def self.ccitt_code_value(code)
|
|
71
|
+
v = 0
|
|
72
|
+
i = 0
|
|
73
|
+
n = code.bytesize
|
|
74
|
+
while i < n
|
|
75
|
+
v = (v << 1) | (code.getbyte(i) - 48)
|
|
76
|
+
i += 1
|
|
77
|
+
end
|
|
78
|
+
v
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Writes (value << 4 | code length) into +table+ (indexed by +bits+ bits)
|
|
82
|
+
# at every index that starts with +code+.
|
|
83
|
+
def self.ccitt_add_code(table, bits, code, value)
|
|
84
|
+
len = code.bytesize
|
|
85
|
+
i = ccitt_code_value(code) << (bits - len)
|
|
86
|
+
stop = i + (1 << (bits - len))
|
|
87
|
+
while i < stop
|
|
88
|
+
table[i] = (value << 4) | len
|
|
89
|
+
i += 1
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Lookup table for the next 13 bits: (run << 4 | code length), or -1
|
|
94
|
+
# where no code matches (EOL, garbage).
|
|
95
|
+
def self.ccitt_run_table(terminating, makeup)
|
|
96
|
+
table = Array.new(8192, -1)
|
|
97
|
+
i = 0
|
|
98
|
+
while i < terminating.size
|
|
99
|
+
ccitt_add_code(table, 13, terminating[i], i)
|
|
100
|
+
i += 1
|
|
101
|
+
end
|
|
102
|
+
i = 0
|
|
103
|
+
while i < makeup.size
|
|
104
|
+
ccitt_add_code(table, 13, makeup[i], 64 * (i + 1))
|
|
105
|
+
i += 1
|
|
106
|
+
end
|
|
107
|
+
i = 0
|
|
108
|
+
while i < CCITT_EXTENDED_MAKEUP.size
|
|
109
|
+
ccitt_add_code(table, 13, CCITT_EXTENDED_MAKEUP[i], 1792 + 64 * i)
|
|
110
|
+
i += 1
|
|
111
|
+
end
|
|
112
|
+
table
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Lookup table for the next 7 bits: (mode << 4 | code length).
|
|
116
|
+
def self.ccitt_mode_table
|
|
117
|
+
table = Array.new(128, CCITT_ZEROS << 4)
|
|
118
|
+
i = 0
|
|
119
|
+
while i < CCITT_MODE_CODES.size
|
|
120
|
+
ccitt_add_code(table, 7, CCITT_MODE_CODES[i], i)
|
|
121
|
+
i += 1
|
|
122
|
+
end
|
|
123
|
+
table
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Strings of 0..CCITT_CACHED_RUNS-1 copies of +byte+.
|
|
127
|
+
def self.ccitt_runs(byte)
|
|
128
|
+
runs = []
|
|
129
|
+
run = String.new(capacity: CCITT_CACHED_RUNS, encoding: Encoding::BINARY)
|
|
130
|
+
i = 0
|
|
131
|
+
while i < CCITT_CACHED_RUNS
|
|
132
|
+
runs << run.dup
|
|
133
|
+
run << byte
|
|
134
|
+
i += 1
|
|
135
|
+
end
|
|
136
|
+
runs
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
CCITT_WHITE_RUNS = ccitt_run_table(CCITT_WHITE_TERMINATING, CCITT_WHITE_MAKEUP)
|
|
140
|
+
CCITT_BLACK_RUNS = ccitt_run_table(CCITT_BLACK_TERMINATING, CCITT_BLACK_MAKEUP)
|
|
141
|
+
CCITT_MODES = ccitt_mode_table
|
|
142
|
+
CCITT_RUNS_FF = ccitt_runs(255)
|
|
143
|
+
CCITT_RUNS_00 = ccitt_runs(0)
|
|
144
|
+
|
|
145
|
+
# Decodes a CCITT stream to +columns+-byte rows (coded white 255, black
|
|
146
|
+
# 0; swapped when +black_is_1+). +k+ < 0: Group 4; 0: Group 3 1-D; > 0:
|
|
147
|
+
# Group 3 2-D. +rows+ > 0: exactly that many rows (white after the end
|
|
148
|
+
# of the data); 0: all rows up to EOFB/RTC or the end of the data, at
|
|
149
|
+
# most +max_rows+ (more raise UnsupportedImageError). +byte_align+: rows
|
|
150
|
+
# start on byte boundaries; +end_of_line+: EOLs precede the rows (only
|
|
151
|
+
# decides whether an unaligned EOL may stand before a byte-aligned row).
|
|
152
|
+
#
|
|
153
|
+
# Damaged rows of EOL-delimited (Group 3) data are replaced by the row
|
|
154
|
+
# above and decoding resumes at the next EOL. Without EOLs an error ends
|
|
155
|
+
# the image there. Raises ImageError when not even the first row decodes.
|
|
156
|
+
# spinel-sample: QrReaderKernels.ccitt_decode("\x26\xA0\x00\x10\x01".b, 8, 0, -1, false, false, false, 100)
|
|
157
|
+
def self.ccitt_decode(data, columns, rows, k, byte_align, end_of_line, black_is_1, max_rows)
|
|
158
|
+
decoder = CcittDecoder.new(data, columns, k, byte_align, end_of_line)
|
|
159
|
+
out = decoder.decode(rows, max_rows, black_is_1)
|
|
160
|
+
raise QrReader::ImageError, "corrupt CCITT data (first row undecodable)" if decoder.failed_at_start
|
|
161
|
+
raise QrReader::ImageError, "CCITT data contains no rows" if out.bytesize == 0
|
|
162
|
+
|
|
163
|
+
out
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Decodes one CCITT stream.
|
|
167
|
+
class CcittDecoder
|
|
168
|
+
attr_reader :failed_at_start
|
|
169
|
+
|
|
170
|
+
def initialize(data, columns, k, byte_align, end_of_line)
|
|
171
|
+
@data = data
|
|
172
|
+
@nbits = data.bytesize * 8
|
|
173
|
+
@win = words(data)
|
|
174
|
+
@columns = columns
|
|
175
|
+
@k = k
|
|
176
|
+
@byte_align = byte_align
|
|
177
|
+
@end_of_line = end_of_line
|
|
178
|
+
@pos = 0
|
|
179
|
+
@eols = false
|
|
180
|
+
@failed_at_start = false
|
|
181
|
+
@last = ""
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# The rows (see QrReaderKernels.ccitt_decode).
|
|
185
|
+
def decode(rows, max_rows, inverted)
|
|
186
|
+
cols = @columns
|
|
187
|
+
bg = filled(inverted ? 0 : 255, cols)
|
|
188
|
+
fg = filled(inverted ? 255 : 0, cols)
|
|
189
|
+
bg_runs = inverted ? CCITT_RUNS_00 : CCITT_RUNS_FF
|
|
190
|
+
fg_runs = inverted ? CCITT_RUNS_FF : CCITT_RUNS_00
|
|
191
|
+
out = String.new(capacity: cols * (rows > 0 ? rows : 64), encoding: Encoding::BINARY)
|
|
192
|
+
limit = rows > 0 ? rows : max_rows
|
|
193
|
+
ref = [cols, cols, cols, cols]
|
|
194
|
+
cur = []
|
|
195
|
+
count = 0
|
|
196
|
+
while count < limit
|
|
197
|
+
mode = start_row(count == 0)
|
|
198
|
+
break if mode == CCITT_DONE
|
|
199
|
+
|
|
200
|
+
ok = mode == CCITT_TWO_D ? decode_2d_row(ref, cur) : decode_1d_row(cur)
|
|
201
|
+
stop = false
|
|
202
|
+
if ok
|
|
203
|
+
push_sentinels(cur, cols)
|
|
204
|
+
elsif @eols
|
|
205
|
+
# Damaged row: repeat the row above, resume at the next EOL.
|
|
206
|
+
copy(ref, cur)
|
|
207
|
+
@pos = find_eol(@pos)
|
|
208
|
+
else
|
|
209
|
+
# No EOLs to resynchronise at: keep the row's start and stop.
|
|
210
|
+
@failed_at_start = true if count == 0
|
|
211
|
+
cur.pop if cur.size.odd?
|
|
212
|
+
push_sentinels(cur, cols)
|
|
213
|
+
stop = true
|
|
214
|
+
end
|
|
215
|
+
out << row_for(cur, ref, bg, fg, bg_runs, fg_runs)
|
|
216
|
+
count += 1
|
|
217
|
+
break if stop
|
|
218
|
+
|
|
219
|
+
swap = ref
|
|
220
|
+
ref = cur
|
|
221
|
+
cur = swap
|
|
222
|
+
cur.clear
|
|
223
|
+
end
|
|
224
|
+
if rows > 0
|
|
225
|
+
while count < rows
|
|
226
|
+
out << bg
|
|
227
|
+
count += 1
|
|
228
|
+
end
|
|
229
|
+
elsif count == limit && start_row(false) != CCITT_DONE
|
|
230
|
+
raise QrReader::UnsupportedImageError, "CCITT image too large (more than #{limit} rows)"
|
|
231
|
+
end
|
|
232
|
+
out
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
private
|
|
236
|
+
|
|
237
|
+
# The data as big-endian 16-bit words plus zero padding: any 13 bits
|
|
238
|
+
# lie within two adjacent words, any 25 bits within three.
|
|
239
|
+
def words(data)
|
|
240
|
+
n = data.bytesize
|
|
241
|
+
count = (n + 1) / 2 + 8
|
|
242
|
+
win = Array.new(count, 0)
|
|
243
|
+
i = 0
|
|
244
|
+
while i < n
|
|
245
|
+
i = words_part(data, win, i, n)
|
|
246
|
+
end
|
|
247
|
+
win
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
# Fills words from byte +i+ on (a part, so that YJIT compiles the loop).
|
|
251
|
+
def words_part(data, win, i, n)
|
|
252
|
+
stop = i + 8192
|
|
253
|
+
stop = n - 1 if stop > n - 1
|
|
254
|
+
while i < stop
|
|
255
|
+
win[i >> 1] = (data.getbyte(i) << 8) | data.getbyte(i + 1)
|
|
256
|
+
i += 2
|
|
257
|
+
end
|
|
258
|
+
if i == n - 1
|
|
259
|
+
win[i >> 1] = data.getbyte(i) << 8
|
|
260
|
+
i += 1
|
|
261
|
+
end
|
|
262
|
+
i
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
def filled(byte, count)
|
|
266
|
+
s = String.new(capacity: count, encoding: Encoding::BINARY)
|
|
267
|
+
i = 0
|
|
268
|
+
while i < count
|
|
269
|
+
s << byte
|
|
270
|
+
i += 1
|
|
271
|
+
end
|
|
272
|
+
s
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
def push_sentinels(cur, cols)
|
|
276
|
+
cur << cols
|
|
277
|
+
cur << cols
|
|
278
|
+
cur << cols
|
|
279
|
+
cur << cols
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
def copy(from, to)
|
|
283
|
+
to.clear
|
|
284
|
+
i = 0
|
|
285
|
+
n = from.size
|
|
286
|
+
while i < n
|
|
287
|
+
to << from[i]
|
|
288
|
+
i += 1
|
|
289
|
+
end
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
def peek(pos, bits)
|
|
293
|
+
w = @win
|
|
294
|
+
k = pos >> 4
|
|
295
|
+
(((w[k] << 32) | (w[k + 1] << 16) | w[k + 2]) >> (48 - bits - (pos & 15))) & ((1 << bits) - 1)
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
# Handles byte alignment, fill bits, EOL codes, EOFB/RTC and the
|
|
299
|
+
# 1-D/2-D tag bit in front of a row. Returns CCITT_ONE_D, CCITT_TWO_D
|
|
300
|
+
# or CCITT_DONE. Group 3 data may start with several EOLs.
|
|
301
|
+
def start_row(first)
|
|
302
|
+
pos = @pos
|
|
303
|
+
return CCITT_DONE if pos >= @nbits
|
|
304
|
+
|
|
305
|
+
if @byte_align && !@end_of_line
|
|
306
|
+
# EOFB/RTC may follow the last row unaligned; two EOLs in a row
|
|
307
|
+
# cannot be mistaken for fill bits and a row.
|
|
308
|
+
return CCITT_DONE if peek(pos, 24) == 0b000000000001_000000000001
|
|
309
|
+
|
|
310
|
+
pos = (pos + 7) & ~7
|
|
311
|
+
end
|
|
312
|
+
eol = false
|
|
313
|
+
# 11 zero bits: an EOL (maybe after fill bits), EOFB/RTC or padding.
|
|
314
|
+
while peek(pos, 12) <= 1
|
|
315
|
+
pos = skip_eol(pos)
|
|
316
|
+
return CCITT_DONE if pos < 0
|
|
317
|
+
# A second EOL right away is EOFB (Group 4) or RTC (Group 3).
|
|
318
|
+
return CCITT_DONE if eol && !(first && @k >= 0)
|
|
319
|
+
|
|
320
|
+
eol = true
|
|
321
|
+
@eols = true
|
|
322
|
+
# In 2-D Group 3 data each EOL of an RTC carries a tag bit.
|
|
323
|
+
pos += 1 if @k > 0 && peek(pos + 1, 12) <= 1
|
|
324
|
+
end
|
|
325
|
+
pos = (pos + 7) & ~7 if @byte_align && !eol
|
|
326
|
+
two_d = @k < 0
|
|
327
|
+
if @k > 0
|
|
328
|
+
two_d = peek(pos, 1) == 0
|
|
329
|
+
pos += 1
|
|
330
|
+
end
|
|
331
|
+
@pos = pos
|
|
332
|
+
two_d ? CCITT_TWO_D : CCITT_ONE_D
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
# Skips zero bits and the 1 bit that ends an EOL. Returns the position
|
|
336
|
+
# after it, or -1 when only zeros remain.
|
|
337
|
+
def skip_eol(pos)
|
|
338
|
+
while pos < @nbits
|
|
339
|
+
bits = peek(pos, 25)
|
|
340
|
+
return pos + 26 - bit_length(bits) if bits != 0
|
|
341
|
+
|
|
342
|
+
# 25 zero bits: continue at the next nonzero byte.
|
|
343
|
+
i = (pos >> 3) + 1
|
|
344
|
+
n = @data.bytesize
|
|
345
|
+
i += 1 while i < n && @data.getbyte(i) == 0
|
|
346
|
+
return -1 if i >= n
|
|
347
|
+
|
|
348
|
+
pos = i * 8
|
|
349
|
+
end
|
|
350
|
+
-1
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
# Bit position of the next EOL code (or of the fill bits before it)
|
|
354
|
+
# at or after +pos+; the end of the data when there is none.
|
|
355
|
+
def find_eol(pos)
|
|
356
|
+
nbits = @nbits
|
|
357
|
+
while pos < nbits
|
|
358
|
+
bits = peek(pos, 12)
|
|
359
|
+
return pos if bits <= 1
|
|
360
|
+
|
|
361
|
+
# No EOL can start before the first 1 bit of this window.
|
|
362
|
+
pos += 13 - bit_length(bits)
|
|
363
|
+
end
|
|
364
|
+
nbits
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
def bit_length(v)
|
|
368
|
+
n = 0
|
|
369
|
+
while v > 0
|
|
370
|
+
v >>= 1
|
|
371
|
+
n += 1
|
|
372
|
+
end
|
|
373
|
+
n
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
# Decodes a Modified Huffman (1-D) row into changing elements. Returns
|
|
377
|
+
# false on a coding error.
|
|
378
|
+
def decode_1d_row(cur)
|
|
379
|
+
pos = @pos
|
|
380
|
+
win = @win
|
|
381
|
+
cols = @columns
|
|
382
|
+
white = CCITT_WHITE_RUNS
|
|
383
|
+
black = CCITT_BLACK_RUNS
|
|
384
|
+
a = 0
|
|
385
|
+
while true
|
|
386
|
+
run = 0
|
|
387
|
+
while true
|
|
388
|
+
k = pos >> 4
|
|
389
|
+
e = white[(((win[k] << 16) | win[k + 1]) >> (19 - (pos & 15))) & 0x1FFF]
|
|
390
|
+
if e < 0
|
|
391
|
+
@pos = pos
|
|
392
|
+
return false
|
|
393
|
+
end
|
|
394
|
+
pos += e & 15
|
|
395
|
+
e >>= 4
|
|
396
|
+
run += e
|
|
397
|
+
break if e < 64
|
|
398
|
+
end
|
|
399
|
+
a += run
|
|
400
|
+
a = cols if a > cols
|
|
401
|
+
cur << a
|
|
402
|
+
break if a >= cols
|
|
403
|
+
|
|
404
|
+
run = 0
|
|
405
|
+
while true
|
|
406
|
+
k = pos >> 4
|
|
407
|
+
e = black[(((win[k] << 16) | win[k + 1]) >> (19 - (pos & 15))) & 0x1FFF]
|
|
408
|
+
if e < 0
|
|
409
|
+
@pos = pos
|
|
410
|
+
return false
|
|
411
|
+
end
|
|
412
|
+
pos += e & 15
|
|
413
|
+
e >>= 4
|
|
414
|
+
run += e
|
|
415
|
+
break if e < 64
|
|
416
|
+
end
|
|
417
|
+
a += run
|
|
418
|
+
a = cols if a > cols
|
|
419
|
+
cur << a
|
|
420
|
+
break if a >= cols
|
|
421
|
+
end
|
|
422
|
+
@pos = pos
|
|
423
|
+
true
|
|
424
|
+
end
|
|
425
|
+
|
|
426
|
+
# Decodes a 2-D (READ) row against the reference row's changing
|
|
427
|
+
# elements +ref+ (terminated by sentinels equal to the width). Returns
|
|
428
|
+
# false on a coding error.
|
|
429
|
+
def decode_2d_row(ref, cur)
|
|
430
|
+
pos = @pos
|
|
431
|
+
win = @win
|
|
432
|
+
cols = @columns
|
|
433
|
+
modes = CCITT_MODES
|
|
434
|
+
a0 = -1
|
|
435
|
+
color = 0 # of the run starting at a0; changing element parity
|
|
436
|
+
bi = 0 # index of b1 in ref
|
|
437
|
+
while a0 < cols
|
|
438
|
+
k = pos >> 4
|
|
439
|
+
m = modes[(((win[k] << 16) | win[k + 1]) >> (25 - (pos & 15))) & 0x7F]
|
|
440
|
+
pos += m & 15
|
|
441
|
+
m >>= 4
|
|
442
|
+
if m < CCITT_PASS # vertical: a1 = b1 + (m - 3)
|
|
443
|
+
a1 = ref[bi] + m - 3
|
|
444
|
+
if a1 < a0 || a1 < 0
|
|
445
|
+
@pos = pos
|
|
446
|
+
return false
|
|
447
|
+
end
|
|
448
|
+
a1 = cols if a1 > cols
|
|
449
|
+
cur << a1
|
|
450
|
+
a0 = a1
|
|
451
|
+
color ^= 1
|
|
452
|
+
# b1 for the other colour: next element on the right, or (after
|
|
453
|
+
# VL) possibly the previous one.
|
|
454
|
+
bi += m >= 3 || bi == 0 ? 1 : -1
|
|
455
|
+
bi += 2 while (b = ref[bi]) <= a0 && b < cols
|
|
456
|
+
elsif m == CCITT_PASS
|
|
457
|
+
a0 = ref[bi + 1]
|
|
458
|
+
bi += 2
|
|
459
|
+
bi += 2 while (b = ref[bi]) <= a0 && b < cols
|
|
460
|
+
elsif m == CCITT_HORIZONTAL
|
|
461
|
+
first = color == 0 ? CCITT_WHITE_RUNS : CCITT_BLACK_RUNS
|
|
462
|
+
run1 = 0
|
|
463
|
+
while true
|
|
464
|
+
k = pos >> 4
|
|
465
|
+
e = first[(((win[k] << 16) | win[k + 1]) >> (19 - (pos & 15))) & 0x1FFF]
|
|
466
|
+
if e < 0
|
|
467
|
+
@pos = pos
|
|
468
|
+
return false
|
|
469
|
+
end
|
|
470
|
+
pos += e & 15
|
|
471
|
+
e >>= 4
|
|
472
|
+
run1 += e
|
|
473
|
+
break if e < 64
|
|
474
|
+
end
|
|
475
|
+
second = color == 0 ? CCITT_BLACK_RUNS : CCITT_WHITE_RUNS
|
|
476
|
+
run2 = 0
|
|
477
|
+
while true
|
|
478
|
+
k = pos >> 4
|
|
479
|
+
e = second[(((win[k] << 16) | win[k + 1]) >> (19 - (pos & 15))) & 0x1FFF]
|
|
480
|
+
if e < 0
|
|
481
|
+
@pos = pos
|
|
482
|
+
return false
|
|
483
|
+
end
|
|
484
|
+
pos += e & 15
|
|
485
|
+
e >>= 4
|
|
486
|
+
run2 += e
|
|
487
|
+
break if e < 64
|
|
488
|
+
end
|
|
489
|
+
a1 = (a0 < 0 ? 0 : a0) + run1
|
|
490
|
+
a1 = cols if a1 > cols
|
|
491
|
+
a0 = a1 + run2
|
|
492
|
+
a0 = cols if a0 > cols
|
|
493
|
+
cur << a1
|
|
494
|
+
cur << a0
|
|
495
|
+
bi += 2 while (b = ref[bi]) <= a0 && b < cols
|
|
496
|
+
else # extension (uncompressed mode) or an EOL inside the row
|
|
497
|
+
@pos = pos
|
|
498
|
+
return false
|
|
499
|
+
end
|
|
500
|
+
end
|
|
501
|
+
@pos = pos
|
|
502
|
+
true
|
|
503
|
+
end
|
|
504
|
+
|
|
505
|
+
# The row described by the changing elements +cur+ (+ref+: those of
|
|
506
|
+
# the row above, last painted as @last).
|
|
507
|
+
def row_for(cur, ref, bg, fg, bg_runs, fg_runs)
|
|
508
|
+
cols = @columns
|
|
509
|
+
return bg if cur[0] >= cols
|
|
510
|
+
return @last if same(cur, ref)
|
|
511
|
+
|
|
512
|
+
row = String.new(capacity: cols, encoding: Encoding::BINARY)
|
|
513
|
+
a = 0 # start of the next background run
|
|
514
|
+
i = 0
|
|
515
|
+
while (b = cur[i]) < cols
|
|
516
|
+
n = b - a
|
|
517
|
+
if n < CCITT_CACHED_RUNS
|
|
518
|
+
row << bg_runs[n]
|
|
519
|
+
else
|
|
520
|
+
row << bg.byteslice(0, n)
|
|
521
|
+
end
|
|
522
|
+
a = cur[i + 1]
|
|
523
|
+
n = a - b
|
|
524
|
+
if n < CCITT_CACHED_RUNS
|
|
525
|
+
row << fg_runs[n]
|
|
526
|
+
else
|
|
527
|
+
row << fg.byteslice(0, n)
|
|
528
|
+
end
|
|
529
|
+
i += 2
|
|
530
|
+
end
|
|
531
|
+
n = cols - a
|
|
532
|
+
if n < CCITT_CACHED_RUNS
|
|
533
|
+
row << bg_runs[n]
|
|
534
|
+
elsif n > 0
|
|
535
|
+
row << bg.byteslice(0, n)
|
|
536
|
+
end
|
|
537
|
+
@last = row
|
|
538
|
+
row
|
|
539
|
+
end
|
|
540
|
+
|
|
541
|
+
def same(cur, ref)
|
|
542
|
+
n = cur.size
|
|
543
|
+
return false unless n == ref.size
|
|
544
|
+
|
|
545
|
+
i = 0
|
|
546
|
+
while i < n
|
|
547
|
+
return false unless cur[i] == ref[i]
|
|
548
|
+
|
|
549
|
+
i += 1
|
|
550
|
+
end
|
|
551
|
+
true
|
|
552
|
+
end
|
|
553
|
+
end
|
|
554
|
+
end
|