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,1345 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "zlib"
4
+ require_relative "../kernels/lzw"
5
+
6
+ module QrReader
7
+ module Formats
8
+ # TIFF decoder, aimed at what scanners, fax software and image editors
9
+ # write: bilevel CCITT pages as well as gray, colour and palette images.
10
+ #
11
+ # TIFF.decode(bytes) # => first page as a QrReader::Image
12
+ # TIFF.decode(bytes, page: -1) # => last page (negative indices count from the end)
13
+ # TIFF.page_count(bytes) # => 3
14
+ # TIFF.each_page(bytes) { |image| } # decodes one page at a time; Enumerator without a block
15
+ # TIFF.dimensions(bytes, page: 1) # => [2480, 3508], from the page's directory only
16
+ #
17
+ # Supported: little and big endian, BigTIFF, multi-page directory chains,
18
+ # strips and tiles; compression none, CCITT RLE / Group 3 / Group 4 (see
19
+ # CCITT), LZW (also the old LSB-first variant), JPEG (JPEGTables merged
20
+ # into each strip or tile; old-style JPEG only via JPEGInterchangeFormat),
21
+ # Deflate and PackBits; predictor 2 (horizontal differencing) and 3
22
+ # (floating point); photometric WhiteIsZero, BlackIsZero, RGB, palette,
23
+ # CMYK, YCbCr (luma only) and CIELab (L* as luminance); 1 to 64 bits per
24
+ # sample (unsigned, signed or floating point); planar configurations 1 and
25
+ # 2; FillOrder 2; extra samples (associated or unassociated alpha is
26
+ # composited over white like Image.from_pixels). Orientation is ignored:
27
+ # QR codes read in any orientation.
28
+ #
29
+ # Pages are the images of the main directory chain; reduced-resolution
30
+ # copies (thumbnails) and transparency masks are skipped. Damaged or
31
+ # missing strips and tiles are left white, like truncated data; only a page
32
+ # without any decodable image data raises ImageError.
33
+ module TIFF
34
+ NEW_SUBFILE_TYPE = 254
35
+ SUBFILE_TYPE = 255
36
+ IMAGE_WIDTH = 256
37
+ IMAGE_LENGTH = 257
38
+ BITS_PER_SAMPLE = 258
39
+ COMPRESSION = 259
40
+ PHOTOMETRIC = 262
41
+ FILL_ORDER = 266
42
+ STRIP_OFFSETS = 273
43
+ SAMPLES_PER_PIXEL = 277
44
+ ROWS_PER_STRIP = 278
45
+ STRIP_BYTE_COUNTS = 279
46
+ PLANAR_CONFIGURATION = 284
47
+ T4_OPTIONS = 292
48
+ PREDICTOR = 317
49
+ COLOR_MAP = 320
50
+ TILE_WIDTH = 322
51
+ TILE_LENGTH = 323
52
+ TILE_OFFSETS = 324
53
+ TILE_BYTE_COUNTS = 325
54
+ INK_SET = 332
55
+ EXTRA_SAMPLES = 338
56
+ SAMPLE_FORMAT = 339
57
+ JPEG_TABLES = 347
58
+ JPEG_INTERCHANGE_FORMAT = 513
59
+ JPEG_INTERCHANGE_FORMAT_LENGTH = 514
60
+ YCBCR_SUBSAMPLING = 530
61
+ X_RESOLUTION = 282
62
+ Y_RESOLUTION = 283
63
+ RESOLUTION_UNIT = 296
64
+ # Tags read by the decoder (a directory keeps no others).
65
+ KNOWN_TAGS = [
66
+ NEW_SUBFILE_TYPE, SUBFILE_TYPE, IMAGE_WIDTH, IMAGE_LENGTH, BITS_PER_SAMPLE, COMPRESSION, PHOTOMETRIC,
67
+ FILL_ORDER, STRIP_OFFSETS, SAMPLES_PER_PIXEL, ROWS_PER_STRIP, STRIP_BYTE_COUNTS, PLANAR_CONFIGURATION,
68
+ T4_OPTIONS, PREDICTOR, COLOR_MAP, TILE_WIDTH, TILE_LENGTH, TILE_OFFSETS, TILE_BYTE_COUNTS, INK_SET,
69
+ EXTRA_SAMPLES, SAMPLE_FORMAT, JPEG_TABLES, JPEG_INTERCHANGE_FORMAT, JPEG_INTERCHANGE_FORMAT_LENGTH,
70
+ YCBCR_SUBSAMPLING, X_RESOLUTION, Y_RESOLUTION, RESOLUTION_UNIT
71
+ ].to_h { |tag| [tag, true] }.freeze
72
+ # Directories (pages) followed in one file, and directory entries
73
+ # read in all: crafted files can chain overlapping directories.
74
+ MAX_DIRECTORIES = 65_536
75
+ MAX_ENTRIES = 4_000_000
76
+ # Pixel aspect ratios beyond this (or its inverse) come from broken
77
+ # resolution values and are reported as 1.0.
78
+ MAX_ASPECT = 8.0
79
+
80
+ # Field type -> byte size.
81
+ TYPE_SIZES = {
82
+ 1 => 1, 2 => 1, 3 => 2, 4 => 4, 5 => 8, 6 => 1, 7 => 1, 8 => 2, 9 => 4, 10 => 8,
83
+ 11 => 4, 12 => 8, 13 => 4, 16 => 8, 17 => 8, 18 => 8
84
+ }.freeze
85
+ # Rational / floating point field type -> unpack directive, little
86
+ # and big endian (rationals: numerator and denominator).
87
+ NUMBER_DIRECTIVES_LE = { 5 => "V2", 10 => "l<2", 11 => "e", 12 => "E" }.freeze
88
+ NUMBER_DIRECTIVES_BE = { 5 => "N2", 10 => "l>2", 11 => "g", 12 => "G" }.freeze
89
+ # Integer field type -> unpack directive, little and big endian.
90
+ INT_DIRECTIVES_LE = {
91
+ 1 => "C*", 3 => "v*", 4 => "V*", 6 => "c*", 7 => "C*", 8 => "s<*", 9 => "l<*", 13 => "V*",
92
+ 16 => "Q<*", 17 => "q<*", 18 => "Q<*"
93
+ }.freeze
94
+ INT_DIRECTIVES_BE = {
95
+ 1 => "C*", 3 => "n*", 4 => "N*", 6 => "c*", 7 => "C*", 8 => "s>*", 9 => "l>*", 13 => "N*",
96
+ 16 => "Q>*", 17 => "q>*", 18 => "Q>*"
97
+ }.freeze
98
+
99
+ UNSUPPORTED_COMPRESSION = {
100
+ 9 => "JBIG (T.85)", 10 => "JBIG2 (T.43)", 32_766 => "NeXT RLE", 32_771 => "CCITT RLE (word-aligned)",
101
+ 32_809 => "ThunderScan RLE", 34_661 => "JBIG", 34_676 => "SGI LogLuv", 34_677 => "SGI LogLuv",
102
+ 34_712 => "JPEG 2000", 34_887 => "LERC", 34_892 => "lossy JPEG (DNG)", 34_925 => "LZMA",
103
+ 50_000 => "Zstandard", 50_001 => "WebP", 50_002 => "JPEG XL"
104
+ }.freeze
105
+
106
+ WHITE = "\xFF".b
107
+ SOI = "\xFF\xD8".b
108
+ EOI = "\xFF\xD9".b
109
+ # Adobe APP14 segment without its transform byte (marks JPEG data as RGB).
110
+ ADOBE = "\xFF\xEE\x00\x0EAdobe\x00\x64\x00\x00\x00\x00".b
111
+ HEX_DIGITS = "0123456789abcdef".b
112
+ ALL_BYTES = (0..255).to_a.freeze
113
+ # Byte -> byte with its bits reversed (FillOrder 2), as String#tr tables.
114
+ REVERSE_FROM = Image::TR_ESCAPE.call(ALL_BYTES)
115
+ REVERSE_TO = Image::TR_ESCAPE.call(ALL_BYTES.map { |b| format("%08b", b).reverse.to_i(2) })
116
+ # Byte -> its four 2-bit samples as bytes 0..3.
117
+ TWO_BIT = Array.new(256) { |b| [b >> 6, (b >> 4) & 3, (b >> 2) & 3, b & 3].pack("C4").freeze }.freeze
118
+ # Input slice size for Zlib, so that a decompression bomb stops early.
119
+ INFLATE_SLICE = 16_384
120
+ # Largest decompressed strip or tile, and largest buffer of unpacked
121
+ # samples of one, whatever sizes a (malicious) header declares: a
122
+ # chunk is held in memory while it is converted to luminance.
123
+ CHUNK_LIMIT = 1 << 28
124
+ # Unpack directives (little, big endian) of wide samples for predictor 2.
125
+ WORDS = { 16 => %w[v* n*], 32 => %w[V* N*], 64 => %w[Q<* Q>*] }.freeze
126
+ # Unpack directives of floating point samples by [bits, big endian].
127
+ FLOATS = { [32, false] => "e*", [32, true] => "g*", [64, false] => "E*", [64, true] => "G*" }.freeze
128
+
129
+ # An empty binary String with room for +size+ bytes. Hot methods call
130
+ # this rather than String.new(capacity:, encoding:) themselves: YJIT
131
+ # side-exits on that keyword-argument call for the rest of the method.
132
+ module Bytes
133
+ def self.buffer(size)
134
+ String.new(capacity: size, encoding: Encoding::BINARY)
135
+ end
136
+ end
137
+
138
+ class << self
139
+ # Decodes page +page+ (0-based; negative counts from the end) to a
140
+ # luminance Image. Raises IndexError for a page that does not exist.
141
+ def decode(bytes, page: 0)
142
+ Container.new(bytes).page(page).decode
143
+ end
144
+
145
+ # Number of pages.
146
+ def page_count(bytes)
147
+ Container.new(bytes).pages.size
148
+ end
149
+
150
+ # Yields each page as an Image, decoding it only when it is reached.
151
+ # Returns an Enumerator (sized by page_count) without a block.
152
+ def each_page(bytes)
153
+ return enum_for(:each_page, bytes) { page_count(bytes) } unless block_given?
154
+
155
+ container = Container.new(bytes)
156
+ container.pages.each_index { |i| yield container.page(i).decode }
157
+ nil
158
+ end
159
+
160
+ # [width, height] of a page, read from its directory only.
161
+ def dimensions(bytes, page: 0)
162
+ Container.new(bytes).page(page).dimensions
163
+ end
164
+
165
+ # What a page's directory says about it, without decoding it:
166
+ # {page:, width:, height:, pixel_aspect:, dpi:}. +pixel_aspect+ is
167
+ # the width of a pixel divided by its height (YResolution /
168
+ # XResolution: 0.48 for a 204 x 98 dpi fax; 1.0 when unknown);
169
+ # +dpi+ is [x, y] (nil without a resolution in inches or cm).
170
+ def info(bytes, page: 0)
171
+ container = Container.new(bytes)
172
+ index = page.is_a?(Integer) && page.negative? ? container.pages.size + page : page
173
+ container.page(page).info(index)
174
+ end
175
+
176
+ # Yields each decodable page as an Image with its info (see #info),
177
+ # decoding one page at a time. Pages that cannot be decoded are
178
+ # skipped (+info[:page]+ tells which page an image is); only a file
179
+ # without any decodable page raises (UnsupportedImageError if a page
180
+ # was unsupported, else ImageError). Returns an Enumerator without a
181
+ # block.
182
+ def each_image(bytes)
183
+ return enum_for(:each_image, bytes) unless block_given?
184
+
185
+ container = Container.new(bytes)
186
+ failure = nil
187
+ decoded = 0
188
+ container.pages.each_index do |i|
189
+ page = begin
190
+ container.page(i)
191
+ rescue Error => e
192
+ failure = pick_failure(failure, e)
193
+ next
194
+ end
195
+ image = begin
196
+ page.decode
197
+ rescue Error => e
198
+ failure = pick_failure(failure, e)
199
+ next
200
+ end
201
+ decoded += 1
202
+ yield image, page.info(i)
203
+ end
204
+ raise failure if decoded.zero? && failure
205
+
206
+ nil
207
+ end
208
+
209
+ private
210
+
211
+ # The error to raise when no page decodes: the first one, or the
212
+ # first UnsupportedImageError.
213
+ def pick_failure(failure, error)
214
+ return error if failure.nil?
215
+ return error if error.is_a?(UnsupportedImageError) && !failure.is_a?(UnsupportedImageError)
216
+
217
+ failure
218
+ end
219
+ end
220
+
221
+ # The file header and the chain of image file directories (IFDs).
222
+ class Container
223
+ attr_reader :bytes
224
+
225
+ def initialize(bytes)
226
+ raise ImageError, "expected a String of TIFF data, got #{bytes.class}" unless bytes.is_a?(String)
227
+
228
+ @bytes = bytes.encoding == Encoding::BINARY ? bytes : bytes.b
229
+ raise ImageError, "truncated TIFF header" if @bytes.bytesize < 8
230
+
231
+ case @bytes.byteslice(0, 4)
232
+ when "II*\0" then @le = true
233
+ when "MM\0*" then @le = false
234
+ when "II+\0" then @le = @big = true
235
+ when "MM\0+"
236
+ @le = false
237
+ @big = true
238
+ else raise ImageError, "not a TIFF file"
239
+ end
240
+ @directives = @le ? INT_DIRECTIVES_LE : INT_DIRECTIVES_BE
241
+ if @big
242
+ raise ImageError, "unsupported BigTIFF offset size" unless @bytes.bytesize >= 16 && uint(4, 2) == 8
243
+
244
+ @first = uint(8, 8)
245
+ else
246
+ @first = uint(4, 4)
247
+ end
248
+ end
249
+
250
+ def little_endian?
251
+ @le
252
+ end
253
+
254
+ # Directories of the pages, in file order.
255
+ def pages
256
+ @pages ||= begin
257
+ dirs = directories
258
+ raise ImageError, "TIFF file contains no image directory" if dirs.empty?
259
+
260
+ main = dirs.reject(&:subsidiary?)
261
+ main.empty? ? dirs : main
262
+ end
263
+ end
264
+
265
+ def page(index)
266
+ dir = pages[index] if index.is_a?(Integer)
267
+ raise IndexError, "TIFF page #{index.inspect} out of range (#{pages.size} pages)" unless dir
268
+
269
+ Page.new(self, dir)
270
+ end
271
+
272
+ # Unsigned little/big endian integer of +size+ bytes at +pos+.
273
+ def uint(pos, size)
274
+ v = 0
275
+ if @le
276
+ i = pos + size - 1
277
+ while i >= pos
278
+ v = (v << 8) | @bytes.getbyte(i)
279
+ i -= 1
280
+ end
281
+ else
282
+ stop = pos + size
283
+ while pos < stop
284
+ v = (v << 8) | @bytes.getbyte(pos)
285
+ pos += 1
286
+ end
287
+ end
288
+ v
289
+ end
290
+
291
+ # Integer values of a field (nil for non-integer types or values
292
+ # outside the file), at most +limit+ of them.
293
+ def ints(type, count, pos, limit = nil)
294
+ directive = @directives[type] or return nil
295
+ size = count * TYPE_SIZES[type]
296
+ return nil if pos + size > @bytes.bytesize
297
+
298
+ size = limit * TYPE_SIZES[type] if limit && count > limit
299
+ @bytes.byteslice(pos, size).unpack(directive)
300
+ end
301
+
302
+ # First value of a numeric field (rationals and floating point
303
+ # included) as a Float, or nil.
304
+ def number(type, count, pos)
305
+ return nil unless count.positive?
306
+
307
+ directive = (@le ? NUMBER_DIRECTIVES_LE : NUMBER_DIRECTIVES_BE)[type]
308
+ return ints(type, 1, pos)&.first&.to_f unless directive
309
+ return nil if pos + TYPE_SIZES[type] > @bytes.bytesize
310
+
311
+ num, den = @bytes.unpack(directive, offset: pos)
312
+ value = den ? (den.zero? ? nil : num.fdiv(den)) : num
313
+ value&.finite? ? value : nil
314
+ end
315
+
316
+ # Raw bytes of a field.
317
+ def blob(type, count, pos)
318
+ size = count * TYPE_SIZES[type]
319
+ return nil if pos + size > @bytes.bytesize
320
+
321
+ @bytes.byteslice(pos, size)
322
+ end
323
+
324
+ private
325
+
326
+ # Follows the IFD chain (stops at loops, offsets outside the file
327
+ # and after MAX_DIRECTORIES directories or MAX_ENTRIES entries).
328
+ def directories
329
+ dirs = []
330
+ seen = {}
331
+ offset = @first
332
+ @entries_left = MAX_ENTRIES
333
+ while offset.positive? && !seen[offset] && dirs.size < MAX_DIRECTORIES
334
+ seen[offset] = true
335
+ dir = read_directory(offset) or break
336
+ dirs << dir
337
+ offset = dir.next_offset
338
+ end
339
+ dirs
340
+ end
341
+
342
+ def read_directory(offset)
343
+ size = @bytes.bytesize
344
+ count_size, entry_size, value_size = @big ? [8, 20, 8] : [2, 12, 4]
345
+ return nil if offset + count_size > size
346
+
347
+ count = uint(offset, count_size)
348
+ start = offset + count_size
349
+ return nil if count.zero? || start + count * entry_size > size || (@entries_left -= count).negative?
350
+
351
+ entries = {}
352
+ count.times do |i|
353
+ pos = start + i * entry_size
354
+ tag = uint(pos, 2)
355
+ next unless KNOWN_TAGS[tag]
356
+
357
+ type = uint(pos + 2, 2)
358
+ n = uint(pos + 4, value_size)
359
+ type_size = TYPE_SIZES[type]
360
+ next if entries.key?(tag) || !type_size
361
+
362
+ value_pos = pos + 4 + value_size
363
+ value_pos = uint(value_pos, value_size) if n * type_size > value_size
364
+ entries[tag] = [type, n, value_pos]
365
+ end
366
+ next_pos = start + count * entry_size
367
+ next_offset = next_pos + value_size <= size ? uint(next_pos, value_size) : 0
368
+ Directory.new(self, entries, next_offset)
369
+ end
370
+ end
371
+
372
+ # One image file directory: tag -> [type, count, value position].
373
+ class Directory
374
+ attr_reader :next_offset
375
+
376
+ def initialize(container, entries, next_offset)
377
+ @container = container
378
+ @entries = entries
379
+ @next_offset = next_offset
380
+ end
381
+
382
+ def include?(tag)
383
+ @entries.key?(tag)
384
+ end
385
+
386
+ # Integer values of +tag+ (at most +limit+), or nil.
387
+ def ints(tag, limit = nil)
388
+ entry = @entries[tag] or return nil
389
+ @container.ints(*entry, limit)
390
+ end
391
+
392
+ # First integer value of +tag+, or +default+.
393
+ def int(tag, default = nil)
394
+ ints(tag, 1)&.first || default
395
+ end
396
+
397
+ # First value of +tag+ as a Float (rationals too), or nil.
398
+ def number(tag)
399
+ entry = @entries[tag] or return nil
400
+ @container.number(*entry)
401
+ end
402
+
403
+ # Raw bytes of +tag+ (JPEGTables), or nil.
404
+ def blob(tag)
405
+ entry = @entries[tag] or return nil
406
+ @container.blob(*entry)
407
+ end
408
+
409
+ # Reduced-resolution image or transparency mask, not a page.
410
+ def subsidiary?
411
+ int(NEW_SUBFILE_TYPE, 0) & 5 != 0 || int(SUBFILE_TYPE) == 2
412
+ end
413
+ end
414
+
415
+ # Decodes one page. Strips and tiles ("chunks") are decompressed to
416
+ # sample rows, normalised to one byte per sample and converted to
417
+ # luminance; bilevel CCITT chunks and colour JPEG chunks yield
418
+ # luminance directly.
419
+ class Page
420
+ attr_reader :width, :height
421
+
422
+ def initialize(container, dir)
423
+ @bytes = container.bytes
424
+ @le = container.little_endian?
425
+ @dir = dir
426
+ @width = dir.int(IMAGE_WIDTH)
427
+ @height = dir.int(IMAGE_LENGTH)
428
+ raise ImageError, "TIFF page without image dimensions" unless @width && @height
429
+
430
+ raise ImageError, "invalid TIFF dimensions #{@width}x#{@height}" unless @width.positive? && @height.positive?
431
+ return if @width * @height <= MAX_PIXELS
432
+
433
+ raise UnsupportedImageError, "TIFF image too large (#{@width}x#{@height}, limit #{MAX_PIXELS} pixels)"
434
+ end
435
+
436
+ def dimensions
437
+ [@width, @height]
438
+ end
439
+
440
+ # {page:, width:, height:, pixel_aspect:, dpi:} (see TIFF.info).
441
+ def info(index)
442
+ x = @dir.number(X_RESOLUTION)
443
+ y = @dir.number(Y_RESOLUTION)
444
+ x = nil unless x&.positive?
445
+ y = nil unless y&.positive?
446
+ x ||= y
447
+ y ||= x
448
+ aspect = x ? y / x : 1.0
449
+ aspect = 1.0 unless aspect.between?(1 / MAX_ASPECT, MAX_ASPECT)
450
+ scale = case @dir.int(RESOLUTION_UNIT, 2)
451
+ when 2 then 1.0
452
+ when 3 then 2.54
453
+ end
454
+ dpi = [(x * scale).round(2), (y * scale).round(2)] if x && scale
455
+ { page: index, width: @width, height: @height, pixel_aspect: aspect.round(6), dpi: dpi }
456
+ end
457
+
458
+ def decode
459
+ setup
460
+ @decoded = 0
461
+ data = if @codec == :ojpeg then old_jpeg
462
+ elsif @planes then decode_planar
463
+ else assemble(0, WHITE) { |src, cw, rows| chunk_luma(src, cw, rows) }
464
+ end
465
+ raise ImageError, "TIFF page has no decodable image data" if @decoded.zero?
466
+
467
+ Image.new(@width, @height, data)
468
+ end
469
+
470
+ private
471
+
472
+ # --- parameters ---------------------------------------------------
473
+
474
+ def setup
475
+ d = @dir
476
+ @spp = d.int(SAMPLES_PER_PIXEL, 1)
477
+ raise ImageError, "invalid TIFF samples per pixel #{@spp}" unless @spp.between?(1, 64)
478
+
479
+ depths = d.ints(BITS_PER_SAMPLE, 64) || [1]
480
+ @bps = depths.first
481
+ raise ImageError, "invalid TIFF bits per sample #{@bps}" unless @bps&.between?(1, 64)
482
+ unless depths.first(@spp).uniq.size == 1
483
+ raise UnsupportedImageError, "TIFF images with different bit depths per sample are not supported"
484
+ end
485
+
486
+ @compression = d.int(COMPRESSION, 1)
487
+ @photometric = d.int(PHOTOMETRIC) || default_photometric
488
+ @fill_order = d.int(FILL_ORDER, 1)
489
+ @predictor = d.int(PREDICTOR, 1)
490
+ @sample_format = d.int(SAMPLE_FORMAT, 1)
491
+ setup_layout
492
+ setup_codec
493
+ return if %i[ojpeg fax].include?(@codec)
494
+
495
+ setup_color
496
+ check_chunk_size unless @codec == :jpeg
497
+ end
498
+
499
+ # Decompressed chunks and unpacked low-depth samples are held in
500
+ # memory whole (an uncompressed chunk is a slice of the file).
501
+ def check_chunk_size
502
+ nsamp = planar? ? 1 : @spp
503
+ raw = @codec == :none ? 0 : chunk_size(@chunk_w, @chunk_h, nsamp)
504
+ samples = @bps < 8 ? @chunk_w * @chunk_h * nsamp : 0
505
+ return if raw <= CHUNK_LIMIT && samples <= CHUNK_LIMIT
506
+
507
+ raise UnsupportedImageError,
508
+ "TIFF strips or tiles too large (#{@chunk_w}x#{@chunk_h}, #{nsamp} samples of #{@bps} bits per pixel)"
509
+ end
510
+
511
+ def default_photometric
512
+ if [2, 3, 4].include?(@dir.int(COMPRESSION)) then 0
513
+ elsif @spp >= 3 then 2
514
+ elsif @dir.include?(COLOR_MAP) then 3
515
+ else 1
516
+ end
517
+ end
518
+
519
+ def setup_layout
520
+ d = @dir
521
+ w = @width
522
+ h = @height
523
+ if d.include?(TILE_WIDTH)
524
+ tw = d.int(TILE_WIDTH)
525
+ tl = d.int(TILE_LENGTH)
526
+ raise ImageError, "invalid TIFF tile size" unless tw&.positive? && tl&.positive?
527
+
528
+ @tiled = true
529
+ @chunk_w = tw
530
+ @chunk_h = tl
531
+ @across = (w + tw - 1) / tw
532
+ @down = (h + tl - 1) / tl
533
+ if @across * @down * tw * tl > 2 * MAX_PIXELS
534
+ raise UnsupportedImageError, "TIFF tiles too large (#{tw}x#{tl} for a #{w}x#{h} image)"
535
+ end
536
+
537
+ @offsets = d.ints(TILE_OFFSETS) || d.ints(STRIP_OFFSETS)
538
+ @counts = d.ints(TILE_BYTE_COUNTS) || d.ints(STRIP_BYTE_COUNTS)
539
+ else
540
+ rps = d.int(ROWS_PER_STRIP, h)
541
+ rps = h unless rps.positive? && rps < h
542
+ @tiled = false
543
+ @chunk_w = w
544
+ @chunk_h = rps
545
+ @across = 1
546
+ @down = (h + rps - 1) / rps
547
+ @offsets = d.ints(STRIP_OFFSETS)
548
+ @counts = d.ints(STRIP_BYTE_COUNTS)
549
+ end
550
+ raise ImageError, "TIFF page without image data offsets" unless @offsets
551
+
552
+ @per_plane = @across * @down
553
+ end
554
+
555
+ def setup_codec
556
+ case @compression
557
+ when 1 then @codec = :none
558
+ when 2, 3, 4 then setup_fax
559
+ when 5 then @codec = :lzw
560
+ when 6 then @codec = :ojpeg
561
+ when 7
562
+ @codec = :jpeg
563
+ @jpeg_tables = @dir.blob(JPEG_TABLES)
564
+ when 8, 32_946 then @codec = :deflate
565
+ when 32_773 then @codec = :packbits
566
+ else
567
+ name = UNSUPPORTED_COMPRESSION.fetch(@compression) { "compression #{@compression}" }
568
+ raise UnsupportedImageError, "#{name} TIFF images are not supported"
569
+ end
570
+ end
571
+
572
+ def setup_fax
573
+ raise ImageError, "CCITT-compressed TIFF must be 1 bit per pixel" unless @bps == 1 && @spp == 1
574
+
575
+ @codec = :fax
576
+ t4 = @dir.int(T4_OPTIONS, 0)
577
+ @fax = case @compression
578
+ when 2 then { k: 0, encoded_byte_align: true }
579
+ when 3 then { k: t4 & 1, encoded_byte_align: t4 & 4 != 0, end_of_line: true }
580
+ else { k: -1 }
581
+ end
582
+ @fax[:fill_order] = @fill_order
583
+ @fax[:black_is_1] = @photometric == 1 # coded white runs are 0 bits
584
+ end
585
+
586
+ # Chooses the luminance conversion (@mode), which samples it reads
587
+ # and the per-value mapping applied while unpacking samples.
588
+ def setup_color
589
+ ph = @photometric
590
+ spp = @spp
591
+ extra = @dir.ints(EXTRA_SAMPLES, 64) || []
592
+ @invert = false
593
+ lut = nil
594
+ color = case ph
595
+ when 0, 1
596
+ @invert = ph.zero?
597
+ 1
598
+ when 2 then 3
599
+ when 3
600
+ lut = palette
601
+ 1
602
+ when 5 then @dir.int(INK_SET, 1) == 1 && spp >= 4 ? 4 : 1
603
+ when 6 then 3
604
+ when 8, 9, 10 then 1 # L* only
605
+ else raise UnsupportedImageError, "TIFF photometric interpretation #{ph} is not supported"
606
+ end
607
+ raise ImageError, "TIFF photometric #{ph} needs #{color} samples per pixel, got #{spp}" if spp < color
608
+
609
+ alpha = spp > color && [1, 2].include?(extra.first) ? spp - extra.size : nil
610
+ alpha = nil if alpha && alpha < color
611
+ @associated = extra.first == 1
612
+ @invert = true if ph == 5 && color == 1 # a single ink: 0 is white
613
+
614
+ @mode = if @codec == :jpeg && spp > 1 && !planar? then :luma
615
+ elsif ph == 6 then planar? ? :single : :ycbcr
616
+ elsif color == 4 then :cmyk
617
+ elsif color == 3 && ph == 2 then alpha ? :rgba : :rgb
618
+ elsif alpha && lut.nil? && [0, 1].include?(ph) then :gray_alpha
619
+ else spp == 1 ? :single : :pick
620
+ end
621
+ setup_planes(alpha)
622
+ setup_samples(lut)
623
+ @adobe = 0 if @mode == :luma && ph == 2 # JPEG data holds RGB, not YCbCr
624
+ end
625
+
626
+ def planar?
627
+ @dir.int(PLANAR_CONFIGURATION, 1) == 2 && @spp > 1
628
+ end
629
+
630
+ # Sample offsets within a pixel (@channels) for the conversion and,
631
+ # for planar data, which planes are needed.
632
+ def setup_planes(alpha)
633
+ used = case @mode
634
+ when :rgb then [0, 1, 2]
635
+ when :rgba then [0, 1, 2, alpha]
636
+ when :cmyk then [0, 1, 2, 3]
637
+ when :gray_alpha then [0, alpha]
638
+ else [0]
639
+ end
640
+ if planar?
641
+ @planes = used
642
+ @channels = (0...used.size).to_a
643
+ @stride = used.size
644
+ @mode = :single if used.size == 1
645
+ else
646
+ @channels = used
647
+ @stride = @spp
648
+ end
649
+ end
650
+
651
+ # Mapping of unpacked sample values to output bytes: scaling of low
652
+ # bit depths, palette lookup, WhiteIsZero inversion, signed samples.
653
+ def setup_samples(lut)
654
+ bps = @bps
655
+ @float = @sample_format == 3
656
+ if @float && ![32, 64].include?(bps)
657
+ raise UnsupportedImageError, "#{bps}-bit floating point TIFF samples are not supported"
658
+ end
659
+ raise UnsupportedImageError, "TIFF palettes with more than 8 bits are not supported" if lut && bps > 8
660
+ if @predictor == 2 && bps != 8 && !WORDS.key?(bps)
661
+ raise UnsupportedImageError, "TIFF predictor 2 with #{bps}-bit samples is not supported"
662
+ end
663
+ unless [1, 2, 3].include?(@predictor)
664
+ raise UnsupportedImageError, "TIFF predictor #{@predictor} is not supported"
665
+ end
666
+ raise UnsupportedImageError, "TIFF predictor 3 needs floating point samples" if @predictor == 3 && !@float
667
+ raise UnsupportedImageError, "JPEG-compressed TIFF must have 8-bit samples" if @codec == :jpeg && bps != 8
668
+
669
+ signed = @sample_format == 2 && bps >= 8
670
+ final = %i[single pick].include?(@mode)
671
+ max = (1 << bps) - 1
672
+ levels = bps < 8 ? max + 1 : 256
673
+ map = Array.new(levels) do |v|
674
+ if lut then lut[v]
675
+ else
676
+ s = bps < 8 ? (v * 255 + max / 2) / max : v
677
+ s ^= 0x80 if signed
678
+ final && @invert ? 255 - s : s
679
+ end
680
+ end
681
+ from = case bps
682
+ when 1 then "01".b
683
+ when 4 then HEX_DIGITS
684
+ else ALL_BYTES.first(levels).pack("C*")
685
+ end
686
+ @fused_rgb = @mode == :rgb && @predictor == 2 && bps == 8 && !planar? && map == ALL_BYTES
687
+ return if map == ALL_BYTES
688
+
689
+ @tr_from = Image::TR_ESCAPE.call(from.bytes)
690
+ @tr_to = Image::TR_ESCAPE.call(map)
691
+ end
692
+
693
+ # Palette entry -> luminance. 16-bit colour map values; files with
694
+ # 8-bit values (all below 256) are accepted like libtiff does.
695
+ def palette
696
+ n = 1 << [@bps, 8].min
697
+ cmap = @dir.ints(COLOR_MAP, 3 * 256)
698
+ raise ImageError, "TIFF palette image without a valid colour map" unless cmap && cmap.size >= 3 * n
699
+
700
+ shift = cmap.first(3 * n).max < 256 ? 0 : 8
701
+ # (Values of the wrong type - signed, 32-bit - are clamped.)
702
+ v = ->(i) { (cmap[i] >> shift).clamp(0, 255) }
703
+ Array.new(n) { |i| Image.luma(v.call(i), v.call(n + i), v.call(2 * n + i)) }
704
+ end
705
+
706
+ # --- chunk layout -------------------------------------------------
707
+
708
+ # Decodes the chunks of one plane into a width * height buffer. The
709
+ # block turns a chunk's data into rows of +cw+ bytes (possibly fewer
710
+ # rows than asked for); missing parts are filled with +fill+.
711
+ def assemble(plane, fill)
712
+ w = @width
713
+ h = @height
714
+ cw = @chunk_w
715
+ ch = @chunk_h
716
+ base = plane * @per_plane
717
+ # Chunks without an offset are missing: no need to visit them.
718
+ count = (@offsets.size - base).clamp(0, @per_plane)
719
+ if @tiled
720
+ out = fill * (w * h)
721
+ count.times do |i|
722
+ data = chunk_data(base + i) or next
723
+ x0 = (i % @across) * cw
724
+ y0 = (i / @across) * ch
725
+ part = yield(data, cw, ch) or next
726
+ vis_w = [cw, w - x0].min
727
+ rows = [part.bytesize / cw, h - y0].min
728
+ rows.times { |r| out.bytesplice((y0 + r) * w + x0, vis_w, part.byteslice(r * cw, vis_w)) }
729
+ end
730
+ else
731
+ out = Bytes.buffer(w * h)
732
+ count.times do |i|
733
+ rows = [ch, h - i * ch].min
734
+ need = rows * w
735
+ data = chunk_data(base + i)
736
+ part = yield(data, w, rows) if data
737
+ if part.nil? || part.empty?
738
+ out << fill * need
739
+ elsif part.bytesize >= need
740
+ out << (part.bytesize == need ? part : part.byteslice(0, need))
741
+ else
742
+ out << part << fill * (need - part.bytesize)
743
+ end
744
+ end
745
+ out << fill * (w * h - out.bytesize)
746
+ end
747
+ out
748
+ end
749
+
750
+ # Bytes of chunk +index+ (shorter when the file is truncated), or nil.
751
+ def chunk_data(index)
752
+ off = @offsets[index]
753
+ size = @bytes.bytesize
754
+ return nil unless off&.positive? && off < size
755
+
756
+ count = @counts&.[](index)
757
+ return nil if count&.<= 0
758
+
759
+ # (64-bit counts may exceed what byteslice accepts.)
760
+ count = size - off if count.nil? || count > size - off
761
+ @bytes.byteslice(off, count)
762
+ end
763
+
764
+ # Luminance rows of one chunk (planar configuration 1).
765
+ def chunk_luma(data, cw, rows)
766
+ case @codec
767
+ when :fax then return fax_chunk(data, cw, rows)
768
+ when :jpeg
769
+ samples = jpeg_chunk(data, cw, rows) or return nil
770
+ return samples if @mode == :luma
771
+
772
+ avail = samples.bytesize / cw
773
+ samples.tr!(@tr_from, @tr_to) if @tr_from
774
+ else
775
+ raw = decompress(data, chunk_size(cw, rows, @spp))
776
+ return ycbcr_luma(raw, cw, rows) if @mode == :ycbcr
777
+ return predicted_rgb_luma(raw, cw, rows) if @fused_rgb
778
+
779
+ samples, avail = normalize(raw, cw, rows, @spp)
780
+ return nil if avail.zero?
781
+
782
+ @decoded += 1
783
+ end
784
+ convert(samples, cw, avail)
785
+ end
786
+
787
+ # Normalised samples of one chunk of one plane (planar configuration 2).
788
+ def plane_samples(data, cw, rows)
789
+ if @codec == :jpeg
790
+ samples = jpeg_chunk(data, cw, rows) or return nil
791
+ samples.tr!(@tr_from, @tr_to) if @tr_from
792
+ return samples
793
+ end
794
+
795
+ samples, avail = normalize(decompress(data, chunk_size(cw, rows, 1)), cw, rows, 1)
796
+ @decoded += 1 if avail.positive?
797
+ samples
798
+ end
799
+
800
+ def decode_planar
801
+ # Single planes hold luminance; otherwise white is the colour
802
+ # samples' white and opaque alpha.
803
+ alpha = @planes.last if %i[rgba gray_alpha].include?(@mode)
804
+ fills = @planes.map { |p| @mode == :single || p == alpha ? WHITE : white_sample }
805
+ planes = @planes.zip(fills).map do |p, fill|
806
+ assemble(p, fill) { |src, cw, rows| plane_samples(src, cw, rows) }
807
+ end
808
+ return planes[0] if @mode == :single
809
+
810
+ # Each row's planes are joined, so that the converters read them
811
+ # as channels +w+ bytes apart. Rows without data are white.
812
+ w = @width
813
+ @channels = Array.new(planes.size) { |i| i * w }
814
+ @stride = 1
815
+ blank = fills.map { |fill| fill * w }.join
816
+ white = WHITE * w
817
+ out = Bytes.buffer(w * @height)
818
+ @height.times do |y|
819
+ row = planes.map { |pl| pl.byteslice(y * w, w) }.join
820
+ if row == blank
821
+ out << white
822
+ else
823
+ convert_row(row, 0, w, out)
824
+ end
825
+ end
826
+ out
827
+ end
828
+
829
+ # The value of a white colour sample before conversion.
830
+ def white_sample
831
+ [0, 5].include?(@photometric) ? "\0".b : WHITE
832
+ end
833
+
834
+ # Expected decompressed size of a chunk.
835
+ def chunk_size(cw, rows, nsamp)
836
+ if @mode == :ycbcr
837
+ sh, sv = subsampling
838
+ ((rows + sv - 1) / sv) * ((cw + sh - 1) / sh) * (sh * sv + 2)
839
+ else
840
+ ((cw * nsamp * @bps + 7) >> 3) * rows
841
+ end
842
+ end
843
+
844
+ # --- codecs -------------------------------------------------------
845
+
846
+ def decompress(data, expected)
847
+ data = data.tr(REVERSE_FROM, REVERSE_TO) if @fill_order == 2
848
+ case @codec
849
+ when :none then data
850
+ when :lzw then lzw(data, expected)
851
+ when :deflate then inflate(data, expected)
852
+ when :packbits then packbits(data, expected)
853
+ end
854
+ end
855
+
856
+ # TIFF LZW: MSB-first codes with "early change"; data starting with
857
+ # 0x00 and an odd byte is the old LSB-first variant without early
858
+ # change (detected like libtiff does). The kernel is shared with PDF.
859
+ def lzw(data, expected)
860
+ compat = data.getbyte(0) == 0 && data.bytesize > 1 && data.getbyte(1).odd?
861
+ QrReaderKernels.lzw_decode(data, expected, compat ? 0 : 1, compat)
862
+ end
863
+
864
+ # Room to reserve for decompressed data: never what a header claims
865
+ # (String.new(capacity:) commits the memory), the buffer grows.
866
+ def initial_capacity(data, expected)
867
+ guess = 4 * data.bytesize + 4096
868
+ guess < expected ? guess : expected
869
+ end
870
+
871
+ def fax_chunk(data, cw, rows)
872
+ image = CCITT.decode(data, columns: cw, rows: rows, **@fax)
873
+ @decoded += 1
874
+ image.data
875
+ rescue UnsupportedImageError
876
+ raise
877
+ rescue ImageError
878
+ nil # not even the first row decodes: the chunk stays white
879
+ end
880
+
881
+ # Inflates zlib (or raw deflate) data; stops after +expected+ bytes
882
+ # and keeps what was decoded before an error.
883
+ def inflate(data, expected)
884
+ out = Bytes.buffer(initial_capacity(data, expected))
885
+ [Zlib::MAX_WBITS, -Zlib::MAX_WBITS].each do |window_bits|
886
+ zstream = Zlib::Inflate.new(window_bits)
887
+ pos = 0
888
+ while pos < data.bytesize && out.bytesize < expected
889
+ out << zstream.inflate(data.byteslice(pos, INFLATE_SLICE))
890
+ pos += INFLATE_SLICE
891
+ end
892
+ break
893
+ rescue Zlib::Error
894
+ break unless out.empty?
895
+ ensure
896
+ zstream&.reset
897
+ zstream&.close
898
+ end
899
+ out
900
+ end
901
+
902
+ def packbits(data, expected)
903
+ out = Bytes.buffer(initial_capacity(data, expected))
904
+ i = 0
905
+ n = data.bytesize
906
+ while i < n && out.bytesize < expected
907
+ c = data.getbyte(i)
908
+ i += 1
909
+ if c < 128
910
+ out << data.byteslice(i, c + 1)
911
+ i += c + 1
912
+ elsif c > 128
913
+ b = data.byteslice(i, 1)
914
+ out << b * (257 - c)
915
+ i += 1
916
+ end
917
+ end
918
+ out
919
+ end
920
+
921
+ # Luminance (or, for gray JPEGs, sample) rows of a JPEG chunk:
922
+ # JPEGTables merged in, cropped or padded to +cw+ x +rows+.
923
+ def jpeg_chunk(data, cw, rows)
924
+ stream = data
925
+ tables = @jpeg_tables
926
+ if tables&.start_with?(SOI) && stream.start_with?(SOI)
927
+ tables = tables.byteslice(0, tables.bytesize - 2) if tables.end_with?(EOI)
928
+ stream = tables + stream.byteslice(2, stream.bytesize - 2)
929
+ end
930
+ if @adobe && stream.start_with?(SOI)
931
+ stream = SOI + ADOBE + @adobe.chr + stream.byteslice(2, stream.bytesize - 2)
932
+ end
933
+ jw, jh = JPEG.dimensions(stream)
934
+ return nil if jw * jh > cw * @chunk_h # not this chunk's image; also bounds crafted sizes
935
+
936
+ image = JPEG.decode(stream)
937
+ @decoded += 1
938
+ fit(image.data, jw, [jh, rows].min, cw)
939
+ rescue UnsupportedImageError
940
+ raise
941
+ rescue ImageError
942
+ nil
943
+ end
944
+
945
+ # +rows+ rows of +cw+ bytes from a buffer with rows of +w+ bytes.
946
+ def fit(data, w, rows, cw)
947
+ return data.byteslice(0, cw * rows) if w == cw
948
+
949
+ out = Bytes.buffer(cw * rows)
950
+ rows.times do |r|
951
+ out << data.byteslice(r * w, [w, cw].min)
952
+ out << WHITE * (cw - w) if w < cw
953
+ end
954
+ out
955
+ end
956
+
957
+ # Old-style JPEG (compression 6): only the common case of a complete
958
+ # JPEG stream at JPEGInterchangeFormat is supported.
959
+ def old_jpeg
960
+ off = @dir.int(JPEG_INTERCHANGE_FORMAT)
961
+ unless off&.positive? && off < @bytes.bytesize
962
+ raise UnsupportedImageError, "old-style JPEG TIFF images are not supported"
963
+ end
964
+
965
+ length = @dir.int(JPEG_INTERCHANGE_FORMAT_LENGTH)
966
+ length = @bytes.bytesize - off unless length&.positive? && length < @bytes.bytesize - off
967
+ image = JPEG.decode(@bytes.byteslice(off, length))
968
+ @decoded = 1
969
+ w = @width
970
+ rows = [image.height, @height].min
971
+ data = fit(image.data, image.width, rows, w)
972
+ data << WHITE * (w * (@height - rows)) if rows < @height
973
+ data
974
+ rescue UnsupportedImageError
975
+ raise
976
+ rescue ImageError
977
+ raise UnsupportedImageError, "old-style JPEG TIFF images are not supported"
978
+ end
979
+
980
+ # --- samples ------------------------------------------------------
981
+
982
+ # Unpacks the complete rows of a decompressed chunk to one byte per
983
+ # sample (values mapped by setup_samples). Returns [samples, rows].
984
+ def normalize(raw, cw, rows, nsamp)
985
+ bps = @bps
986
+ count = cw * nsamp
987
+ row_bytes = (count * bps + 7) >> 3
988
+ avail = raw.bytesize / row_bytes
989
+ avail = rows if avail > rows
990
+ return ["".b, 0] if avail.zero?
991
+
992
+ raw = raw.byteslice(0, avail * row_bytes) if raw.bytesize != avail * row_bytes
993
+ raw = undo_predictor(raw, row_bytes, avail, nsamp) if @predictor != 1
994
+ samples = case bps
995
+ when 8 then raw
996
+ when 1 then unpack_text(raw.unpack1("B*"), row_bytes * 8, count, avail)
997
+ when 4 then unpack_text(raw.unpack1("H*"), row_bytes * 2, count, avail)
998
+ end
999
+ samples ||= unpack_rows(raw, row_bytes, count, avail)
1000
+ samples.tr!(@tr_from, @tr_to) if @tr_from
1001
+ [samples, avail]
1002
+ end
1003
+
1004
+ # Rows of an unpacked bit/hex digit string trimmed to +count+ samples.
1005
+ def unpack_text(text, per_row, count, rows)
1006
+ text.force_encoding(Encoding::BINARY)
1007
+ return text if per_row == count
1008
+
1009
+ out = Bytes.buffer(count * rows)
1010
+ rows.times { |r| out << text.byteslice(r * per_row, count) }
1011
+ out
1012
+ end
1013
+
1014
+ def unpack_rows(raw, row_bytes, count, rows)
1015
+ bps = @bps
1016
+ out = Bytes.buffer(count * rows)
1017
+ if @float
1018
+ # File byte order, except after predictor 3 (always big-endian).
1019
+ directive = FLOATS[[bps, !@le || @predictor == 3]]
1020
+ rows.times { |r| floats_row(raw.byteslice(r * row_bytes, row_bytes).unpack(directive), out) }
1021
+ elsif bps == 2
1022
+ rows.times { |r| unpack2_row(raw, r * row_bytes, count, out) }
1023
+ elsif (bps & 7).zero?
1024
+ step = bps >> 3
1025
+ msb = @le ? step - 1 : 0
1026
+ rows.times { |r| pick(raw, r * row_bytes + msb, count, step, out) }
1027
+ else
1028
+ rows.times { |r| bits_row(raw, r * row_bytes, count, bps, out) }
1029
+ end
1030
+ out
1031
+ end
1032
+
1033
+ # 2-bit samples (MSB first) -> one byte 0..3 each.
1034
+ def unpack2_row(src, off, n, out)
1035
+ stop = off + (n >> 2)
1036
+ i = off
1037
+ while i < stop
1038
+ out << TWO_BIT[src.getbyte(i)]
1039
+ i += 1
1040
+ end
1041
+ out << TWO_BIT[src.getbyte(stop)].byteslice(0, n & 3) if (n & 3).positive?
1042
+ out
1043
+ end
1044
+
1045
+ # Every +step+-th byte (the most significant byte of wide samples).
1046
+ def pick(src, off, n, step, out)
1047
+ stop = off + n * step
1048
+ while off < stop
1049
+ out << src.getbyte(off)
1050
+ off += step
1051
+ end
1052
+ out
1053
+ end
1054
+
1055
+ # Samples of an odd bit depth (MSB first), reduced to 8 bits if wider.
1056
+ def bits_row(src, off, n, bps, out)
1057
+ pos = off * 8
1058
+ shift = bps > 8 ? bps - 8 : 0
1059
+ i = 0
1060
+ while i < n
1061
+ v = 0
1062
+ need = bps
1063
+ while need.positive?
1064
+ left = 8 - (pos & 7)
1065
+ take = need < left ? need : left
1066
+ v = (v << take) | ((src.getbyte(pos >> 3) >> (left - take)) & ((1 << take) - 1))
1067
+ pos += take
1068
+ need -= take
1069
+ end
1070
+ out << (v >> shift)
1071
+ i += 1
1072
+ end
1073
+ out
1074
+ end
1075
+
1076
+ # Floating point samples (0.0 = black, 1.0 = white) -> bytes.
1077
+ def floats_row(values, out)
1078
+ i = 0
1079
+ n = values.size
1080
+ while i < n
1081
+ v = values[i] * 255 + 0.5
1082
+ out << if v.nan? || v <= 0 then 0
1083
+ elsif v >= 255 then 255
1084
+ else v.to_i
1085
+ end
1086
+ i += 1
1087
+ end
1088
+ out
1089
+ end
1090
+
1091
+ # Returns +raw+ with the predictor undone (in place, or a new String).
1092
+ def undo_predictor(raw, row_bytes, rows, nsamp)
1093
+ bps = @bps
1094
+ if @predictor == 3
1095
+ rows.times do |r|
1096
+ off = r * row_bytes
1097
+ raw.bytesplice(off, row_bytes, float_row(raw.byteslice(off, row_bytes), nsamp, bps >> 3))
1098
+ end
1099
+ elsif bps == 8 && nsamp == 1
1100
+ out = Bytes.buffer(raw.bytesize)
1101
+ rows.times { |r| accumulate_row(raw, r * row_bytes, row_bytes, out) }
1102
+ return out
1103
+ elsif bps == 8
1104
+ rows.times { |r| accumulate_bytes(raw, r * row_bytes, row_bytes, nsamp) }
1105
+ else
1106
+ directive = WORDS[bps][@le ? 0 : 1]
1107
+ mask = (1 << bps) - 1
1108
+ rows.times do |r|
1109
+ off = r * row_bytes
1110
+ values = raw.byteslice(off, row_bytes).unpack(directive)
1111
+ accumulate(values, nsamp, mask)
1112
+ raw.bytesplice(off, row_bytes, values.pack(directive))
1113
+ end
1114
+ end
1115
+ raw
1116
+ end
1117
+
1118
+ # Horizontal differencing on one 8-bit sample per pixel, appended to
1119
+ # +out+ (appending beats setbyte in place by about 1.5x).
1120
+ def accumulate_row(src, off, n, out)
1121
+ acc = 0
1122
+ i = off
1123
+ stop = off + n
1124
+ while i < stop
1125
+ out << (acc = (acc + src.getbyte(i)) & 0xFF)
1126
+ i += 1
1127
+ end
1128
+ out
1129
+ end
1130
+
1131
+ # 8-bit RGB with horizontal differencing (what most writers produce
1132
+ # for colour LZW and Deflate): the predictor is undone while
1133
+ # converting to luminance, three to four times faster than two passes.
1134
+ def predicted_rgb_luma(raw, cw, rows)
1135
+ stride = @spp
1136
+ row_bytes = cw * stride
1137
+ avail = [raw.bytesize / row_bytes, rows].min
1138
+ return nil if avail.zero?
1139
+
1140
+ @decoded += 1
1141
+ out = Bytes.buffer(cw * avail)
1142
+ avail.times { |r| predicted_rgb_row(raw, r * row_bytes, cw, stride, out) }
1143
+ out
1144
+ end
1145
+
1146
+ def predicted_rgb_row(src, off, n, stride, out)
1147
+ r = g = b = 0
1148
+ i = off
1149
+ stop = off + n * stride
1150
+ while i < stop
1151
+ r = (r + src.getbyte(i)) & 0xFF
1152
+ g = (g + src.getbyte(i + 1)) & 0xFF
1153
+ b = (b + src.getbyte(i + 2)) & 0xFF
1154
+ out << ((306 * r + 601 * g + 117 * b + 0x200) >> 10)
1155
+ i += stride
1156
+ end
1157
+ out
1158
+ end
1159
+
1160
+ # Horizontal differencing on bytes (8-bit samples).
1161
+ def accumulate_bytes(raw, off, len, stride)
1162
+ i = off + stride
1163
+ stop = off + len
1164
+ while i < stop
1165
+ raw.setbyte(i, (raw.getbyte(i) + raw.getbyte(i - stride)) & 0xFF)
1166
+ i += 1
1167
+ end
1168
+ end
1169
+
1170
+ def accumulate(values, stride, mask)
1171
+ i = stride
1172
+ n = values.size
1173
+ while i < n
1174
+ values[i] = (values[i] + values[i - stride]) & mask
1175
+ i += 1
1176
+ end
1177
+ end
1178
+
1179
+ # Undoes the floating point predictor: byte differencing over the
1180
+ # row, then the bytes of each sample are gathered from byte planes
1181
+ # (most significant first). The result is big-endian.
1182
+ def float_row(row, stride, size)
1183
+ bytes = row.bytes
1184
+ i = stride
1185
+ n = bytes.size
1186
+ while i < n
1187
+ bytes[i] = (bytes[i] + bytes[i - stride]) & 0xFF
1188
+ i += 1
1189
+ end
1190
+ count = n / size
1191
+ planes = Array.new(size) { |b| bytes[b * count, count] }
1192
+ planes[0].zip(*planes[1..]).flatten!.pack("C*")
1193
+ end
1194
+
1195
+ # Y samples of subsampled YCbCr data (8 bits, data units of
1196
+ # h x v luma samples followed by Cb and Cr).
1197
+ def ycbcr_luma(raw, cw, rows)
1198
+ raise UnsupportedImageError, "#{@bps}-bit YCbCr TIFF images are not supported" unless @bps == 8
1199
+
1200
+ sh, sv = subsampling
1201
+ units = (cw + sh - 1) / sh
1202
+ unit_size = sh * sv + 2
1203
+ unit_rows = [raw.bytesize / (units * unit_size), (rows + sv - 1) / sv].min
1204
+ return nil if unit_rows.zero?
1205
+
1206
+ @decoded += 1
1207
+ out = Bytes.buffer(cw * rows)
1208
+ unit_rows.times do |ur|
1209
+ sv.times do |j|
1210
+ break if ur * sv + j >= rows
1211
+
1212
+ ycbcr_row(raw, ur * units * unit_size + j * sh, sh, unit_size, cw, out)
1213
+ end
1214
+ end
1215
+ out
1216
+ end
1217
+
1218
+ def ycbcr_row(raw, off, sh, unit_size, cw, out)
1219
+ x = 0
1220
+ while x < cw
1221
+ out << raw.getbyte(off + (x / sh) * unit_size + x % sh)
1222
+ x += 1
1223
+ end
1224
+ out
1225
+ end
1226
+
1227
+ def subsampling
1228
+ @subsampling ||= begin
1229
+ sh, sv = @dir.ints(YCBCR_SUBSAMPLING, 2) || [2, 2]
1230
+ sv ||= 2
1231
+ unless [1, 2, 4].include?(sh) && [1, 2, 4].include?(sv)
1232
+ raise ImageError, "invalid TIFF YCbCr subsampling #{sh}x#{sv}"
1233
+ end
1234
+
1235
+ [sh, sv]
1236
+ end
1237
+ end
1238
+
1239
+ # --- luminance ----------------------------------------------------
1240
+
1241
+ def convert(samples, cw, rows)
1242
+ return samples if @mode == :single
1243
+
1244
+ out = Bytes.buffer(cw * rows)
1245
+ stride = @stride
1246
+ rows.times { |r| convert_row(samples, r * cw * stride, cw, out) }
1247
+ out
1248
+ end
1249
+
1250
+ # Appends the luminance of +n+ pixels at +off+ to +out+.
1251
+ def convert_row(src, off, n, out)
1252
+ c = @channels
1253
+ stride = @stride
1254
+ case @mode
1255
+ when :pick then pick(src, off, n, stride, out)
1256
+ when :rgb then rgb_row(src, off, n, out, c[0], c[1], c[2], stride)
1257
+ when :rgba
1258
+ if @associated
1259
+ premultiplied_row(src, off, n, out, c[0], c[1], c[2], c[3], stride)
1260
+ else
1261
+ rgba_row(src, off, n, out, c[0], c[1], c[2], c[3], stride)
1262
+ end
1263
+ when :gray_alpha then gray_alpha_row(src, off, n, out, c[1], stride)
1264
+ when :cmyk then cmyk_row(src, off, n, out, c[0], c[1], c[2], c[3], stride)
1265
+ end
1266
+ end
1267
+
1268
+ def rgb_row(src, off, n, out, ro, go, bo, stride)
1269
+ i = off
1270
+ stop = off + n * stride
1271
+ while i < stop
1272
+ out << ((306 * src.getbyte(i + ro) + 601 * src.getbyte(i + go) + 117 * src.getbyte(i + bo) + 0x200) >> 10)
1273
+ i += stride
1274
+ end
1275
+ out
1276
+ end
1277
+
1278
+ # Unassociated alpha, composited over white like Image.from_pixels.
1279
+ def rgba_row(src, off, n, out, ro, go, bo, ao, stride)
1280
+ i = off
1281
+ stop = off + n * stride
1282
+ while i < stop
1283
+ v = (306 * src.getbyte(i + ro) + 601 * src.getbyte(i + go) + 117 * src.getbyte(i + bo) + 0x200) >> 10
1284
+ a = src.getbyte(i + ao)
1285
+ out << (a == 255 ? v : (v * a + 255 * (255 - a) + 127) / 255)
1286
+ i += stride
1287
+ end
1288
+ out
1289
+ end
1290
+
1291
+ # Associated (premultiplied) alpha: colour + (255 - alpha) of white.
1292
+ def premultiplied_row(src, off, n, out, ro, go, bo, ao, stride)
1293
+ i = off
1294
+ stop = off + n * stride
1295
+ while i < stop
1296
+ v = ((306 * src.getbyte(i + ro) + 601 * src.getbyte(i + go) + 117 * src.getbyte(i + bo) + 0x200) >> 10) +
1297
+ 255 - src.getbyte(i + ao)
1298
+ out << (v > 255 ? 255 : v)
1299
+ i += stride
1300
+ end
1301
+ out
1302
+ end
1303
+
1304
+ def gray_alpha_row(src, off, n, out, ao, stride)
1305
+ invert = @invert
1306
+ associated = @associated
1307
+ i = off
1308
+ stop = off + n * stride
1309
+ while i < stop
1310
+ v = src.getbyte(i)
1311
+ a = src.getbyte(i + ao)
1312
+ if associated
1313
+ v = a - v if invert
1314
+ v += 255 - a
1315
+ out << (v > 255 ? 255 : v)
1316
+ else
1317
+ v = 255 - v if invert
1318
+ out << (a == 255 ? v : (v * a + 255 * (255 - a) + 127) / 255)
1319
+ end
1320
+ i += stride
1321
+ end
1322
+ out
1323
+ end
1324
+
1325
+ # Ink amounts (0 = none): R = (255 - C) * (255 - K) / 255, ..., like
1326
+ # the JPEG decoder's Adobe CMYK conversion.
1327
+ def cmyk_row(src, off, n, out, co, mo, yo, ko, stride)
1328
+ i = off
1329
+ stop = off + n * stride
1330
+ while i < stop
1331
+ k = 255 - src.getbyte(i + ko)
1332
+ r = ((255 - src.getbyte(i + co)) * k + 127) / 255
1333
+ g = ((255 - src.getbyte(i + mo)) * k + 127) / 255
1334
+ b = ((255 - src.getbyte(i + yo)) * k + 127) / 255
1335
+ out << ((306 * r + 601 * g + 117 * b + 0x200) >> 10)
1336
+ i += stride
1337
+ end
1338
+ out
1339
+ end
1340
+ end
1341
+
1342
+ private_constant(*constants)
1343
+ end
1344
+ end
1345
+ end