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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +18 -0
- data/README.md +23 -7
- data/exe/qr_reader +1 -1
- data/lib/qr_reader/formats/jpeg.rb +61 -2
- data/lib/qr_reader/formats/jpeg2000/codestream.rb +447 -0
- data/lib/qr_reader/formats/jpeg2000/container.rb +156 -0
- data/lib/qr_reader/formats/jpeg2000/dwt.rb +607 -0
- data/lib/qr_reader/formats/jpeg2000/tier1.rb +1084 -0
- data/lib/qr_reader/formats/jpeg2000/tile.rb +722 -0
- data/lib/qr_reader/formats/jpeg2000.rb +764 -0
- data/lib/qr_reader/formats/pdf/font_programs.rb +1205 -0
- data/lib/qr_reader/formats/pdf/font_tables.rb +224 -0
- data/lib/qr_reader/formats/pdf/fonts.rb +412 -0
- data/lib/qr_reader/formats/pdf/text.rb +151 -0
- data/lib/qr_reader/formats/pdf/vector.rb +799 -0
- data/lib/qr_reader/formats/pdf.rb +183 -44
- data/lib/qr_reader/formats.rb +13 -4
- data/lib/qr_reader/reader.rb +36 -10
- data/lib/qr_reader/version.rb +1 -1
- data/lib/qr_reader.rb +1 -1
- metadata +15 -4
|
@@ -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
|