qrio 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,40 @@
1
+ module Qrio
2
+ # track angle and distance between two Finder Patterns
3
+ class Neighbor
4
+ attr_reader :source, :destination, :angle, :distance
5
+
6
+ ANGLE = Math::PI / 8
7
+ ZERO = 0..ANGLE
8
+ NINETY = (ANGLE * 3)..(ANGLE * 5)
9
+ ONEEIGHTY = (ANGLE * 7)..(ANGLE * 8)
10
+
11
+ def initialize(source, destination)
12
+ @source = source
13
+ @destination = destination
14
+
15
+ source.neighbors << self
16
+
17
+ dx = destination.center.first - source.center.first
18
+ # images are top down, geometry is bottom up. invert.
19
+ dy = source.center.last - destination.center.last
20
+
21
+ @angle = Math.atan2(dy, dx)
22
+ @distance = Math.sqrt(dx ** 2 + dy ** 2)
23
+ end
24
+
25
+ def to_coordinates
26
+ [source.center, destination.center].flatten
27
+ end
28
+
29
+ def to_s
30
+ "N[#{ to_coordinates * ',' }]"
31
+ end
32
+
33
+ def right_angle?
34
+ ZERO.include?(angle.abs) ||
35
+ NINETY.include?(angle.abs) ||
36
+ ONEEIGHTY.include?(angle.abs)
37
+ end
38
+ end
39
+ end
40
+
@@ -0,0 +1,207 @@
1
+ module Qrio
2
+ class Qr
3
+ attr_reader :candidates, :matches, :finder_patterns, :qr_bounds, :qr
4
+ include ImageDumper
5
+
6
+ def initialize
7
+ initialize_storage
8
+ end
9
+
10
+ def self.load(filename)
11
+ instance = new
12
+ instance.load_image(filename)
13
+
14
+ instance.scan(:horizontal)
15
+ instance.scan(:vertical)
16
+
17
+ instance.filter_candidates
18
+ instance.find_intersections
19
+ instance.set_qr_bounds
20
+
21
+ instance.build_normalized_qr
22
+ instance.find_alignment_pattern
23
+
24
+ instance
25
+ end
26
+
27
+ def load_image(filename)
28
+ initialize_storage
29
+
30
+ image_type = File.extname(filename).upcase.gsub('.', '')
31
+ image_loader_class = "#{ image_type }ImageLoader"
32
+ image_loader_class = ImageLoader.const_get(image_loader_class) rescue nil
33
+
34
+ if image_loader_class.nil?
35
+ raise "Image type '#{ image_type }' not supported"
36
+ end
37
+
38
+ @input_matrix = image_loader_class.load(filename)
39
+ end
40
+
41
+ def scan(direction)
42
+ vectors = direction == :horizontal ? @input_matrix.rows : @input_matrix.columns
43
+ vectors.each_with_index do |vector, offset|
44
+ pattern = rle(vector)
45
+
46
+ if pattern.length >= 5
47
+ origin = 0
48
+ segment = pattern.slice!(0,4)
49
+
50
+ while next_length = pattern.shift
51
+ segment << next_length
52
+
53
+ if candidate = find_candidate(offset, origin, segment, direction)
54
+ add_candidate(candidate, direction)
55
+ end
56
+
57
+ origin += segment.shift
58
+ end
59
+ end
60
+ end
61
+ end
62
+
63
+ def find_candidate(offset, origin, segment, direction)
64
+ FinderPatternSlice.build_matching(offset, origin, segment, direction)
65
+ end
66
+
67
+ def add_candidate(new_candidate, direction)
68
+ added = false
69
+
70
+ @candidates[direction].each_with_index do |existing, index|
71
+ if new_candidate.adjacent?(existing)
72
+ @candidates[direction][index] = existing.union(new_candidate)
73
+ added = true
74
+ end
75
+ end
76
+
77
+ @candidates[direction] << new_candidate unless added
78
+ end
79
+
80
+ def filter_candidates
81
+ [:horizontal, :vertical].each do |direction|
82
+ @candidates[direction].uniq.each do |candidate|
83
+ @matches[direction] << candidate if candidate.matches_aspect_ratio?
84
+ end
85
+ end
86
+ end
87
+
88
+ # transform a vector of bits in to a run-length encoded vector of widths
89
+ # example: [1, 1, 1, 1, 0, 0, 1, 1, 1] => [4, 2, 3]
90
+ def rle(vector)
91
+ v = vector.dup
92
+
93
+ pattern = []
94
+ length = 1
95
+ last = v.shift
96
+
97
+ v.each do |current|
98
+ if current === last
99
+ length += 1
100
+ else
101
+ pattern << length
102
+ length = 1
103
+ last = current
104
+ end
105
+ end
106
+
107
+ pattern << length
108
+ end
109
+
110
+ # find intersections of horizontal and vertical slices, these
111
+ # are (likely) finder patterns
112
+ def find_intersections
113
+ @matches[:horizontal].each do |h|
114
+ @matches[:vertical].each do |v|
115
+ @finder_patterns << h.union(v) if h.intersects?(v)
116
+ end
117
+ end
118
+ end
119
+
120
+ def set_qr_bounds
121
+ if @finder_patterns.length >= 3
122
+ @sampling_grid = SamplingGrid.new(@input_matrix, @finder_patterns)
123
+ @qr_bounds = @sampling_grid.bounds
124
+ end
125
+ end
126
+
127
+ # extract the qr into a smaller matrix and rotate to standard orientation
128
+ def build_normalized_qr
129
+ return false if @sampling_grid.nil?
130
+
131
+ original_orientation = @sampling_grid.orientation
132
+
133
+ @sampling_grid.normalize
134
+ @extracted_matrix = @sampling_grid.matrix
135
+
136
+ bits = []
137
+ @sampling_grid.extracted_pixels do |x, y|
138
+ bits << @extracted_matrix[x, y]
139
+ end
140
+ @qr = QrMatrix.new(bits, @sampling_grid.logical_width, @sampling_grid.logical_height)
141
+
142
+ @translated_matches = {
143
+ :horizontal => [],
144
+ :vertical => []
145
+ }
146
+ @translated_finder_patterns = []
147
+ @translated_neighbors = []
148
+
149
+ @matches[:horizontal].each do |m|
150
+ m = m.translate(*@qr_bounds.top_left)
151
+ if original_orientation > 0
152
+ (4 - original_orientation).times do
153
+ m = m.rotate(@qr_bounds.width, @qr_bounds.height)
154
+ end
155
+ end
156
+ @translated_matches[:horizontal] << m
157
+ end
158
+
159
+ @matches[:vertical].each do |m|
160
+ m = m.translate(*@qr_bounds.top_left)
161
+ if original_orientation > 0
162
+ (4 - original_orientation).times do
163
+ m = m.rotate(@qr_bounds.width, @qr_bounds.height)
164
+ end
165
+ end
166
+ @translated_matches[:vertical] << m
167
+ end
168
+ end
169
+
170
+ def find_alignment_pattern
171
+ test_x = @sampling_grid.top_right.center.first - (@sampling_grid.block_width * 3)
172
+ test_y = @sampling_grid.bottom_left.center.last - (@sampling_grid.block_height * 3)
173
+
174
+ test_x = (test_x - @sampling_grid.block_width / 2.0).round
175
+ test_y = (test_y - @sampling_grid.block_height / 2.0).round
176
+
177
+ # TODO : this is where the AP *should* be, given no image skew.
178
+ # starting here, find center of nearest blob that "looks" like an AP.
179
+ # offset from predicted will be used in sampling grid to account for skew
180
+ @alignment_pattern = Qrio::Region.new(
181
+ test_x, test_y,
182
+ (test_x + @sampling_grid.block_width).round,
183
+ (test_y + @sampling_grid.block_height).round
184
+ )
185
+ end
186
+
187
+ private
188
+
189
+ def initialize_storage
190
+ @candidates = {
191
+ :horizontal => [],
192
+ :vertical => [],
193
+ }
194
+ @matches = {
195
+ :horizontal => [],
196
+ :vertical => [],
197
+ }
198
+ @finder_patterns = []
199
+ @input_matrix = @extracted_matrix = nil
200
+ end
201
+ end
202
+
203
+ def self.decode(filename)
204
+ qr = Qr.load(filename)
205
+ qr.decoded? ? qr.text : qr
206
+ end
207
+ end
@@ -0,0 +1,403 @@
1
+ module Qrio
2
+ class QrMatrix < Matrix
3
+ def initialize(*args)
4
+ super
5
+ @unmasked = false
6
+ end
7
+
8
+ FORMAT_MASK = 0b101_0100_0001_0010
9
+ ERROR_CORRECTION_LEVEL = %w( M L H Q )
10
+ MODE = {
11
+ 1 => :numeric,
12
+ 2 => :alphanumeric,
13
+ 4 => :ascii,
14
+ 8 => :kanji
15
+ }
16
+ WORD_WIDTHS = {
17
+ :numeric => { 1..9 => 10, 10..26 => 12, 27..40 => 14 },
18
+ :alphanumeric => { 1..9 => 9, 10..26 => 11, 27..40 => 13 },
19
+ :ascii => { 1..9 => 8, 10..26 => 16, 27..40 => 16 },
20
+ :kanji => { 1..9 => 8, 10..26 => 10, 27..40 => 12 }
21
+ }
22
+ ALIGNMENT_CENTERS = [
23
+ [],
24
+ [6, 18],
25
+ [6, 22],
26
+ [6, 26],
27
+ [6, 30],
28
+ [6, 34],
29
+ [6, 22, 38],
30
+ [6, 24, 42],
31
+ [6, 26, 46],
32
+ [6, 28, 50],
33
+
34
+ [6, 30, 54],
35
+ [6, 32, 58],
36
+ [6, 34, 62],
37
+ [6, 26, 46, 66],
38
+ [6, 26, 48, 70],
39
+ [6, 26, 50, 74],
40
+ [6, 30, 54, 78],
41
+ [6, 30, 56, 82],
42
+ [6, 30, 58, 86],
43
+ [6, 34, 62, 90],
44
+
45
+ [6, 28, 50, 72, 94],
46
+ [6, 26, 50, 74, 98],
47
+ [6, 30, 54, 78, 102],
48
+ [6, 28, 54, 80, 106],
49
+ [6, 32, 58, 84, 110],
50
+ [6, 30, 58, 86, 114],
51
+ [6, 34, 62, 90, 118],
52
+ [6, 26, 50, 74, 98, 122],
53
+ [6, 30, 54, 78, 102, 126],
54
+ [6, 26, 52, 78, 104, 130],
55
+
56
+ [6, 26, 52, 78, 104, 130],
57
+ [6, 30, 56, 82, 108, 134],
58
+ [6, 34, 60, 86, 112, 138],
59
+ [6, 30, 58, 86, 114, 142],
60
+ [6, 34, 62, 90, 118, 146],
61
+ [6, 30, 54, 78, 102, 126, 150],
62
+ [6, 24, 50, 76, 102, 128, 154],
63
+ [6, 28, 54, 80, 106, 132, 158],
64
+ [6, 32, 58, 84, 110, 136, 162],
65
+ [6, 26, 54, 82, 110, 138, 166],
66
+ [6, 30, 58, 86, 114, 142, 170]
67
+ ]
68
+
69
+ BLOCK_STRUCTURE = [
70
+ # to determine block structure, find the row corresponding to QR version
71
+ # (row 0 = version 1), then find the column corresponding to ECC level
72
+ # (M / L / H / Q)
73
+ #
74
+ # the result array will be:
75
+ # * block count
76
+ # * number of data bytes per block
77
+ # * nubmer of error correction bytes per block
78
+ # * (optional) number of blocks with one additional data byte
79
+ #
80
+ [[ 1, 16, 10 ], [ 1, 19, 7 ], [ 1, 9, 17 ], [ 1, 13, 13, ]],
81
+ [[ 1, 28, 16 ], [ 1, 34, 10 ], [ 1, 16, 28 ], [ 1, 22, 22, ]],
82
+ [[ 1, 44, 26 ], [ 1, 55, 15 ], [ 2, 13, 22 ], [ 2, 17, 18, ]],
83
+ [[ 2, 32, 18 ], [ 1, 80, 20 ], [ 4, 9, 16 ], [ 2, 24, 26, ]],
84
+ [[ 2, 43, 24 ], [ 1, 108, 26 ], [ 2, 11, 22, 2], [ 2, 15, 18, 2]],
85
+ [[ 4, 27, 16 ], [ 2, 68, 18 ], [ 4, 15, 28, ], [ 4, 19, 24, ]],
86
+ [[ 4, 31, 18 ], [ 2, 78, 20 ], [ 4, 13, 26, 1], [ 2, 14, 18, 4]],
87
+ [[ 2, 38, 22, 2], [ 2, 97, 24 ], [ 4, 14, 26, 2], [ 4, 18, 22, 2]],
88
+ [[ 3, 36, 22, 2], [ 2, 116, 30 ], [ 4, 12, 24, 4], [ 4, 16, 20, 4]],
89
+ [[ 4, 43, 26, 1], [ 2, 68, 18, 2], [ 6, 15, 28, 2], [ 6, 19, 24, 2]],
90
+
91
+ [[ 1, 50, 30, 4], [ 4, 81, 20 ], [ 3, 12, 24, 8], [ 4, 22, 28, 4]],
92
+ [[ 6, 36, 22, 2], [ 2, 92, 24, 2], [ 7, 14, 28, 4], [ 4, 20, 26, 6]],
93
+ [[ 8, 37, 22, 1], [ 4, 107, 26 ], [12, 11, 22, 4], [ 8, 20, 24, 4]],
94
+ [[ 4, 40, 24, 5], [ 3, 115, 30, 1], [11, 12, 24, 5], [11, 16, 20, 5]],
95
+ [[ 5, 41, 24, 5], [ 5, 87, 22, 1], [11, 12, 24, 7], [ 5, 24, 30, 7]],
96
+ [[ 7, 45, 28, 3], [ 5, 98, 24, 1], [ 3, 15, 30, 13], [15, 19, 24, 2]],
97
+ [[10, 46, 28, 1], [ 1, 107, 28, 5], [ 2, 14, 28, 17], [ 1, 22, 28, 15]],
98
+ [[ 9, 43, 26, 4], [ 5, 120, 30, 1], [ 2, 14, 28, 19], [17, 22, 28, 1]],
99
+ [[ 3, 44, 26, 11], [ 3, 113, 28, 4], [ 9, 13, 26, 16], [17, 21, 26, 4]],
100
+ [[ 3, 41, 26, 13], [ 3, 107, 20, 5], [15, 15, 28, 10], [15, 24, 30, 5]],
101
+
102
+ [[17, 42, 26 ], [ 4, 116, 28, 4], [19, 16, 30, 6], [17, 22, 28, 6]],
103
+ [[17, 46, 28 ], [ 2, 111, 28, 7], [34, 13, 24, ], [ 7, 24, 30, 16]],
104
+ [[ 4, 47, 28, 14], [ 4, 121, 30, 5], [16, 15, 30, 14], [11, 24, 30, 14]],
105
+ [[ 6, 45, 28, 14], [ 6, 117, 30, 4], [30, 16, 30, 2], [11, 24, 30, 16]],
106
+ [[ 8, 47, 28, 13], [ 8, 106, 26, 4], [22, 15, 30, 13], [ 7, 24, 30, 22]],
107
+ [[19, 46, 28, 4], [10, 114, 28, 2], [33, 16, 30, 4], [28, 22, 28, 6]],
108
+ [[22, 45, 28, 3], [ 8, 122, 30, 4], [12, 15, 30, 28], [ 8, 23, 30, 26]],
109
+ [[ 3, 45, 28, 23], [ 3, 117, 30, 10], [11, 15, 30, 31], [ 4, 24, 30, 31]],
110
+ [[21, 45, 28, 7], [ 7, 116, 30, 7], [19, 15, 30, 26], [ 1, 23, 30, 37]],
111
+ [[19, 47, 28, 10], [ 5, 115, 30, 10], [23, 15, 30, 25], [15, 24, 30, 25]],
112
+
113
+ [[ 2, 46, 28, 29], [13, 115, 30, 3], [23, 15, 30, 28], [42, 24, 30, 1]],
114
+ [[10, 46, 28, 23], [17, 115, 30 ], [19, 15, 30, 35], [10, 24, 30, 35]],
115
+ [[14, 46, 28, 21], [17, 115, 30, 1], [11, 15, 30, 46], [29, 24, 30, 19]],
116
+ [[14, 46, 28, 23], [13, 115, 30, 6], [59, 16, 30, 1], [44, 24, 30, 7]],
117
+ [[12, 47, 28, 26], [12, 121, 30, 7], [22, 15, 30, 41], [39, 24, 30, 14]],
118
+ [[ 6, 47, 28, 34], [ 6, 121, 30, 14], [ 2, 15, 30, 64], [46, 24, 30, 10]],
119
+ [[29, 46, 28, 14], [17, 122, 30, 4], [24, 15, 30, 46], [49, 24, 30, 10]],
120
+ [[13, 46, 28, 32], [ 4, 122, 30, 18], [42, 15, 30, 32], [48, 24, 30, 14]],
121
+ [[40, 47, 28, 7], [20, 117, 30, 4], [10, 15, 30, 67], [43, 24, 30, 22]],
122
+ [[18, 47, 28, 31], [19, 118, 30, 6], [20, 15, 30, 61], [34, 24, 30, 34]]
123
+ ]
124
+
125
+ def error_correction_level
126
+ ERROR_CORRECTION_LEVEL[read_format[:error_correction]]
127
+ end
128
+
129
+ def mask_pattern
130
+ read_format[:mask_pattern]
131
+ end
132
+
133
+ def version
134
+ (width - 17) / 4
135
+ end
136
+
137
+ def unmask
138
+ p = [
139
+ lambda{|x,y| (x + y) % 2 == 0 },
140
+ lambda{|x,y| x % 2 == 0 },
141
+ lambda{|x,y| y % 3 == 0 },
142
+ lambda{|x,y| (x + y) % 3 == 0 },
143
+ lambda{|x,y| ((x / 2) + (y / 3)) % 2 == 0 },
144
+ lambda{|x,y| prod = x * y; (prod % 2) + (prod % 3) == 0 },
145
+ lambda{|x,y| prod = x * y; (((prod % 2) + (prod % 3)) % 2) == 0 },
146
+ lambda{|x,y| prod = x * y; sum = x + y; (((prod % 3) + (sum % 2)) % 2) == 0 }
147
+ ][mask_pattern]
148
+
149
+ raise "could not load mask pattern #{ mask_pattern }" unless p
150
+
151
+ 0.upto(height - 1) do |y|
152
+ 0.upto(width - 1) do |x|
153
+ if data_or_correction?(x, y)
154
+ self[x, y] = self[x, y] ^ p.call(x, y)
155
+ end
156
+ end
157
+ end
158
+
159
+ @unmasked = ! @unmasked
160
+ end
161
+
162
+ # raw bytestream, as read from the QR symbol
163
+ def raw_bytes
164
+ @raw_bytes ||= read_raw_bytes
165
+ end
166
+
167
+ def to_s
168
+ str = ""
169
+ rows.each do |row|
170
+ row.each do |m|
171
+ str << (m ? '#' : ' ')
172
+ end
173
+ str << "\n"
174
+ end
175
+
176
+ str
177
+ end
178
+
179
+ def text
180
+ @text ||= begin
181
+ text = []
182
+
183
+ unmask unless @unmasked
184
+
185
+ # deinterlace
186
+ @blocks = []
187
+
188
+ byte_pointer = 0
189
+
190
+ # TODO : handle ragged block sizes
191
+ block_structure.each do |count, data, ecc|
192
+ data.times do |word_index|
193
+ block_count.times do |blk_index|
194
+ @blocks[blk_index] ||= []
195
+ @blocks[blk_index] << raw_bytes[byte_pointer]
196
+ byte_pointer += 1
197
+ end
198
+ end
199
+ end
200
+
201
+ @blocks = @blocks.flatten
202
+
203
+ set_mode
204
+
205
+ character_count = read
206
+
207
+ character_count.times do |idx|
208
+ byte = read
209
+ text << byte.chr
210
+ end
211
+
212
+ text.join
213
+ end
214
+ end
215
+
216
+ def mode
217
+ MODE[@mode]
218
+ end
219
+
220
+ def word_size
221
+ @word_size ||= begin
222
+ widths = WORD_WIDTHS[mode]
223
+ version_width = widths.detect{|k,v| k.include? version }
224
+
225
+ raise "Could not find word width" if version_width.nil?
226
+
227
+ version_width.last
228
+ end
229
+ end
230
+
231
+ def block_count
232
+ block_structure.inject(0){|sum, (blocks,data,ecc)| sum += blocks }
233
+ end
234
+
235
+ def block_structure
236
+ @block_structure ||= begin
237
+ @short_blocks = []
238
+ @long_blocks = []
239
+
240
+ params = block_structure_params.dup
241
+
242
+ @short_blocks = params.slice!(0,3)
243
+ structure = [@short_blocks]
244
+
245
+ unless params.empty?
246
+ @long_blocks = @short_blocks.dup
247
+ @long_blocks[0] = params[0]
248
+ structure << @long_blocks
249
+ end
250
+
251
+ structure
252
+ end
253
+ end
254
+
255
+ def ecc_bytes_per_block
256
+ block_structure.first.last
257
+ end
258
+
259
+ def data_or_correction?(x, y)
260
+ ! in_finder_pattern?(x, y) &&
261
+ ! in_alignment_pattern?(x, y) &&
262
+ ! in_alignment_line?(x, y)
263
+ end
264
+
265
+ def in_finder_pattern?(x, y)
266
+ (x < 9 && y < 9) ||
267
+ (x > (width - 9) && y < 9) ||
268
+ (x < 9 && y > (height - 9))
269
+ end
270
+
271
+ def in_alignment_pattern?(x, y)
272
+ return false if version == 1
273
+
274
+ alignment_centers = ALIGNMENT_CENTERS[version - 1]
275
+
276
+ cy = alignment_centers.detect{|c| (c - y).abs <= 2 }
277
+ cx = alignment_centers.detect{|c| (c - x).abs <= 2 }
278
+
279
+ cx && cy && ! in_finder_pattern?(cx, cy)
280
+ end
281
+
282
+ def draw_alignment_patterns
283
+ rows = ALIGNMENT_CENTERS[version - 1].dup
284
+ cols = rows.dup
285
+
286
+ cols.each do |cy|
287
+ rows.each do |cx|
288
+ unless in_finder_pattern?(cx, cy)
289
+ ((cy - 2)...(cy + 2)).each do |y|
290
+ ((cx - 2)...(cx + 2)).each do |x|
291
+ self[x, y] = (cx - x).abs == 2 ||
292
+ (cy - y).abs == 2 ||
293
+ (x == cx && y == cy)
294
+ end
295
+ end
296
+ end
297
+ end
298
+ end
299
+ end
300
+
301
+ def in_alignment_line?(x, y)
302
+ (x == 6) || (y == 6)
303
+ end
304
+
305
+ private
306
+
307
+ def set_mode
308
+ @mode ||= begin
309
+ @pointer ||= 0
310
+ mode_number = read(4)
311
+ end
312
+ raise "Unknown mode #{ @mode }" unless mode
313
+ end
314
+
315
+ def set_data_length
316
+ @data_length ||= read
317
+ end
318
+
319
+ def block_structure_params
320
+ BLOCK_STRUCTURE[version - 1][read_format[:error_correction]].dup
321
+ end
322
+
323
+ # read +bits+ bits from bitstream and return the binary
324
+ def read(bits=nil)
325
+ bits ||= word_size
326
+ binary = []
327
+
328
+ bits.times do |i|
329
+ block_index, bit_index = @pointer.divmod(8)
330
+ data = @blocks[block_index] || 0
331
+ binary << (((data >> (7 - bit_index)) & 1) == 1)
332
+ @pointer += 1
333
+ end
334
+
335
+ binary.map{|b| b ? '1' : '0' }.join.to_i(2)
336
+ end
337
+
338
+ def read_raw_bytes
339
+ @raw_bytes = []
340
+ @byte = []
341
+
342
+ (0..(width - 3)).step(2) do |bcol|
343
+ bcol = width - 1 - bcol
344
+ scanning_up = ((bcol / 2) % 2) == 0
345
+ bcol -= 1 if bcol <= 6
346
+
347
+ (0..(height - 1)).each do |brow|
348
+ brow = height - 1 - brow if scanning_up
349
+
350
+ add_bit(bcol, brow)
351
+ add_bit(bcol - 1, brow)
352
+ end
353
+ end
354
+
355
+ @raw_bytes
356
+ end
357
+
358
+ def add_bit(x, y)
359
+ if data_or_correction?(x, y)
360
+ @byte.push self[x, y]
361
+
362
+ if @byte.length == 8
363
+ @raw_bytes << @byte.map{|b| b ? '1' : '0' }.join.to_i(2)
364
+ @byte = []
365
+ end
366
+ end
367
+ end
368
+
369
+ def read_format
370
+ @format ||= begin
371
+ bits = 0
372
+
373
+ 0.upto(5) do |x|
374
+ bits = bits << 1
375
+ bits += 1 if self[x, 8]
376
+ end
377
+
378
+ bits = bits << 1
379
+ bits += 1 if self[7, 8]
380
+ bits = bits << 1
381
+ bits += 1 if self[8, 8]
382
+ bits = bits << 1
383
+ bits += 1 if self[8, 7]
384
+
385
+ 5.downto(0) do |y|
386
+ bits = bits << 1
387
+ bits += 1 if self[8, y]
388
+ end
389
+
390
+ format_string = (bits ^ FORMAT_MASK).to_s(2).rjust(15, '0')
391
+
392
+ # TODO check BCH error detection
393
+ # TODO if too many errors, read alternate format blocks
394
+
395
+ {
396
+ :error_correction => format_string[0,2].to_i(2),
397
+ :mask_pattern => format_string[2,3].to_i(2),
398
+ :bch_error_detection => format_string[5..-1].to_i(2)
399
+ }
400
+ end
401
+ end
402
+ end
403
+ end