qr_reader 0.0.1 → 0.0.2

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.
@@ -0,0 +1,764 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../errors"
4
+ require_relative "../image"
5
+ require_relative "jpeg2000/codestream"
6
+ require_relative "jpeg2000/container"
7
+ require_relative "jpeg2000/tier1"
8
+ require_relative "jpeg2000/dwt"
9
+ require_relative "jpeg2000/tile"
10
+
11
+ module QrReader
12
+ module Formats
13
+ # Pure-Ruby JPEG 2000 (ITU-T T.800 | ISO/IEC 15444-1, "Part 1") decoder
14
+ # for JP2 files and raw codestreams, e.g. the JPXDecode images of PDFs.
15
+ #
16
+ # JPEG2000.decode(bytes) # => QrReader::Image (luminance)
17
+ # JPEG2000.decode(bytes, reduce: 2) # => 1/4 of the size, cheaply
18
+ # JPEG2000.dimensions(bytes) # => [width, height]
19
+ # JPEG2000.decode_components(bytes) # => JPEG2000::Components
20
+ #
21
+ # Supported: everything in Part 1 - any number of components with 1 to
22
+ # 30 bits (signed or not) and sub-sampling, tiles (any order, several
23
+ # tile-parts, missing tiles), all progression orders with precincts and
24
+ # POC changes, quality layers, PPM/PPT packed headers, SOP/EPH markers,
25
+ # all code-block styles (bypass, reset, termall, vertically causal,
26
+ # predictable termination, segmentation symbols), ROI max-shift, 5/3
27
+ # and 9/7 wavelets with RCT/ICT, and the JP2 boxes colr (enumerated and
28
+ # ICC), pclr/cmap palettes and cdef channel definitions. Part 2
29
+ # extensions and HTJ2K raise UnsupportedImageError; truncated data
30
+ # decodes what is present (missing tiles stay mid-gray); other
31
+ # corruption raises ImageError.
32
+ #
33
+ # Samples are converted to 8 bits: level shift (unsigned) or offset
34
+ # 2**(p-1) (signed), clamping, then v * 255 / (2**p - 1) rounded for
35
+ # precisions other than 8. Luminance: gray and sYCC take the first
36
+ # channel, RGB Image.luma, CMYK the PDF formula; without colour
37
+ # information 1 or 2 channels are gray (+ alpha), 3 RGB and 4 CMYK.
38
+ # Alpha channels (cdef) are ignored.
39
+ #
40
+ # +reduce: n+ discards the n highest resolution levels (like OpenJPEG's
41
+ # reduce factor): the result is ceil(w / 2**n) x ceil(h / 2**n) for an
42
+ # image at the grid origin, and the discarded code-blocks are not
43
+ # decoded at all. It is limited to the decomposition levels present.
44
+ #
45
+ # Memory: tiles are decoded one by one and components one by one; the
46
+ # wavelet planes of the tile-component being decoded take about 8 bytes
47
+ # per sample (Ruby Arrays), the components 0 and 1 of an RGB tile wait
48
+ # for the inverse colour transform packed at 4 bytes per sample, and the
49
+ # output takes 1 byte per pixel and channel. A tile-component larger
50
+ # than MAX_DECODE_SAMPLES at the decoded resolution raises
51
+ # UnsupportedImageError: decode it with a +reduce+ factor.
52
+ module JPEG2000
53
+ # The decoded channels of an image (see JPEG2000.decode_components):
54
+ # +components+ are binary Strings of width * height 8-bit samples
55
+ # (after palette, inverse MCT and up-sampling, in codestream /
56
+ # palette order), +color+ is :gray, :rgb, :ycc (sYCC), :cmyk or nil
57
+ # (unknown), +palette_applied+ whether a JP2 palette mapped the
58
+ # components, +alpha+ the indices of opacity channels (cdef).
59
+ Components = Data.define(:width, :height, :components, :color, :palette_applied, :alpha)
60
+
61
+ # Exceptions that only a bug triggered by corrupt input could raise;
62
+ # they are reported as ImageError.
63
+ INTERNAL_ERRORS = [NoMethodError, TypeError, ArgumentError, RangeError, IndexError, ZeroDivisionError,
64
+ EncodingError, FrozenError, FloatDomainError, KeyError, StopIteration].freeze
65
+
66
+ # Enumerated colour spaces (I.5.3.3 and Part 2) we can interpret.
67
+ ENUM_COLORS = {
68
+ 16 => :rgb, 20 => :rgb, 21 => :rgb, 17 => :gray, 12 => :cmyk,
69
+ 18 => :ycc, 24 => :ycc, 1 => :ycc, 3 => :ycc, 4 => :ycc, 22 => :ycc, 23 => :ycc
70
+ }.freeze
71
+ ICC_COLORS = { "GRAY" => :gray, "RGB " => :rgb, "CMYK" => :cmyk, "YCbr" => :ycc }.freeze
72
+
73
+ # Largest tile-component (samples at the decoded resolution) decoded
74
+ # at once; larger ones need +reduce:+.
75
+ MAX_DECODE_SAMPLES = TileDecoder::MAX_DECODE_SAMPLES
76
+
77
+ class << self
78
+ # Decodes a JP2 file or codestream to a luminance Image.
79
+ def decode(bytes, reduce: 0)
80
+ reduce = check_reduce(reduce)
81
+ bytes = binary(bytes)
82
+ width, height, data = guard { Decoder.new(bytes).luminance(reduce) }
83
+ Image.new(width, height, data)
84
+ end
85
+
86
+ # Decodes all channels (see Components); +palette: false+ leaves JP2
87
+ # palette indices unmapped.
88
+ def decode_components(bytes, reduce: 0, palette: true)
89
+ reduce = check_reduce(reduce)
90
+ bytes = binary(bytes)
91
+ guard { Decoder.new(bytes).components(reduce, palette: palette) }
92
+ end
93
+
94
+ # [width, height] of the image from the SIZ marker only.
95
+ def dimensions(bytes)
96
+ bytes = binary(bytes)
97
+ guard do
98
+ header = Container.parse(bytes)
99
+ Codestream.image_size(header.codestream)
100
+ end
101
+ end
102
+
103
+ # The smallest +reduce+ at which #decode accepts the image: 0 unless
104
+ # a tile is larger than MAX_DECODE_SAMPLES (big single-tile pages).
105
+ def fitting_reduce(bytes)
106
+ bytes = binary(bytes)
107
+ guard do
108
+ codestream = Container.parse(bytes).codestream
109
+ width, height = Codestream.image_size(codestream)
110
+ tile_w, tile_h = codestream.byteslice(24, 8).unpack("N2")
111
+ tile_w = width if tile_w.nil? || tile_w > width
112
+ tile_h = height if tile_h.nil? || tile_h > height
113
+ n = 0
114
+ n += 1 while n < 32 && ((tile_w + (1 << n) - 1) >> n) * ((tile_h + (1 << n) - 1) >> n) > MAX_DECODE_SAMPLES
115
+ n
116
+ end
117
+ end
118
+
119
+ # The luminance bytes of decoded Components (internal).
120
+ def luminance(comps)
121
+ chans = comps.components
122
+ colour = colour_channels(comps)
123
+ kind = comps.color
124
+ kind ||= case colour.size
125
+ when 3 then :rgb
126
+ when 4.. then :cmyk
127
+ else :gray
128
+ end
129
+ kind = :gray if (kind == :rgb && colour.size < 3) || (kind == :cmyk && colour.size < 4)
130
+ case kind
131
+ when :rgb then rgb_luma(chans[colour[0]], chans[colour[1]], chans[colour[2]])
132
+ when :cmyk then cmyk_luma(chans[colour[0]], chans[colour[1]], chans[colour[2]], chans[colour[3]])
133
+ else chans[colour[0] || 0]
134
+ end
135
+ end
136
+
137
+ private
138
+
139
+ def guard
140
+ yield
141
+ rescue *INTERNAL_ERRORS, PacketError, SystemStackError => e
142
+ raise ImageError, "corrupt JPEG 2000 data (internal #{e.class}: #{e.message})"
143
+ end
144
+
145
+ def check_reduce(reduce)
146
+ unless reduce.is_a?(Integer) && reduce >= 0
147
+ raise ArgumentError, "reduce must be a non-negative Integer, not #{reduce.inspect}"
148
+ end
149
+
150
+ reduce
151
+ end
152
+
153
+ def binary(bytes)
154
+ raise ImageError, "JPEG 2000 data must be a String" unless bytes.is_a?(String)
155
+
156
+ bytes.encoding == Encoding::BINARY ? bytes : bytes.b
157
+ end
158
+
159
+ # Indices of the colour channels (alpha removed, cdef order).
160
+ def colour_channels(comps)
161
+ n = comps.components.size
162
+ alpha = comps.alpha
163
+ list = (0...n).reject { |i| alpha.include?(i) }
164
+ list.empty? ? [0] : list
165
+ end
166
+
167
+ def rgb_luma(r, g, b)
168
+ out = Image.buffer(r.bytesize)
169
+ n = r.bytesize
170
+ pos = 0
171
+ while pos < n
172
+ len = n - pos
173
+ len = 4096 if len > 4096
174
+ rgb_luma_run(r, g, b, pos, len, out)
175
+ pos += len
176
+ end
177
+ out
178
+ end
179
+
180
+ def rgb_luma_run(r, g, b, i, n, out)
181
+ stop = i + n
182
+ while i < stop
183
+ out << ((306 * r.getbyte(i) + 601 * g.getbyte(i) + 117 * b.getbyte(i) + 0x200) >> 10)
184
+ i += 1
185
+ end
186
+ end
187
+
188
+ def cmyk_luma(c, m, y, k)
189
+ n = c.bytesize
190
+ out = Image.buffer(n)
191
+ i = 0
192
+ while i < n
193
+ kk = 255 - k.getbyte(i)
194
+ out << Image.luma(((255 - c.getbyte(i)) * kk + 127) / 255, ((255 - m.getbyte(i)) * kk + 127) / 255,
195
+ ((255 - y.getbyte(i)) * kk + 127) / 255)
196
+ i += 1
197
+ end
198
+ out
199
+ end
200
+ end
201
+
202
+ # Turns a JP2 file or codestream into Components (or luminance).
203
+ class Decoder
204
+ # Adding and subtracting 1.5 * 2**52 rounds a Float half to even
205
+ # (like lrintf in OpenJPEG).
206
+ ROUND_EVEN = 6_755_399_441_055_744.0
207
+ # Most tile-components (tiles x components) decoded.
208
+ MAX_TILE_COMPONENTS = 1 << 20
209
+
210
+ Plane = Struct.new(:x0, :y0, :width, :height, :data, :raw)
211
+
212
+ def initialize(bytes)
213
+ @header = Container.parse(bytes)
214
+ @cs = Codestream.new(@header.codestream)
215
+ end
216
+
217
+ # All channels as Components. +palette+ false leaves the components
218
+ # a JP2 palette indexes as their index values (as PDFs with their
219
+ # own /ColorSpace use them).
220
+ def components(reduce, palette: true)
221
+ @use_palette = palette
222
+ setup(reduce)
223
+ palette_index = palette_components
224
+ @planes = @cs.components.each_with_index.map do |comp, c|
225
+ x0, y0, w, h = plane_rect(comp)
226
+ check_size(w, h)
227
+ raw = palette_index.include?(c)
228
+ Plane.new(x0, y0, w, h, raw ? Array.new(w * h, 0) : ("\x80".b * (w * h)), raw)
229
+ end
230
+ each_tile { |td| decode_tile(td) }
231
+ finish
232
+ end
233
+
234
+ # [width, height, luminance bytes]. RGB images of 8-bit samples go
235
+ # straight from the wavelet rows to luminance (components 3 and up,
236
+ # e.g. alpha, are not even decoded); everything else via
237
+ # #components.
238
+ def luminance(reduce)
239
+ setup(reduce)
240
+ return fused_luminance if fused_rgb?
241
+
242
+ comps = components(reduce)
243
+ [comps.width, comps.height, JPEG2000.luminance(comps)]
244
+ end
245
+
246
+ private
247
+
248
+ def setup(reduce)
249
+ cs = @cs
250
+ @reduce = [reduce, max_reduce].min
251
+ @width = ceil_shift(cs.x1, @reduce) - ceil_shift(cs.x0, @reduce)
252
+ @height = ceil_shift(cs.y1, @reduce) - ceil_shift(cs.y0, @reduce)
253
+ check_size(@width, @height)
254
+ # Bounds for headers declaring thousands of components: the
255
+ # planes and the per-tile structures.
256
+ samples = cs.components.sum { |comp| plane_rect(comp).then { |_, _, w, h| w * h } }
257
+ if samples > 4 * Formats::MAX_PIXELS || cs.components.size * [cs.tiles.size, 1].max > MAX_TILE_COMPONENTS
258
+ raise UnsupportedImageError, "JPEG 2000 image with too many components (#{cs.components.size})"
259
+ end
260
+ end
261
+
262
+ def each_tile
263
+ tier1 = Tier1.new
264
+ @cs.tile_count.times do |t|
265
+ info = @cs.tiles[t] or next
266
+ td = TileDecoder.new(@cs, info, @reduce, tier1)
267
+ td.read_packets
268
+ yield td
269
+ end
270
+ end
271
+
272
+ def ceil_shift(a, k)
273
+ -((-a) >> k)
274
+ end
275
+
276
+ def ceil_div(a, b)
277
+ -(-a / b)
278
+ end
279
+
280
+ def check_size(w, h)
281
+ raise ImageError, "empty JPEG 2000 image" unless w.positive? && h.positive?
282
+ return if w * h <= Formats::MAX_PIXELS
283
+
284
+ raise UnsupportedImageError, "JPEG 2000 image too large (#{w}x#{h}, limit #{Formats::MAX_PIXELS} pixels)"
285
+ end
286
+
287
+ # The reduce factor is limited by the main header's levels.
288
+ def max_reduce
289
+ main = @cs.main
290
+ (0...@cs.components.size).map { |c| (main.coc[c] || main.style)&.levels || 0 }.min
291
+ end
292
+
293
+ def plane_rect(comp)
294
+ r = @reduce
295
+ x0 = ceil_shift(ceil_div(@cs.x0, comp.dx), r)
296
+ y0 = ceil_shift(ceil_div(@cs.y0, comp.dy), r)
297
+ x1 = ceil_shift(ceil_div(@cs.x1, comp.dx), r)
298
+ y1 = ceil_shift(ceil_div(@cs.y1, comp.dy), r)
299
+ [x0, y0, x1 - x0, y1 - y0]
300
+ end
301
+
302
+ # Components that are palette indices (kept unscaled).
303
+ def palette_components
304
+ return [] unless @header.palette
305
+
306
+ cmap = @header.cmap
307
+ return [0] unless cmap
308
+
309
+ cmap.select { |_, type, _| type == 1 }.map(&:first).uniq
310
+ end
311
+
312
+ def plain8?(comp)
313
+ comp.precision == 8 && !comp.signed
314
+ end
315
+
316
+ # Whether the luminance is Image.luma of components 0..2.
317
+ def fused_rgb?
318
+ comps = @cs.components
319
+ return false if @header.palette || comps.size < 3
320
+ return false unless comps[0, 3].all? { |c| plain8?(c) && c.dx == 1 && c.dy == 1 }
321
+
322
+ alpha = alpha_channels(comps.size)
323
+ channels = (0...comps.size).reject { |i| alpha.include?(i) }
324
+ kind = colour || (channels.size == 3 ? :rgb : nil)
325
+ kind == :rgb && channels.first(3) == [0, 1, 2]
326
+ end
327
+
328
+ # The rows of components 0..2 of a tile, their common rectangle and
329
+ # transform (:rct, :ict or nil). With +pack+, the rows of components
330
+ # 0 and 1 are kept as packed Strings (4 bytes per sample instead of
331
+ # 8 while the next component is decoded; see #row_values).
332
+ def rgb_rows(td, pack: true)
333
+ comps = td.components
334
+ rects = (0..2).map { |i| comps[i].output_rect }
335
+ raise ImageError, "JPEG 2000 MCT on components of different sizes" unless rects.uniq.size == 1
336
+
337
+ transform = (comps[0].reversible? ? :rct : :ict) if td.mct == 1
338
+ rows = (0..2).map do |i|
339
+ r = td.component_rows(i)
340
+ if r.nil? && !pack
341
+ x0, y0, x1, y1 = rects[0]
342
+ zero = comps[i].reversible? ? 0 : 0.0
343
+ r = Array.new(y1 - y0) { Array.new(x1 - x0, zero) }
344
+ elsif r && pack && i < 2
345
+ format = comps[i].reversible? ? "l*" : "e*"
346
+ r.map! { |row| row.pack(format).tap { row.clear } }
347
+ end
348
+ r
349
+ end
350
+ [rows, rects[0], transform, comps[0, 3].map { |tc| tc.reversible? ? "l*" : "e*" }]
351
+ end
352
+
353
+ # A row of #rgb_rows as an Array (+zero+ Arrays of +width+ for
354
+ # all-zero components).
355
+ def row_values(rows, j, format, width, zero)
356
+ return Array.new(width, zero) unless rows
357
+
358
+ row = rows[j]
359
+ row.is_a?(String) ? row.unpack(format) : row
360
+ end
361
+
362
+ def fused_luminance
363
+ luma = "\x80".b * (@width * @height)
364
+ gx0 = ceil_shift(@cs.x0, @reduce)
365
+ gy0 = ceil_shift(@cs.y0, @reduce)
366
+ each_tile do |td|
367
+ (r0, r1, r2), (x0, y0, x1, y1), transform, (f0, f1, f2) = rgb_rows(td)
368
+ w = x1 - x0
369
+ next if w <= 0
370
+
371
+ j = 0
372
+ while j < y1 - y0
373
+ a = row_values(r0, j, f0, w, f0 == "l*" ? 0 : 0.0)
374
+ b = row_values(r1, j, f1, w, f1 == "l*" ? 0 : 0.0)
375
+ c = row_values(r2, j, f2, w, f2 == "l*" ? 0 : 0.0)
376
+ bytes = case transform
377
+ when :ict then luma_ict_row(a, b, c, w)
378
+ when :rct then luma_rct_row(a, b, c, w)
379
+ else luma_plain_row(a, b, c, w)
380
+ end
381
+ luma.bytesplice((y0 + j - gy0) * @width + (x0 - gx0), w, bytes)
382
+ c.clear # frees the row now rather than at the next major GC
383
+ r0[j] = nil if r0
384
+ r1[j] = nil if r1
385
+ r2[j] = nil if r2
386
+ j += 1
387
+ end
388
+ end
389
+ [@width, @height, luma]
390
+ end
391
+
392
+ def luma_ict_row(a, b, c, w)
393
+ m = ROUND_EVEN
394
+ k = 128.0 + m
395
+ out = Array.new(w)
396
+ x = 0
397
+ while x < w
398
+ y = a[x]
399
+ u = b[x]
400
+ v = c[x]
401
+ r = (y + 1.402 * v + k) - m
402
+ r = 0.0 if r < 0.0
403
+ r = 255.0 if r > 255.0
404
+ g = (y - 0.34413 * u - 0.71414 * v + k) - m
405
+ g = 0.0 if g < 0.0
406
+ g = 255.0 if g > 255.0
407
+ bl = (y + 1.772 * u + k) - m
408
+ bl = 0.0 if bl < 0.0
409
+ bl = 255.0 if bl > 255.0
410
+ out[x] = (306.0 * r + 601.0 * g + 117.0 * bl + 512.0) * 0.0009765625
411
+ x += 1
412
+ end
413
+ out.pack("C*")
414
+ end
415
+
416
+ def luma_rct_row(a, b, c, w)
417
+ out = Array.new(w)
418
+ x = 0
419
+ while x < w
420
+ u = b[x]
421
+ v = c[x]
422
+ g = a[x] - ((u + v) >> 2) + 128
423
+ r = v + g
424
+ bl = u + g
425
+ r = 0 if r.negative?
426
+ r = 255 if r > 255
427
+ g = 0 if g.negative?
428
+ g = 255 if g > 255
429
+ bl = 0 if bl.negative?
430
+ bl = 255 if bl > 255
431
+ out[x] = (306 * r + 601 * g + 117 * bl + 512) >> 10
432
+ x += 1
433
+ end
434
+ out.pack("C*")
435
+ end
436
+
437
+ def luma_plain_row(a, b, c, w)
438
+ r = sample_row(a, w, 128, 255)
439
+ g = sample_row(b, w, 128, 255)
440
+ bl = sample_row(c, w, 128, 255)
441
+ out = Array.new(w)
442
+ x = 0
443
+ while x < w
444
+ out[x] = (306 * r[x] + 601 * g[x] + 117 * bl[x] + 512) >> 10
445
+ x += 1
446
+ end
447
+ out.pack("C*")
448
+ end
449
+
450
+ # Integer samples round(v + add) clamped to 0..maxv.
451
+ def sample_row(row, w, add, maxv)
452
+ out = Array.new(w)
453
+ x = 0
454
+ if row[0].is_a?(Float)
455
+ m = ROUND_EVEN
456
+ k = add + m
457
+ fmax = maxv.to_f
458
+ while x < w
459
+ v = (row[x] + k) - m
460
+ v = 0.0 if v < 0.0
461
+ v = fmax if v > fmax
462
+ out[x] = v.to_i
463
+ x += 1
464
+ end
465
+ else
466
+ while x < w
467
+ v = row[x] + add
468
+ v = 0 if v.negative?
469
+ v = maxv if v > maxv
470
+ out[x] = v
471
+ x += 1
472
+ end
473
+ end
474
+ out
475
+ end
476
+
477
+ def decode_tile(td)
478
+ comps = td.components
479
+ ncomp = comps.size
480
+ c = 0
481
+ if td.mct == 1 && ncomp >= 3
482
+ if (0..2).all? { |i| plain8?(@cs.components[i]) && !@planes[i].raw }
483
+ rows, rect, transform, formats = rgb_rows(td)
484
+ store_mct(rows, rect, transform, formats)
485
+ else
486
+ rows, rect, transform, = rgb_rows(td, pack: false)
487
+ transform == :rct ? inverse_rct(*rows, rect) : inverse_ict(*rows, rect)
488
+ 3.times { |i| store(comps[i], rows[i]) }
489
+ end
490
+ c = 3
491
+ end
492
+ while c < ncomp
493
+ store(comps[c], td.component_rows(c))
494
+ c += 1
495
+ end
496
+ end
497
+
498
+ # Inverse MCT of 8-bit components straight into their planes.
499
+ def store_mct(rows, rect, transform, formats)
500
+ x0, y0, x1, y1 = rect
501
+ w = x1 - x0
502
+ return if w <= 0
503
+
504
+ planes = @planes[0, 3]
505
+ r0, r1, r2 = rows
506
+ j = 0
507
+ while j < y1 - y0
508
+ zeros = formats.map { |f| f == "l*" ? 0 : 0.0 }
509
+ a = row_values(r0, j, formats[0], w, zeros[0])
510
+ b = row_values(r1, j, formats[1], w, zeros[1])
511
+ c = row_values(r2, j, formats[2], w, zeros[2])
512
+ outs = transform == :rct ? rct_bytes(a, b, c, w) : ict_bytes(a, b, c, w)
513
+ 3.times do |i|
514
+ plane = planes[i]
515
+ plane.data.bytesplice((y0 + j - plane.y0) * plane.width + (x0 - plane.x0), w, outs[i])
516
+ end
517
+ c.clear
518
+ r0[j] = nil if r0
519
+ r1[j] = nil if r1
520
+ r2[j] = nil if r2
521
+ j += 1
522
+ end
523
+ end
524
+
525
+ def ict_bytes(a, b, c, w)
526
+ m = ROUND_EVEN
527
+ k = 128.0 + m
528
+ ro = Array.new(w)
529
+ go = Array.new(w)
530
+ bo = Array.new(w)
531
+ x = 0
532
+ while x < w
533
+ y = a[x]
534
+ u = b[x]
535
+ v = c[x]
536
+ r = (y + 1.402 * v + k) - m
537
+ r = 0.0 if r < 0.0
538
+ r = 255.0 if r > 255.0
539
+ ro[x] = r
540
+ g = (y - 0.34413 * u - 0.71414 * v + k) - m
541
+ g = 0.0 if g < 0.0
542
+ g = 255.0 if g > 255.0
543
+ go[x] = g
544
+ bl = (y + 1.772 * u + k) - m
545
+ bl = 0.0 if bl < 0.0
546
+ bl = 255.0 if bl > 255.0
547
+ bo[x] = bl
548
+ x += 1
549
+ end
550
+ [ro.pack("C*"), go.pack("C*"), bo.pack("C*")]
551
+ end
552
+
553
+ def rct_bytes(a, b, c, w)
554
+ ro = Array.new(w)
555
+ go = Array.new(w)
556
+ bo = Array.new(w)
557
+ x = 0
558
+ while x < w
559
+ u = b[x]
560
+ v = c[x]
561
+ g = a[x] - ((u + v) >> 2) + 128
562
+ r = v + g
563
+ bl = u + g
564
+ r = 0 if r.negative?
565
+ r = 255 if r > 255
566
+ g = 0 if g.negative?
567
+ g = 255 if g > 255
568
+ bl = 0 if bl.negative?
569
+ bl = 255 if bl > 255
570
+ ro[x] = r
571
+ go[x] = g
572
+ bo[x] = bl
573
+ x += 1
574
+ end
575
+ [ro.pack("C*"), go.pack("C*"), bo.pack("C*")]
576
+ end
577
+
578
+ def inverse_rct(r0, r1, r2, rect)
579
+ w = rect[2] - rect[0]
580
+ r0.each_index { |j| rct_row(r0[j], r1[j], r2[j], w) }
581
+ end
582
+
583
+ def rct_row(a, b, c, w)
584
+ x = 0
585
+ while x < w
586
+ y = a[x]
587
+ u = b[x]
588
+ v = c[x]
589
+ g = y - ((u + v) >> 2)
590
+ a[x] = v + g
591
+ b[x] = g
592
+ c[x] = u + g
593
+ x += 1
594
+ end
595
+ end
596
+
597
+ def inverse_ict(r0, r1, r2, rect)
598
+ w = rect[2] - rect[0]
599
+ r0.each_index { |j| ict_row(r0[j], r1[j], r2[j], w) }
600
+ end
601
+
602
+ def ict_row(a, b, c, w)
603
+ x = 0
604
+ while x < w
605
+ y = a[x]
606
+ u = b[x]
607
+ v = c[x]
608
+ a[x] = y + 1.402 * v
609
+ b[x] = y - 0.34413 * u - 0.71414 * v
610
+ c[x] = y + 1.772 * u
611
+ x += 1
612
+ end
613
+ end
614
+
615
+ # Writes a tile-component's rows into its plane: level shift (or
616
+ # signed offset) to 0..2**p-1, clamp, scale to 8 bits; palette
617
+ # indices stay unscaled.
618
+ def store(tc, rows)
619
+ comp = @cs.components[tc.index]
620
+ plane = @planes[tc.index]
621
+ x0, y0, x1, y1 = tc.output_rect
622
+ w = x1 - x0
623
+ return if w <= 0
624
+
625
+ prec = comp.precision
626
+ maxv = (1 << prec) - 1
627
+ add = 1 << (prec - 1)
628
+ return store_constant(plane, x0, y0, w, y1 - y0, add, maxv, prec) unless rows
629
+
630
+ j = 0
631
+ while j < y1 - y0
632
+ samples = sample_row(rows[j], w, add, maxv)
633
+ rows[j].clear
634
+ rows[j] = nil
635
+ pos = (y0 + j - plane.y0) * plane.width + (x0 - plane.x0)
636
+ if plane.raw
637
+ plane.data[pos, w] = samples
638
+ else
639
+ scale_row(samples, w, maxv) unless prec == 8
640
+ plane.data.bytesplice(pos, w, samples.pack("C*"))
641
+ end
642
+ j += 1
643
+ end
644
+ end
645
+
646
+ # Fills a tile-component whose samples are all 0 (+add+ after the
647
+ # level shift).
648
+ def store_constant(plane, x0, y0, w, h, add, maxv, prec)
649
+ value = add > maxv ? maxv : add
650
+ if plane.raw
651
+ h.times { |j| plane.data[(y0 + j - plane.y0) * plane.width + (x0 - plane.x0), w] = Array.new(w, value) }
652
+ return
653
+ end
654
+
655
+ value = (value * 255 + (maxv >> 1)) / maxv unless prec == 8
656
+ bytes = value.chr * w
657
+ h.times { |j| plane.data.bytesplice((y0 + j - plane.y0) * plane.width + (x0 - plane.x0), w, bytes) }
658
+ end
659
+
660
+ # v * 255 / maxv, rounded.
661
+ def scale_row(samples, w, maxv)
662
+ half = maxv >> 1
663
+ x = 0
664
+ while x < w
665
+ samples[x] = (samples[x] * 255 + half) / maxv
666
+ x += 1
667
+ end
668
+ end
669
+
670
+ # Palette, up-sampling and colour information -> Components.
671
+ def finish
672
+ channels, sources, palette_applied = map_palette
673
+ strings = channels.each_with_index.map { |data, i| upsample(data, @cs.components[sources[i]]) }
674
+ Components.new(width: @width, height: @height, components: strings, color: colour,
675
+ palette_applied: palette_applied, alpha: alpha_channels(strings.size))
676
+ end
677
+
678
+ # [channel planes, source component of each, palette applied?].
679
+ def map_palette
680
+ palette = @header.palette
681
+ unless palette && @use_palette != false
682
+ data = @planes.map { |plane| plane.raw ? plane.data.map { |v| v.clamp(0, 255) }.pack("C*") : plane.data }
683
+ return [data, (0...@planes.size).to_a, false]
684
+ end
685
+
686
+ cmap = @header.cmap || Array.new(palette.depths.size) { |i| [0, 1, i] }
687
+ channels = []
688
+ sources = []
689
+ cmap.each do |cmp, type, col|
690
+ plane = @planes[cmp] or raise ImageError, "JPEG 2000 cmap names a missing component"
691
+ sources << cmp
692
+ if type == 1
693
+ raise ImageError, "JPEG 2000 cmap names a missing palette column" if col >= palette.depths.size
694
+
695
+ channels << palette_channel(plane, palette, col)
696
+ else
697
+ raise ImageError, "JPEG 2000 cmap maps a palette index directly" if plane.raw
698
+
699
+ channels << plane.data
700
+ end
701
+ end
702
+ [channels, sources, true]
703
+ end
704
+
705
+ def palette_channel(plane, palette, col)
706
+ depth = palette.depths[col]
707
+ maxv = (1 << depth) - 1
708
+ signed = palette.signed[col]
709
+ lut = palette.entries.map do |entry|
710
+ v = entry[col]
711
+ v -= 1 << depth if signed && v >= 1 << (depth - 1)
712
+ v += 1 << (depth - 1) if signed
713
+ depth == 8 ? v : (v * 255 + (maxv >> 1)) / maxv
714
+ end
715
+ last = lut.size - 1
716
+ table = lut.pack("C*")
717
+ data = plane.data
718
+ out = Image.buffer(data.size)
719
+ i = 0
720
+ n = data.size
721
+ while i < n
722
+ v = data[i]
723
+ v = last if v > last
724
+ out << table.getbyte(v)
725
+ i += 1
726
+ end
727
+ out
728
+ end
729
+
730
+ # The channel scaled to the image grid (pixel replication).
731
+ def upsample(data, comp)
732
+ return data if comp.dx == 1 && comp.dy == 1
733
+
734
+ x0, y0, cw, ch = plane_rect(comp)
735
+ gx0 = ceil_shift(@cs.x0, @reduce)
736
+ gy0 = ceil_shift(@cs.y0, @reduce)
737
+ xs = Array.new(@width) { |x| ((gx0 + x) / comp.dx - x0).clamp(0, cw - 1) }
738
+ out = Image.buffer(@width * @height)
739
+ @height.times do |y|
740
+ cy = ((gy0 + y) / comp.dy - y0).clamp(0, ch - 1)
741
+ out << data.byteslice(cy * cw, cw).unpack("C*").values_at(*xs).pack("C*")
742
+ end
743
+ out
744
+ end
745
+
746
+ def colour
747
+ h = @header
748
+ return ENUM_COLORS[h.enum_cs] if h.enum_cs
749
+ return ICC_COLORS[h.icc_space] if h.icc_space
750
+
751
+ nil
752
+ end
753
+
754
+ def alpha_channels(count)
755
+ cdef = @header.cdef
756
+ return [] unless cdef
757
+
758
+ cdef.select { |d| (d.type == 1 || d.type == 2) && d.channel < count }.map(&:channel)
759
+ end
760
+ end
761
+ private_constant :Decoder
762
+ end
763
+ end
764
+ end