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,475 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Binarizer kernels: local-threshold binarization of a grayscale buffer
|
|
4
|
+
# into a bit buffer (one byte per pixel, 1 = dark). Kernel files are plain
|
|
5
|
+
# Ruby in the subset that Spinel compiles (see DESIGN.md, "Kernels"): the
|
|
6
|
+
# optional qr_reader-native gem builds them into a C extension, otherwise
|
|
7
|
+
# they run as they are. See QrReader::Binarizer for the methods.
|
|
8
|
+
module QrReaderKernels
|
|
9
|
+
# Blocks with a luminance range up to this are treated as flat.
|
|
10
|
+
BIN_MIN_RANGE = 24
|
|
11
|
+
|
|
12
|
+
# zxing-cpp-like hybrid binarization of a width x height (both >= 40)
|
|
13
|
+
# gray buffer: per 8x8 block the threshold (min + max) / 2 if it has
|
|
14
|
+
# contrast, thresholds across edges that run exactly along block borders,
|
|
15
|
+
# 5x5 block means of the thresholds, gaps filled in scan order. Returns
|
|
16
|
+
# the bit buffer.
|
|
17
|
+
# spinel-sample: QrReaderKernels.binarize_hybrid("\x10\xf0\x80\x20".b * 400, 40, 40)
|
|
18
|
+
def self.binarize_hybrid(gray, width, height)
|
|
19
|
+
sub_width = (width + 7) >> 3
|
|
20
|
+
sub_height = (height + 7) >> 3
|
|
21
|
+
size = sub_width * sub_height
|
|
22
|
+
block = Array.new(size, 0)
|
|
23
|
+
lows = Array.new(size, 0)
|
|
24
|
+
highs = Array.new(size, 0)
|
|
25
|
+
by = 0
|
|
26
|
+
while by < sub_height
|
|
27
|
+
y0 = by << 3
|
|
28
|
+
y0 = height - 8 if y0 > height - 8
|
|
29
|
+
bin_midranges_row(gray, width, y0 * width, block, lows, highs, by * sub_width, sub_width)
|
|
30
|
+
by += 1
|
|
31
|
+
end
|
|
32
|
+
by = 0
|
|
33
|
+
while by < sub_height
|
|
34
|
+
bin_bridge_row(block, lows, highs, by * sub_width, sub_width, by + 1 < sub_height)
|
|
35
|
+
by += 1
|
|
36
|
+
end
|
|
37
|
+
thresholds = bin_smooth(block, sub_width, sub_height)
|
|
38
|
+
bin_apply(gray, width, height, thresholds, lows, highs)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# ZXing (Java) HybridBinarizer-like local means: per 8x8 block its mean,
|
|
42
|
+
# or for flat blocks half its minimum unless the neighbours suggest a dark
|
|
43
|
+
# area; averaged over 5x5 blocks. Returns the bit buffer (width, height
|
|
44
|
+
# >= 40).
|
|
45
|
+
# spinel-sample: QrReaderKernels.binarize_local_mean("\x10\xf0\x80\x20".b * 400, 40, 40)
|
|
46
|
+
def self.binarize_local_mean(gray, width, height)
|
|
47
|
+
sub_width = (width + 7) >> 3
|
|
48
|
+
sub_height = (height + 7) >> 3
|
|
49
|
+
size = sub_width * sub_height
|
|
50
|
+
points = Array.new(size, 0)
|
|
51
|
+
by = 0
|
|
52
|
+
while by < sub_height
|
|
53
|
+
y0 = by << 3
|
|
54
|
+
y0 = height - 8 if y0 > height - 8
|
|
55
|
+
bin_black_points_row(gray, width, y0 * width, points, by * sub_width, sub_width)
|
|
56
|
+
by += 1
|
|
57
|
+
end
|
|
58
|
+
hsum = Array.new(size, 0)
|
|
59
|
+
by = 0
|
|
60
|
+
while by < sub_height
|
|
61
|
+
bin_five_sums_row(points, hsum, by * sub_width, sub_width)
|
|
62
|
+
by += 1
|
|
63
|
+
end
|
|
64
|
+
means = Array.new(size, 0)
|
|
65
|
+
by = 0
|
|
66
|
+
while by < sub_height
|
|
67
|
+
centre = by
|
|
68
|
+
centre = 2 if centre < 2
|
|
69
|
+
centre = sub_height - 3 if centre > sub_height - 3
|
|
70
|
+
bin_five_means_row(hsum, means, by * sub_width, centre * sub_width, sub_width)
|
|
71
|
+
by += 1
|
|
72
|
+
end
|
|
73
|
+
# Block statistics that never classify a block as uniform.
|
|
74
|
+
lows = Array.new(size, 0)
|
|
75
|
+
highs = Array.new(size, 256)
|
|
76
|
+
bin_apply(gray, width, height, means, lows, highs)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Black points of one row of 8x8 blocks whose top row starts at +off0+
|
|
80
|
+
# (edge blocks shifted inwards to stay inside the image).
|
|
81
|
+
def self.bin_black_points_row(data, width, off0, points, base, sub_width)
|
|
82
|
+
max_x = width - 8
|
|
83
|
+
bx = 0
|
|
84
|
+
while bx < sub_width
|
|
85
|
+
x0 = bx << 3
|
|
86
|
+
x0 = max_x if x0 > max_x
|
|
87
|
+
off = off0 + x0
|
|
88
|
+
sum = 0
|
|
89
|
+
min = 255
|
|
90
|
+
max = 0
|
|
91
|
+
rows = 0
|
|
92
|
+
while rows < 8
|
|
93
|
+
a = data.getbyte(off)
|
|
94
|
+
b = data.getbyte(off + 1)
|
|
95
|
+
c = data.getbyte(off + 2)
|
|
96
|
+
d = data.getbyte(off + 3)
|
|
97
|
+
e = data.getbyte(off + 4)
|
|
98
|
+
f = data.getbyte(off + 5)
|
|
99
|
+
g = data.getbyte(off + 6)
|
|
100
|
+
h = data.getbyte(off + 7)
|
|
101
|
+
sum += a + b + c + d + e + f + g + h
|
|
102
|
+
min = a if a < min
|
|
103
|
+
max = a if a > max
|
|
104
|
+
min = b if b < min
|
|
105
|
+
max = b if b > max
|
|
106
|
+
min = c if c < min
|
|
107
|
+
max = c if c > max
|
|
108
|
+
min = d if d < min
|
|
109
|
+
max = d if d > max
|
|
110
|
+
min = e if e < min
|
|
111
|
+
max = e if e > max
|
|
112
|
+
min = f if f < min
|
|
113
|
+
max = f if f > max
|
|
114
|
+
min = g if g < min
|
|
115
|
+
max = g if g > max
|
|
116
|
+
min = h if h < min
|
|
117
|
+
max = h if h > max
|
|
118
|
+
off += width
|
|
119
|
+
rows += 1
|
|
120
|
+
next if max - min <= BIN_MIN_RANGE
|
|
121
|
+
|
|
122
|
+
# Enough contrast already: only the sum is still needed.
|
|
123
|
+
while rows < 8
|
|
124
|
+
sum += data.getbyte(off) + data.getbyte(off + 1) + data.getbyte(off + 2) + data.getbyte(off + 3) +
|
|
125
|
+
data.getbyte(off + 4) + data.getbyte(off + 5) + data.getbyte(off + 6) + data.getbyte(off + 7)
|
|
126
|
+
off += width
|
|
127
|
+
rows += 1
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
point = sum >> 6
|
|
132
|
+
if max - min <= BIN_MIN_RANGE
|
|
133
|
+
# A flat block is assumed to be background (white it out) unless
|
|
134
|
+
# its neighbours suggest it lies inside a dark area.
|
|
135
|
+
point = min >> 1
|
|
136
|
+
if base > 0 && bx > 0
|
|
137
|
+
up = base - sub_width + bx
|
|
138
|
+
neighbours = (points[up] + 2 * points[base + bx - 1] + points[up - 1]) >> 2
|
|
139
|
+
point = neighbours if min < neighbours
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
points[base + bx] = point
|
|
143
|
+
bx += 1
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Sums over the 5 blocks around each block of one block row (window
|
|
148
|
+
# clamped to the row).
|
|
149
|
+
def self.bin_five_sums_row(values, hsum, base, sub_width)
|
|
150
|
+
last = sub_width - 3
|
|
151
|
+
bx = 0
|
|
152
|
+
while bx < sub_width
|
|
153
|
+
i = base + (bx < 2 ? 2 : (bx > last ? last : bx))
|
|
154
|
+
hsum[base + bx] = values[i - 2] + values[i - 1] + values[i] + values[i + 1] + values[i + 2]
|
|
155
|
+
bx += 1
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Means over 5 block rows (centred on the row starting at +centre+).
|
|
160
|
+
def self.bin_five_means_row(hsum, means, base, centre, sub_width)
|
|
161
|
+
two_rows = 2 * sub_width
|
|
162
|
+
bx = 0
|
|
163
|
+
while bx < sub_width
|
|
164
|
+
i = centre + bx
|
|
165
|
+
means[base + bx] = (hsum[i - two_rows] + hsum[i - sub_width] + hsum[i] +
|
|
166
|
+
hsum[i + sub_width] + hsum[i + two_rows]) / 25
|
|
167
|
+
bx += 1
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# (min + max) / 2 of each block of one block row (top pixel row at
|
|
172
|
+
# +off0+), 0 for flat blocks; minima and maxima into +lows+ / +highs+.
|
|
173
|
+
# Min and max of each 8 pixel row segment come from pairwise
|
|
174
|
+
# comparisons (12 instead of 16 per segment).
|
|
175
|
+
def self.bin_midranges_row(data, width, off0, out, lows, highs, base, sub_width)
|
|
176
|
+
max_x = width - 8
|
|
177
|
+
bx = 0
|
|
178
|
+
while bx < sub_width
|
|
179
|
+
x0 = bx << 3
|
|
180
|
+
x0 = max_x if x0 > max_x
|
|
181
|
+
off = off0 + x0
|
|
182
|
+
min = 255
|
|
183
|
+
max = 0
|
|
184
|
+
stop = off + 8 * width
|
|
185
|
+
while off < stop
|
|
186
|
+
a = data.getbyte(off)
|
|
187
|
+
b = data.getbyte(off + 1)
|
|
188
|
+
if a < b
|
|
189
|
+
lo = a
|
|
190
|
+
hi = b
|
|
191
|
+
else
|
|
192
|
+
lo = b
|
|
193
|
+
hi = a
|
|
194
|
+
end
|
|
195
|
+
a = data.getbyte(off + 2)
|
|
196
|
+
b = data.getbyte(off + 3)
|
|
197
|
+
if a < b
|
|
198
|
+
lo = a if a < lo
|
|
199
|
+
hi = b if b > hi
|
|
200
|
+
else
|
|
201
|
+
lo = b if b < lo
|
|
202
|
+
hi = a if a > hi
|
|
203
|
+
end
|
|
204
|
+
a = data.getbyte(off + 4)
|
|
205
|
+
b = data.getbyte(off + 5)
|
|
206
|
+
if a < b
|
|
207
|
+
lo = a if a < lo
|
|
208
|
+
hi = b if b > hi
|
|
209
|
+
else
|
|
210
|
+
lo = b if b < lo
|
|
211
|
+
hi = a if a > hi
|
|
212
|
+
end
|
|
213
|
+
a = data.getbyte(off + 6)
|
|
214
|
+
b = data.getbyte(off + 7)
|
|
215
|
+
if a < b
|
|
216
|
+
lo = a if a < lo
|
|
217
|
+
hi = b if b > hi
|
|
218
|
+
else
|
|
219
|
+
lo = b if b < lo
|
|
220
|
+
hi = a if a > hi
|
|
221
|
+
end
|
|
222
|
+
min = lo if lo < min
|
|
223
|
+
max = hi if hi > max
|
|
224
|
+
off += width
|
|
225
|
+
end
|
|
226
|
+
i = base + bx
|
|
227
|
+
lows[i] = min
|
|
228
|
+
highs[i] = max
|
|
229
|
+
out[i] = max - min > BIN_MIN_RANGE ? (max + min) >> 1 : 0
|
|
230
|
+
bx += 1
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
# Gives flat blocks of the block row starting at +base+ a threshold when
|
|
235
|
+
# an edge runs along their border: a flat right (or lower) neighbour
|
|
236
|
+
# whose luminance range is more than BIN_MIN_RANGE away from theirs. Both
|
|
237
|
+
# get the threshold halfway across the gap (set thresholds are kept). A
|
|
238
|
+
# smooth gradient never qualifies.
|
|
239
|
+
def self.bin_bridge_row(block, lows, highs, base, sub_width, below)
|
|
240
|
+
i = base
|
|
241
|
+
last = base + sub_width - 1
|
|
242
|
+
while i <= last
|
|
243
|
+
lo = lows[i]
|
|
244
|
+
hi = highs[i]
|
|
245
|
+
if hi - lo <= BIN_MIN_RANGE
|
|
246
|
+
bin_bridge_pair(block, lows, highs, i, i + 1, lo, hi) if i < last
|
|
247
|
+
bin_bridge_pair(block, lows, highs, i, i + sub_width, lo, hi) if below
|
|
248
|
+
end
|
|
249
|
+
i += 1
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
# bin_bridge_row for flat block i (range lo..hi) and block j.
|
|
254
|
+
def self.bin_bridge_pair(block, lows, highs, i, j, lo, hi)
|
|
255
|
+
lo2 = lows[j]
|
|
256
|
+
hi2 = highs[j]
|
|
257
|
+
return if hi2 - lo2 > BIN_MIN_RANGE
|
|
258
|
+
|
|
259
|
+
t = 0
|
|
260
|
+
if lo2 - hi > BIN_MIN_RANGE
|
|
261
|
+
t = (hi + lo2) >> 1
|
|
262
|
+
elsif lo - hi2 > BIN_MIN_RANGE
|
|
263
|
+
t = (hi2 + lo) >> 1
|
|
264
|
+
else
|
|
265
|
+
return
|
|
266
|
+
end
|
|
267
|
+
block[i] = t if block[i] == 0
|
|
268
|
+
block[j] = t if block[j] == 0
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
# Mean over the 5x5 blocks around each block of the thresholds that are
|
|
272
|
+
# set (the block itself counts twice), via separable window sums of the
|
|
273
|
+
# thresholds and of an is-set indicator; then gaps filled in scan order.
|
|
274
|
+
def self.bin_smooth(block, sub_width, sub_height)
|
|
275
|
+
size = sub_width * sub_height
|
|
276
|
+
hsum = Array.new(size, 0)
|
|
277
|
+
hcount = Array.new(size, 0)
|
|
278
|
+
by = 0
|
|
279
|
+
while by < sub_height
|
|
280
|
+
bin_window_row(block, hsum, hcount, by * sub_width, sub_width)
|
|
281
|
+
by += 1
|
|
282
|
+
end
|
|
283
|
+
out = Array.new(size, 0)
|
|
284
|
+
by = 0
|
|
285
|
+
while by < sub_height
|
|
286
|
+
top = by
|
|
287
|
+
top = 2 if top < 2
|
|
288
|
+
top = sub_height - 3 if top > sub_height - 3
|
|
289
|
+
bin_window_means(block, hsum, hcount, out, by * sub_width, (top - 2) * sub_width, sub_width)
|
|
290
|
+
by += 1
|
|
291
|
+
end
|
|
292
|
+
bin_fill_gaps(out, sub_width)
|
|
293
|
+
out
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
# Sums and counts of the set (positive) thresholds over the 5 blocks
|
|
297
|
+
# around each block of one block row (window clamped to the row).
|
|
298
|
+
def self.bin_window_row(block, hsum, hcount, base, sub_width)
|
|
299
|
+
last = sub_width - 3
|
|
300
|
+
bx = 0
|
|
301
|
+
while bx < sub_width
|
|
302
|
+
i = base + (bx < 2 ? 0 : (bx > last ? last - 2 : bx - 2))
|
|
303
|
+
a = block[i]
|
|
304
|
+
b = block[i + 1]
|
|
305
|
+
c = block[i + 2]
|
|
306
|
+
d = block[i + 3]
|
|
307
|
+
e = block[i + 4]
|
|
308
|
+
sum = 0
|
|
309
|
+
count = 0
|
|
310
|
+
if a > 0
|
|
311
|
+
sum += a
|
|
312
|
+
count += 1
|
|
313
|
+
end
|
|
314
|
+
if b > 0
|
|
315
|
+
sum += b
|
|
316
|
+
count += 1
|
|
317
|
+
end
|
|
318
|
+
if c > 0
|
|
319
|
+
sum += c
|
|
320
|
+
count += 1
|
|
321
|
+
end
|
|
322
|
+
if d > 0
|
|
323
|
+
sum += d
|
|
324
|
+
count += 1
|
|
325
|
+
end
|
|
326
|
+
if e > 0
|
|
327
|
+
sum += e
|
|
328
|
+
count += 1
|
|
329
|
+
end
|
|
330
|
+
hsum[base + bx] = sum
|
|
331
|
+
hcount[base + bx] = count
|
|
332
|
+
bx += 1
|
|
333
|
+
end
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
# 5x5 means of one block row from the row sums of the 5 block rows
|
|
337
|
+
# starting at +top+ (0 where no threshold is set nearby).
|
|
338
|
+
def self.bin_window_means(block, hsum, hcount, out, base, top, sub_width)
|
|
339
|
+
s1 = sub_width
|
|
340
|
+
s2 = 2 * sub_width
|
|
341
|
+
s3 = 3 * sub_width
|
|
342
|
+
s4 = 4 * sub_width
|
|
343
|
+
bx = 0
|
|
344
|
+
while bx < sub_width
|
|
345
|
+
i = top + bx
|
|
346
|
+
sum = hsum[i] + hsum[i + s1] + hsum[i + s2] + hsum[i + s3] + hsum[i + s4]
|
|
347
|
+
count = hcount[i] + hcount[i + s1] + hcount[i + s2] + hcount[i + s3] + hcount[i + s4]
|
|
348
|
+
centre = block[base + bx]
|
|
349
|
+
if centre > 0
|
|
350
|
+
sum += 2 * centre
|
|
351
|
+
count += 2
|
|
352
|
+
end
|
|
353
|
+
out[base + bx] = count > 0 ? sum / count : 0
|
|
354
|
+
bx += 1
|
|
355
|
+
end
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
# Sets every 0 entry to the previous non-zero one in scan order (leading
|
|
359
|
+
# zeros to the first non-zero one); all zero (no contrast anywhere)
|
|
360
|
+
# becomes all -1: everything light.
|
|
361
|
+
def self.bin_fill_gaps(values, row_size)
|
|
362
|
+
n = values.size
|
|
363
|
+
first = 0
|
|
364
|
+
first += 1 while first < n && values[first] <= 0
|
|
365
|
+
if first == n
|
|
366
|
+
i = 0
|
|
367
|
+
while i < n
|
|
368
|
+
values[i] = -1
|
|
369
|
+
i += 1
|
|
370
|
+
end
|
|
371
|
+
return
|
|
372
|
+
end
|
|
373
|
+
last = values[first]
|
|
374
|
+
i = 0
|
|
375
|
+
while i < n
|
|
376
|
+
stop = i + row_size
|
|
377
|
+
stop = n if stop > n
|
|
378
|
+
last = bin_fill_span(values, i, stop, last)
|
|
379
|
+
i = stop
|
|
380
|
+
end
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
# bin_fill_gaps for values[i...stop]; returns the last non-zero value.
|
|
384
|
+
def self.bin_fill_span(values, i, stop, last)
|
|
385
|
+
while i < stop
|
|
386
|
+
t = values[i]
|
|
387
|
+
if t == 0
|
|
388
|
+
values[i] = last
|
|
389
|
+
else
|
|
390
|
+
last = t
|
|
391
|
+
end
|
|
392
|
+
i += 1
|
|
393
|
+
end
|
|
394
|
+
last
|
|
395
|
+
end
|
|
396
|
+
|
|
397
|
+
# Bit buffer of the pixels at or below their block's threshold (columns
|
|
398
|
+
# past the last full block use the last block). Blocks whose whole range
|
|
399
|
+
# (+lows+ / +highs+ over the 8x8 pixels the threshold was derived from,
|
|
400
|
+
# which contain every pixel that uses it) lies on one side of their
|
|
401
|
+
# threshold are filled without looking at the pixels.
|
|
402
|
+
def self.bin_apply(gray, width, height, thresholds, lows, highs)
|
|
403
|
+
sub_width = (width + 7) >> 3
|
|
404
|
+
patterns = bin_patterns
|
|
405
|
+
out = String.new(capacity: width * height, encoding: Encoding::BINARY)
|
|
406
|
+
y = 0
|
|
407
|
+
off = 0
|
|
408
|
+
while y < height
|
|
409
|
+
out << bin_apply_row(gray, off, width, thresholds, lows, highs, (y >> 3) * sub_width, sub_width, patterns)
|
|
410
|
+
off += width
|
|
411
|
+
y += 1
|
|
412
|
+
end
|
|
413
|
+
out
|
|
414
|
+
end
|
|
415
|
+
|
|
416
|
+
# The 256 rows of 8 bits: pattern m has byte k set iff bit k of m is.
|
|
417
|
+
def self.bin_patterns
|
|
418
|
+
patterns = []
|
|
419
|
+
m = 0
|
|
420
|
+
while m < 256
|
|
421
|
+
s = String.new(capacity: 8, encoding: Encoding::BINARY)
|
|
422
|
+
k = 0
|
|
423
|
+
while k < 8
|
|
424
|
+
s << ((m >> k) & 1)
|
|
425
|
+
k += 1
|
|
426
|
+
end
|
|
427
|
+
patterns << s
|
|
428
|
+
m += 1
|
|
429
|
+
end
|
|
430
|
+
patterns
|
|
431
|
+
end
|
|
432
|
+
|
|
433
|
+
# One row of bin_apply (block row starting at +base+). Each 8 pixel
|
|
434
|
+
# segment becomes one appended pattern (String#setbyte per pixel costs
|
|
435
|
+
# more than the comparisons).
|
|
436
|
+
def self.bin_apply_row(gray, off, width, thresholds, lows, highs, base, sub_width, patterns)
|
|
437
|
+
full = width >> 3
|
|
438
|
+
row = String.new(capacity: width, encoding: Encoding::BINARY)
|
|
439
|
+
light = patterns[0]
|
|
440
|
+
dark = patterns[255]
|
|
441
|
+
b = base
|
|
442
|
+
stop = base + full
|
|
443
|
+
i = off
|
|
444
|
+
while b < stop
|
|
445
|
+
t = thresholds[b]
|
|
446
|
+
if lows[b] > t
|
|
447
|
+
row << light
|
|
448
|
+
elsif highs[b] <= t
|
|
449
|
+
row << dark
|
|
450
|
+
else
|
|
451
|
+
m = 0
|
|
452
|
+
m |= 1 if gray.getbyte(i) <= t
|
|
453
|
+
m |= 2 if gray.getbyte(i + 1) <= t
|
|
454
|
+
m |= 4 if gray.getbyte(i + 2) <= t
|
|
455
|
+
m |= 8 if gray.getbyte(i + 3) <= t
|
|
456
|
+
m |= 16 if gray.getbyte(i + 4) <= t
|
|
457
|
+
m |= 32 if gray.getbyte(i + 5) <= t
|
|
458
|
+
m |= 64 if gray.getbyte(i + 6) <= t
|
|
459
|
+
m |= 128 if gray.getbyte(i + 7) <= t
|
|
460
|
+
row << patterns[m]
|
|
461
|
+
end
|
|
462
|
+
i += 8
|
|
463
|
+
b += 1
|
|
464
|
+
end
|
|
465
|
+
return row if full == sub_width
|
|
466
|
+
|
|
467
|
+
t = thresholds[base + sub_width - 1]
|
|
468
|
+
stop = off + width
|
|
469
|
+
while i < stop
|
|
470
|
+
row << (gray.getbyte(i) <= t ? 1 : 0)
|
|
471
|
+
i += 1
|
|
472
|
+
end
|
|
473
|
+
row
|
|
474
|
+
end
|
|
475
|
+
end
|