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
+ module QrReader
4
+ # Static properties of a QR Code Model 2 symbol version (1..40):
5
+ # dimension, alignment pattern centres, error correction block layout and
6
+ # the 18-bit version information word (versions 7+).
7
+ class SymbolVersion
8
+ # Error correction levels in the order of their 2-bit format code.
9
+ # Format bits: L = 01, M = 00, Q = 11, H = 10.
10
+ EC_LEVELS = %i[L M Q H].freeze
11
+ EC_LEVEL_FOR_BITS = %i[M L H Q].freeze # index = 2-bit value from format info
12
+
13
+ # Per version, per level (L, M, Q, H):
14
+ # [ec_codewords_per_block, count1, data_codewords1, count2, data_codewords2]
15
+ # (count2 / data_codewords2 are 0 when there is a single block group).
16
+ EC_TABLE = [
17
+ nil,
18
+ [[7, 1, 19, 0, 0], [10, 1, 16, 0, 0], [13, 1, 13, 0, 0], [17, 1, 9, 0, 0]],
19
+ [[10, 1, 34, 0, 0], [16, 1, 28, 0, 0], [22, 1, 22, 0, 0], [28, 1, 16, 0, 0]],
20
+ [[15, 1, 55, 0, 0], [26, 1, 44, 0, 0], [18, 2, 17, 0, 0], [22, 2, 13, 0, 0]],
21
+ [[20, 1, 80, 0, 0], [18, 2, 32, 0, 0], [26, 2, 24, 0, 0], [16, 4, 9, 0, 0]],
22
+ [[26, 1, 108, 0, 0], [24, 2, 43, 0, 0], [18, 2, 15, 2, 16], [22, 2, 11, 2, 12]],
23
+ [[18, 2, 68, 0, 0], [16, 4, 27, 0, 0], [24, 4, 19, 0, 0], [28, 4, 15, 0, 0]],
24
+ [[20, 2, 78, 0, 0], [18, 4, 31, 0, 0], [18, 2, 14, 4, 15], [26, 4, 13, 1, 14]],
25
+ [[24, 2, 97, 0, 0], [22, 2, 38, 2, 39], [22, 4, 18, 2, 19], [26, 4, 14, 2, 15]],
26
+ [[30, 2, 116, 0, 0], [22, 3, 36, 2, 37], [20, 4, 16, 4, 17], [24, 4, 12, 4, 13]],
27
+ [[18, 2, 68, 2, 69], [26, 4, 43, 1, 44], [24, 6, 19, 2, 20], [28, 6, 15, 2, 16]],
28
+ [[20, 4, 81, 0, 0], [30, 1, 50, 4, 51], [28, 4, 22, 4, 23], [24, 3, 12, 8, 13]],
29
+ [[24, 2, 92, 2, 93], [22, 6, 36, 2, 37], [26, 4, 20, 6, 21], [28, 7, 14, 4, 15]],
30
+ [[26, 4, 107, 0, 0], [22, 8, 37, 1, 38], [24, 8, 20, 4, 21], [22, 12, 11, 4, 12]],
31
+ [[30, 3, 115, 1, 116], [24, 4, 40, 5, 41], [20, 11, 16, 5, 17], [24, 11, 12, 5, 13]],
32
+ [[22, 5, 87, 1, 88], [24, 5, 41, 5, 42], [30, 5, 24, 7, 25], [24, 11, 12, 7, 13]],
33
+ [[24, 5, 98, 1, 99], [28, 7, 45, 3, 46], [24, 15, 19, 2, 20], [30, 3, 15, 13, 16]],
34
+ [[28, 1, 107, 5, 108], [28, 10, 46, 1, 47], [28, 1, 22, 15, 23], [28, 2, 14, 17, 15]],
35
+ [[30, 5, 120, 1, 121], [26, 9, 43, 4, 44], [28, 17, 22, 1, 23], [28, 2, 14, 19, 15]],
36
+ [[28, 3, 113, 4, 114], [26, 3, 44, 11, 45], [26, 17, 21, 4, 22], [26, 9, 13, 16, 14]],
37
+ [[28, 3, 107, 5, 108], [26, 3, 41, 13, 42], [30, 15, 24, 5, 25], [28, 15, 15, 10, 16]],
38
+ [[28, 4, 116, 4, 117], [26, 17, 42, 0, 0], [28, 17, 22, 6, 23], [30, 19, 16, 6, 17]],
39
+ [[28, 2, 111, 7, 112], [28, 17, 46, 0, 0], [30, 7, 24, 16, 25], [24, 34, 13, 0, 0]],
40
+ [[30, 4, 121, 5, 122], [28, 4, 47, 14, 48], [30, 11, 24, 14, 25], [30, 16, 15, 14, 16]],
41
+ [[30, 6, 117, 4, 118], [28, 6, 45, 14, 46], [30, 11, 24, 16, 25], [30, 30, 16, 2, 17]],
42
+ [[26, 8, 106, 4, 107], [28, 8, 47, 13, 48], [30, 7, 24, 22, 25], [30, 22, 15, 13, 16]],
43
+ [[28, 10, 114, 2, 115], [28, 19, 46, 4, 47], [28, 28, 22, 6, 23], [30, 33, 16, 4, 17]],
44
+ [[30, 8, 122, 4, 123], [28, 22, 45, 3, 46], [30, 8, 23, 26, 24], [30, 12, 15, 28, 16]],
45
+ [[30, 3, 117, 10, 118], [28, 3, 45, 23, 46], [30, 4, 24, 31, 25], [30, 11, 15, 31, 16]],
46
+ [[30, 7, 116, 7, 117], [28, 21, 45, 7, 46], [30, 1, 23, 37, 24], [30, 19, 15, 26, 16]],
47
+ [[30, 5, 115, 10, 116], [28, 19, 47, 10, 48], [30, 15, 24, 25, 25], [30, 23, 15, 25, 16]],
48
+ [[30, 13, 115, 3, 116], [28, 2, 46, 29, 47], [30, 42, 24, 1, 25], [30, 23, 15, 28, 16]],
49
+ [[30, 17, 115, 0, 0], [28, 10, 46, 23, 47], [30, 10, 24, 35, 25], [30, 19, 15, 35, 16]],
50
+ [[30, 17, 115, 1, 116], [28, 14, 46, 21, 47], [30, 29, 24, 19, 25], [30, 11, 15, 46, 16]],
51
+ [[30, 13, 115, 6, 116], [28, 14, 46, 23, 47], [30, 44, 24, 7, 25], [30, 59, 16, 1, 17]],
52
+ [[30, 12, 121, 7, 122], [28, 12, 47, 26, 48], [30, 39, 24, 14, 25], [30, 22, 15, 41, 16]],
53
+ [[30, 6, 121, 14, 122], [28, 6, 47, 34, 48], [30, 46, 24, 10, 25], [30, 2, 15, 64, 16]],
54
+ [[30, 17, 122, 4, 123], [28, 29, 46, 14, 47], [30, 49, 24, 10, 25], [30, 24, 15, 46, 16]],
55
+ [[30, 4, 122, 18, 123], [28, 13, 46, 32, 47], [30, 48, 24, 14, 25], [30, 42, 15, 32, 16]],
56
+ [[30, 20, 117, 4, 118], [28, 40, 47, 7, 48], [30, 43, 24, 22, 25], [30, 10, 15, 67, 16]],
57
+ [[30, 19, 118, 6, 119], [28, 18, 47, 31, 48], [30, 34, 24, 34, 25], [30, 20, 15, 61, 16]]
58
+ ].freeze
59
+
60
+ VERSION_INFO_GENERATOR = 0x1F25
61
+
62
+ # Error correction block layout for one level of one version.
63
+ EcBlocks = Struct.new(:ec_codewords_per_block, :groups) do
64
+ # Array of data codeword counts, one entry per block, in transmission order.
65
+ def data_codewords_per_block
66
+ groups.flat_map { |count, data| [data] * count }
67
+ end
68
+
69
+ def block_count
70
+ groups.sum { |count, _| count }
71
+ end
72
+
73
+ def total_data_codewords
74
+ groups.sum { |count, data| count * data }
75
+ end
76
+ end
77
+
78
+ attr_reader :number, :dimension, :alignment_centers, :total_codewords, :version_info_bits
79
+
80
+ def initialize(number)
81
+ @number = number
82
+ @dimension = 17 + 4 * number
83
+ @alignment_centers = self.class.compute_alignment_centers(number).freeze
84
+ @ec_blocks = EC_TABLE[number].map do |ec, c1, d1, c2, d2|
85
+ groups = [[c1, d1]]
86
+ groups << [c2, d2] if c2.positive?
87
+ EcBlocks.new(ec, groups.freeze).freeze
88
+ end.freeze
89
+ @total_codewords = self.class.raw_data_modules(number) / 8
90
+ @version_info_bits = number >= 7 ? self.class.encode_version_info(number) : nil
91
+ freeze
92
+ end
93
+
94
+ # EcBlocks for the given level (:L, :M, :Q, :H).
95
+ def ec_blocks(level)
96
+ @ec_blocks[EC_LEVELS.index(level) || raise(ArgumentError, "unknown EC level #{level.inspect}")]
97
+ end
98
+
99
+ def data_codewords(level)
100
+ ec_blocks(level).total_data_codewords
101
+ end
102
+
103
+ def inspect
104
+ "#<QrReader::SymbolVersion #{@number} (#{@dimension}x#{@dimension})>"
105
+ end
106
+ alias to_s inspect
107
+
108
+ class << self
109
+ # Version for a number in 1..40, or nil.
110
+ def [](number)
111
+ ALL[number - 1] if number.is_a?(Integer) && number.between?(1, 40)
112
+ end
113
+
114
+ # Version for a symbol dimension (21, 25, ... 177), or nil when the
115
+ # dimension is not 1 mod 4 or out of range.
116
+ def for_dimension(dimension)
117
+ return nil unless (dimension % 4) == 1
118
+
119
+ self[(dimension - 17) / 4]
120
+ end
121
+
122
+ # Decodes an 18-bit version information word read from the symbol.
123
+ # Returns the version whose code word is closest in Hamming distance,
124
+ # provided the distance is at most 3, else nil.
125
+ def decode_version_info(bits)
126
+ best = nil
127
+ best_distance = 4
128
+ 7.upto(40) do |v|
129
+ code = ALL[v - 1].version_info_bits
130
+ return ALL[v - 1] if code == bits
131
+
132
+ distance = (code ^ bits).digits(2).sum
133
+ if distance < best_distance
134
+ best = ALL[v - 1]
135
+ best_distance = distance
136
+ end
137
+ end
138
+ best
139
+ end
140
+
141
+ # Alignment pattern centre coordinates (same for rows and columns).
142
+ def compute_alignment_centers(version)
143
+ return [] if version == 1
144
+
145
+ count = version / 7 + 2
146
+ last = 17 + 4 * version - 7
147
+ step = version == 32 ? 26 : (version * 4 + count * 2 + 1) / (count * 2 - 2) * 2
148
+ result = [6]
149
+ (count - 1).times { |i| result.insert(1, last - i * step) }
150
+ result
151
+ end
152
+
153
+ # Number of modules available for data + EC codewords (incl. remainder bits).
154
+ def raw_data_modules(version)
155
+ result = (16 * version + 128) * version + 64
156
+ if version >= 2
157
+ align = version / 7 + 2
158
+ result -= (25 * align - 10) * align - 55
159
+ result -= 36 if version >= 7
160
+ end
161
+ result
162
+ end
163
+
164
+ def encode_version_info(version)
165
+ rem = version
166
+ 12.times { rem = (rem << 1) ^ ((rem >> 11) * VERSION_INFO_GENERATOR) }
167
+ (version << 12) | rem
168
+ end
169
+ end
170
+
171
+ ALL = (1..40).map { |n| new(n) }.freeze
172
+ end
173
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module QrReader
4
+ VERSION = "0.0.1"
5
+ end
data/lib/qr_reader.rb ADDED
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "qr_reader/version"
4
+ require_relative "qr_reader/errors"
5
+ require_relative "qr_reader/kernels"
6
+ require_relative "qr_reader/image"
7
+ require_relative "qr_reader/bit_matrix"
8
+ require_relative "qr_reader/result"
9
+ require_relative "qr_reader/symbol_version"
10
+ require_relative "qr_reader/format_information"
11
+ require_relative "qr_reader/formats"
12
+ require_relative "qr_reader/formats/ccitt"
13
+ require_relative "qr_reader/formats/tiff"
14
+ require_relative "qr_reader/formats/pdf"
15
+ require_relative "qr_reader/decoder"
16
+ require_relative "qr_reader/detector"
17
+ require_relative "qr_reader/reader"
18
+ require_relative "qr_reader/native_kernels"
19
+
20
+ # Pure-Ruby QR code reader.
21
+ #
22
+ # QrReader.decode("photo.jpg") # => "https://example.com" or nil
23
+ # QrReader.decode_all("scan.pdf") # => ["first code", "second code"]
24
+ # QrReader.read("label.png") # => #<data QrReader::Result text=..., version=3, ...>
25
+ # QrReader.scan("scans.tiff") { |code| puts "page #{code.page}: #{code.text}" }
26
+ #
27
+ # Every method accepts a file path (String or Pathname), an IO, encoded image
28
+ # bytes (PNG, JPEG, GIF, BMP, PNM, TIFF, PDF), a QrReader::Image, or a
29
+ # ChunkyPNG-style canvas (#width, #height, #pixels), plus these options:
30
+ #
31
+ # try_harder: true - fall back to slower strategies when the fast ones find nothing
32
+ # inverted: :auto - also look for light-on-dark codes (true: only those, false: never)
33
+ module QrReader
34
+ class << self
35
+ # Text of the first QR code found, or nil.
36
+ def decode(input, **options)
37
+ read(input, **options)&.text
38
+ end
39
+
40
+ # Like #decode, but raises NotFoundError when no code can be read.
41
+ def decode!(input, **options)
42
+ decode(input, **options) or raise NotFoundError, "no QR code found"
43
+ end
44
+
45
+ # Texts of all QR codes found (on all pages), possibly empty.
46
+ def decode_all(input, **options)
47
+ read_all(input, **options).map(&:text)
48
+ end
49
+
50
+ # First QR code found as a Result (text plus metadata), or nil.
51
+ def read(input, **options)
52
+ Reader.new(**options).read(input)
53
+ end
54
+
55
+ # All QR codes found (on all pages) as Results, possibly empty.
56
+ def read_all(input, **options)
57
+ Reader.new(**options).read_all(input)
58
+ end
59
+
60
+ # Yields each QR code (a Result) as soon as it is decoded, page by page;
61
+ # +break+ out to stop early. Returns an Enumerator when no block is given.
62
+ def scan(input, **options, &block)
63
+ return enum_for(:scan, input, **options) unless block
64
+
65
+ Reader.new(**options).scan(input, &block)
66
+ nil
67
+ end
68
+
69
+ # Loads an image (path, IO, encoded bytes, Image or ChunkyPNG-style canvas)
70
+ # as a grayscale QrReader::Image; multi-page documents yield their first page.
71
+ def load_image(input)
72
+ Reader.load_image(input)
73
+ end
74
+ end
75
+ end
76
+
77
+ QrReader::NativeKernels.install
metadata ADDED
@@ -0,0 +1,103 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: qr_reader
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Andi Idogawa
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies: []
12
+ description: Detects and decodes QR codes in photos and scanned documents (PNG, JPEG,
13
+ GIF, BMP, PNM, TIFF incl. CCITT fax, PDF) without native extensions or external
14
+ tools.
15
+ email:
16
+ - andi@idogawa.com
17
+ executables:
18
+ - qr_reader
19
+ extensions: []
20
+ extra_rdoc_files: []
21
+ files:
22
+ - CHANGELOG.md
23
+ - LICENSE.txt
24
+ - NOTICE
25
+ - README.md
26
+ - exe/qr_reader
27
+ - lib/qr_reader.rb
28
+ - lib/qr_reader/binarizer.rb
29
+ - lib/qr_reader/bit_matrix.rb
30
+ - lib/qr_reader/decoder.rb
31
+ - lib/qr_reader/decoder/bitstream_parser.rb
32
+ - lib/qr_reader/decoder/character_set.rb
33
+ - lib/qr_reader/decoder/codewords.rb
34
+ - lib/qr_reader/decoder/data_mask.rb
35
+ - lib/qr_reader/decoder/format_reader.rb
36
+ - lib/qr_reader/decoder/function_patterns.rb
37
+ - lib/qr_reader/decoder/galois_field.rb
38
+ - lib/qr_reader/decoder/reed_solomon.rb
39
+ - lib/qr_reader/detector.rb
40
+ - lib/qr_reader/detector/alignment_grid.rb
41
+ - lib/qr_reader/detector/alignment_pattern_finder.rb
42
+ - lib/qr_reader/detector/candidate.rb
43
+ - lib/qr_reader/detector/candidate/dimension.rb
44
+ - lib/qr_reader/detector/candidate/strokes.rb
45
+ - lib/qr_reader/detector/finder_pattern.rb
46
+ - lib/qr_reader/detector/finder_pattern_finder.rb
47
+ - lib/qr_reader/detector/geometry.rb
48
+ - lib/qr_reader/detector/grid_sampler.rb
49
+ - lib/qr_reader/detector/pattern_sets.rb
50
+ - lib/qr_reader/detector/perspective_transform.rb
51
+ - lib/qr_reader/detector/pure_detector.rb
52
+ - lib/qr_reader/errors.rb
53
+ - lib/qr_reader/format_information.rb
54
+ - lib/qr_reader/formats.rb
55
+ - lib/qr_reader/formats/bmp.rb
56
+ - lib/qr_reader/formats/ccitt.rb
57
+ - lib/qr_reader/formats/gif.rb
58
+ - lib/qr_reader/formats/jpeg.rb
59
+ - lib/qr_reader/formats/pdf.rb
60
+ - lib/qr_reader/formats/png.rb
61
+ - lib/qr_reader/formats/pnm.rb
62
+ - lib/qr_reader/formats/tiff.rb
63
+ - lib/qr_reader/image.rb
64
+ - lib/qr_reader/kernels.rb
65
+ - lib/qr_reader/kernels/binarizer.rb
66
+ - lib/qr_reader/kernels/ccitt.rb
67
+ - lib/qr_reader/kernels/detector.rb
68
+ - lib/qr_reader/kernels/image.rb
69
+ - lib/qr_reader/kernels/jpeg.rb
70
+ - lib/qr_reader/kernels/lzw.rb
71
+ - lib/qr_reader/kernels/pixels.rb
72
+ - lib/qr_reader/kernels/png.rb
73
+ - lib/qr_reader/native_kernels.rb
74
+ - lib/qr_reader/reader.rb
75
+ - lib/qr_reader/result.rb
76
+ - lib/qr_reader/symbol_version.rb
77
+ - lib/qr_reader/version.rb
78
+ homepage: https://github.com/largo/qr_reader
79
+ licenses:
80
+ - MIT
81
+ metadata:
82
+ rubygems_mfa_required: 'true'
83
+ source_code_uri: https://github.com/largo/qr_reader
84
+ changelog_uri: https://github.com/largo/qr_reader/blob/main/CHANGELOG.md
85
+ bug_tracker_uri: https://github.com/largo/qr_reader/issues
86
+ rdoc_options: []
87
+ require_paths:
88
+ - lib
89
+ required_ruby_version: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - ">="
92
+ - !ruby/object:Gem::Version
93
+ version: '3.2'
94
+ required_rubygems_version: !ruby/object:Gem::Requirement
95
+ requirements:
96
+ - - ">="
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ requirements: []
100
+ rubygems_version: 4.0.20
101
+ specification_version: 4
102
+ summary: Fast, accurate QR code reader in pure Ruby.
103
+ test_files: []