jxl 1.0.0
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 +7 -0
- data/.rubocop.yml +93 -0
- data/LICENSE.txt +21 -0
- data/README.md +90 -0
- data/Rakefile +31 -0
- data/docs/adr/0001-buffer-representation.md +12 -0
- data/docs/bench_history.md +11 -0
- data/docs/spec_map.md +16 -0
- data/exe/cjxl +21 -0
- data/exe/djxl +66 -0
- data/exe/jxlinfo +31 -0
- data/lib/jxl/basic_info.rb +16 -0
- data/lib/jxl/bit/field.rb +126 -0
- data/lib/jxl/bit/reader.rb +78 -0
- data/lib/jxl/bit/writer.rb +46 -0
- data/lib/jxl/codestream/reader.rb +68 -0
- data/lib/jxl/color/gamut.rb +80 -0
- data/lib/jxl/color/icc_profile.rb +243 -0
- data/lib/jxl/color/opsin.rb +28 -0
- data/lib/jxl/color/transfer.rb +108 -0
- data/lib/jxl/color/ycbcr.rb +63 -0
- data/lib/jxl/container/box.rb +7 -0
- data/lib/jxl/container/parser.rb +134 -0
- data/lib/jxl/container/signature.rb +19 -0
- data/lib/jxl/dct.rb +81 -0
- data/lib/jxl/decoder.rb +85 -0
- data/lib/jxl/encoder.rb +180 -0
- data/lib/jxl/entropy/ans_distribution.rb +212 -0
- data/lib/jxl/entropy/decoder.rb +226 -0
- data/lib/jxl/entropy/encoder.rb +69 -0
- data/lib/jxl/entropy/hybrid_uint.rb +55 -0
- data/lib/jxl/entropy/icc_stream.rb +293 -0
- data/lib/jxl/entropy/permutation.rb +64 -0
- data/lib/jxl/entropy/prefix_code.rb +177 -0
- data/lib/jxl/errors.rb +12 -0
- data/lib/jxl/features/noise.rb +140 -0
- data/lib/jxl/features/patches.rb +147 -0
- data/lib/jxl/features/splines.rb +206 -0
- data/lib/jxl/features/spot_colour.rb +29 -0
- data/lib/jxl/features/upsampling.rb +99 -0
- data/lib/jxl/features/upsampling_weights.bin +0 -0
- data/lib/jxl/filter/epf.rb +119 -0
- data/lib/jxl/filter/gaborish.rb +36 -0
- data/lib/jxl/frame/toc.rb +31 -0
- data/lib/jxl/headers/bit_depth.rb +32 -0
- data/lib/jxl/headers/colour_encoding.rb +81 -0
- data/lib/jxl/headers/custom_transform.rb +46 -0
- data/lib/jxl/headers/frame_header.rb +251 -0
- data/lib/jxl/headers/image_metadata.rb +128 -0
- data/lib/jxl/headers/size_header.rb +29 -0
- data/lib/jxl/image.rb +63 -0
- data/lib/jxl/io/npy.rb +82 -0
- data/lib/jxl/io/pam.rb +14 -0
- data/lib/jxl/io/pfm.rb +21 -0
- data/lib/jxl/io/pgx.rb +22 -0
- data/lib/jxl/io/png.rb +132 -0
- data/lib/jxl/io/ppm.rb +63 -0
- data/lib/jxl/modular/decoder.rb +371 -0
- data/lib/jxl/modular/group_header.rb +82 -0
- data/lib/jxl/modular/ma_tree.rb +95 -0
- data/lib/jxl/modular/predictor.rb +107 -0
- data/lib/jxl/modular/stream.rb +49 -0
- data/lib/jxl/modular/transform.rb +235 -0
- data/lib/jxl/modular/weighted.rb +85 -0
- data/lib/jxl/num.rb +37 -0
- data/lib/jxl/plane.rb +35 -0
- data/lib/jxl/render/blender.rb +61 -0
- data/lib/jxl/render/orientation.rb +42 -0
- data/lib/jxl/trace.rb +36 -0
- data/lib/jxl/vardct/ac_strategy.rb +65 -0
- data/lib/jxl/vardct/afv_basis.bin +0 -0
- data/lib/jxl/vardct/block_context_map.rb +63 -0
- data/lib/jxl/vardct/chroma_from_luma.rb +27 -0
- data/lib/jxl/vardct/coeff_order.rb +35 -0
- data/lib/jxl/vardct/decoder.rb +136 -0
- data/lib/jxl/vardct/default_dequant.bin.z +0 -0
- data/lib/jxl/vardct/dequant.rb +284 -0
- data/lib/jxl/vardct/hf_global.rb +30 -0
- data/lib/jxl/vardct/lf_global.rb +43 -0
- data/lib/jxl/vardct/lf_group.rb +227 -0
- data/lib/jxl/vardct/pass_group.rb +153 -0
- data/lib/jxl/vardct/quantizer.rb +27 -0
- data/lib/jxl/vardct/reconstructor.rb +294 -0
- data/lib/jxl/version.rb +5 -0
- data/lib/jxl.rb +216 -0
- data/tools/bench.rb +20 -0
- data/tools/compare_trace.rb +42 -0
- data/tools/conformance_report.rb +23 -0
- data/tools/fuzz.rb +51 -0
- data/tools/gen_afv_basis.rb +12 -0
- data/tools/gen_quant_tables.rb +11 -0
- data/tools/gen_upsampling_weights.rb +13 -0
- data/tools/libjxl_trace.patch +30 -0
- metadata +135 -0
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JXL
|
|
4
|
+
module VarDCT
|
|
5
|
+
LFGroup = Data.define(:dc, :quant_dc, :strategies, :strategy_first, :quant_field, :sharpness,
|
|
6
|
+
:ytox_map, :ytob_map, :used_strategies, :x, :y) do
|
|
7
|
+
def self.read(reader, frame, metadata, global, group_index: 0, group_count: 1, dc_frame: nil)
|
|
8
|
+
group_dim = 128 << frame.group_size_shift
|
|
9
|
+
groups_x = Num.ceil_div(frame.blocks_width, group_dim)
|
|
10
|
+
x = (group_index % groups_x) * group_dim
|
|
11
|
+
y = (group_index / groups_x) * group_dim
|
|
12
|
+
width = [group_dim, frame.blocks_width - x].min
|
|
13
|
+
height = [group_dim, frame.blocks_height - y].min
|
|
14
|
+
dc, quant_dc = if frame.flags.anybits?(32)
|
|
15
|
+
dc_from_frame(dc_frame, frame, width, height, x, y)
|
|
16
|
+
else
|
|
17
|
+
read_dc(reader, frame, metadata, global, group_index, width, height)
|
|
18
|
+
end
|
|
19
|
+
if global.modular_group
|
|
20
|
+
Modular::Decoder.decode_grouped_dc!(reader, global.modular_group,
|
|
21
|
+
group_index:, groups_x:, stream_id: 1 + group_count + group_index)
|
|
22
|
+
end
|
|
23
|
+
stream_id = 1 + (2 * group_count) + group_index
|
|
24
|
+
metadata_result = read_metadata(reader, width, height, metadata, global.modular_state, stream_id)
|
|
25
|
+
build(dc, quant_dc, metadata_result.channels, width, height, x, y)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def self.read_dc(reader, frame, metadata, global, group_index, width, height)
|
|
29
|
+
extra_precision = reader.read(2)
|
|
30
|
+
shifts = [frame.chroma_subsampling[1], frame.chroma_subsampling[0], frame.chroma_subsampling[2]]
|
|
31
|
+
shapes = shifts.map { |hshift, vshift| [width >> hshift, height >> vshift] }
|
|
32
|
+
result = Modular::Stream.decode(reader, shapes,
|
|
33
|
+
state: global.modular_state, stream_id: 1 + group_index, metadata:)
|
|
34
|
+
dequantize_dc(result.channels, global, extra_precision, frame)
|
|
35
|
+
end
|
|
36
|
+
private_class_method :read_dc
|
|
37
|
+
|
|
38
|
+
def self.dc_from_frame(image, frame, width, height, x, y)
|
|
39
|
+
raise CorruptError, "missing progressive DC frame" unless image
|
|
40
|
+
|
|
41
|
+
channels = image.channels.first(3)
|
|
42
|
+
raise CorruptError, "invalid progressive DC frame" unless channels.length == 3
|
|
43
|
+
|
|
44
|
+
dc = frame.chroma_subsampling.map.with_index do |(hshift, vshift), channel|
|
|
45
|
+
source = channels.fetch(channel)
|
|
46
|
+
plane = Plane.new(width >> hshift, height >> vshift)
|
|
47
|
+
plane.height.times do |row|
|
|
48
|
+
source_x = x >> hshift
|
|
49
|
+
source_y = (y >> vshift) + row
|
|
50
|
+
plane.data[row * plane.width, plane.width] = source.data[(source_y * source.width) + source_x, plane.width]
|
|
51
|
+
end
|
|
52
|
+
plane
|
|
53
|
+
end
|
|
54
|
+
[dc, Plane.new(width, height)]
|
|
55
|
+
end
|
|
56
|
+
private_class_method :dc_from_frame
|
|
57
|
+
|
|
58
|
+
def self.dequantize_dc(channels, global, extra_precision, frame)
|
|
59
|
+
steps = global.quantizer.dc_steps(global.dequant).map { _1 / (1 << extra_precision) }
|
|
60
|
+
factors = global.chroma_from_luma.dc_factors
|
|
61
|
+
output = [channels[1], channels[0], channels[2]].map { Plane.new(_1.width, _1.height) }
|
|
62
|
+
unless frame.max_hshift.zero? && frame.max_vshift.zero?
|
|
63
|
+
channels[0].data.each_index { |index| output[1].data[index] = channels[0].data[index] * steps[1] }
|
|
64
|
+
channels[1].data.each_index { |index| output[0].data[index] = channels[1].data[index] * steps[0] }
|
|
65
|
+
channels[2].data.each_index { |index| output[2].data[index] = channels[2].data[index] * steps[2] }
|
|
66
|
+
return [output, dc_contexts(channels, global.block_context_map, frame)]
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
channels[0].data.length.times do |index|
|
|
70
|
+
y = channels[0].data[index] * steps[1]
|
|
71
|
+
output[1].data[index] = y
|
|
72
|
+
output[0].data[index] = (channels[1].data[index] * steps[0]) + (y * factors[0])
|
|
73
|
+
output[2].data[index] = (channels[2].data[index] * steps[2]) + (y * factors[2])
|
|
74
|
+
end
|
|
75
|
+
[output, dc_contexts(channels, global.block_context_map, frame)]
|
|
76
|
+
end
|
|
77
|
+
private_class_method :dequantize_dc
|
|
78
|
+
|
|
79
|
+
def self.dc_contexts(channels, map, frame)
|
|
80
|
+
plane = Plane.new(channels[0].width, channels[0].height)
|
|
81
|
+
return plane if map.num_dc_contexts == 1
|
|
82
|
+
|
|
83
|
+
plane.data.length.times do |index|
|
|
84
|
+
x = index % plane.width
|
|
85
|
+
y = index / plane.width
|
|
86
|
+
buckets = [channels[1], channels[0], channels[2]].each_with_index.map do |channel, c|
|
|
87
|
+
hshift, vshift = frame.chroma_subsampling[c]
|
|
88
|
+
value = channel[x >> hshift, y >> vshift]
|
|
89
|
+
map.dc_thresholds[c].count { value > _1 }
|
|
90
|
+
end
|
|
91
|
+
plane.data[index] = (((buckets[0] * (map.dc_thresholds[2].length + 1)) + buckets[2]) *
|
|
92
|
+
(map.dc_thresholds[1].length + 1)) + buckets[1]
|
|
93
|
+
end
|
|
94
|
+
plane
|
|
95
|
+
end
|
|
96
|
+
private_class_method :dc_contexts
|
|
97
|
+
|
|
98
|
+
def self.read_metadata(reader, width, height, metadata, state, stream_id)
|
|
99
|
+
count = reader.read(((width * height) - 1).bit_length) + 1
|
|
100
|
+
shapes = [[Num.ceil_div(width, 8), Num.ceil_div(height, 8)],
|
|
101
|
+
[Num.ceil_div(width, 8), Num.ceil_div(height, 8)], [count, 2], [width, height]]
|
|
102
|
+
Modular::Stream.decode(reader, shapes, state:, stream_id:, metadata:)
|
|
103
|
+
end
|
|
104
|
+
private_class_method :read_metadata
|
|
105
|
+
|
|
106
|
+
def self.build(dc, quant_dc, channels, width, height, x = 0, y = 0)
|
|
107
|
+
strategies = Array.new(width * height)
|
|
108
|
+
strategy_first = Array.new(width * height, false)
|
|
109
|
+
quant_field = Plane.new(width, height)
|
|
110
|
+
index = 0
|
|
111
|
+
height.times do |y|
|
|
112
|
+
width.times do |x|
|
|
113
|
+
next if strategies[(y * width) + x]
|
|
114
|
+
|
|
115
|
+
strategy = AcStrategy[channels[2][index, 0]]
|
|
116
|
+
place_strategy!(strategies, width, height, x, y, strategy)
|
|
117
|
+
strategy_first[(y * width) + x] = true
|
|
118
|
+
quant_field[x, y] = 1 + Num.clamp(channels[2][index, 1], 0, 255)
|
|
119
|
+
index += 1
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
raise CorruptError, "invalid AC metadata count" unless index == channels[2].width
|
|
123
|
+
raise CorruptError, "invalid EPF sharpness" unless channels[3].data.all? { _1.between?(0, 7) }
|
|
124
|
+
|
|
125
|
+
new(dc:, quant_dc:, strategies:, strategy_first:, quant_field:, sharpness: channels[3],
|
|
126
|
+
ytox_map: channels[0], ytob_map: channels[1],
|
|
127
|
+
used_strategies: strategies.compact.map(&:id).uniq.sort.freeze, x:, y:)
|
|
128
|
+
end
|
|
129
|
+
private_class_method :build
|
|
130
|
+
|
|
131
|
+
def self.merge(groups, frame)
|
|
132
|
+
return groups.first if groups.one?
|
|
133
|
+
|
|
134
|
+
width = frame.blocks_width
|
|
135
|
+
height = frame.blocks_height
|
|
136
|
+
dc = frame.chroma_subsampling.map do |hshift, vshift|
|
|
137
|
+
Plane.new(width >> hshift, height >> vshift)
|
|
138
|
+
end
|
|
139
|
+
quant_dc = Plane.new(width, height)
|
|
140
|
+
strategies = Array.new(width * height)
|
|
141
|
+
strategy_first = Array.new(width * height, false)
|
|
142
|
+
quant_field = Plane.new(width, height)
|
|
143
|
+
sharpness = Plane.new(width, height)
|
|
144
|
+
ytox_map = Plane.new(Num.ceil_div(width, 8), Num.ceil_div(height, 8))
|
|
145
|
+
ytob_map = Plane.new(ytox_map.width, ytox_map.height)
|
|
146
|
+
groups.each do |group|
|
|
147
|
+
group.dc.each_with_index do |plane, channel|
|
|
148
|
+
hshift, vshift = frame.chroma_subsampling[channel]
|
|
149
|
+
blit_plane!(plane, dc[channel], group.x >> hshift, group.y >> vshift)
|
|
150
|
+
end
|
|
151
|
+
blit_plane!(group.quant_dc, quant_dc, group.x, group.y)
|
|
152
|
+
blit_plane!(group.quant_field, quant_field, group.x, group.y)
|
|
153
|
+
blit_plane!(group.sharpness, sharpness, group.x, group.y)
|
|
154
|
+
blit_plane!(group.ytox_map, ytox_map, group.x / 8, group.y / 8)
|
|
155
|
+
blit_plane!(group.ytob_map, ytob_map, group.x / 8, group.y / 8)
|
|
156
|
+
group.strategies.each_with_index do |strategy, index|
|
|
157
|
+
local_x = index % group.quant_field.width
|
|
158
|
+
local_y = index / group.quant_field.width
|
|
159
|
+
target = ((group.y + local_y) * width) + group.x + local_x
|
|
160
|
+
strategies[target] = strategy
|
|
161
|
+
strategy_first[target] = group.strategy_first[index]
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
new(dc:, quant_dc:, strategies:, strategy_first:, quant_field:, sharpness:, ytox_map:, ytob_map:,
|
|
165
|
+
used_strategies: groups.flat_map(&:used_strategies).uniq.sort.freeze, x: 0, y: 0)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def self.smooth!(group, dc_factors)
|
|
169
|
+
width = group.dc.first.width
|
|
170
|
+
height = group.dc.first.height
|
|
171
|
+
return group if width <= 2 || height <= 2
|
|
172
|
+
|
|
173
|
+
w1 = 0.20345139757231578
|
|
174
|
+
w2 = 0.0334829185968739
|
|
175
|
+
w0 = 1.0 - (4.0 * (w1 + w2))
|
|
176
|
+
output = group.dc.map do |source|
|
|
177
|
+
Plane.new(width, height).tap { |target| target.data.replace(source.data) }
|
|
178
|
+
end
|
|
179
|
+
1.upto(height - 2) do |y|
|
|
180
|
+
1.upto(width - 2) do |x|
|
|
181
|
+
centres = group.dc.map { _1[x, y] }
|
|
182
|
+
smoothed = group.dc.map do |plane|
|
|
183
|
+
corners = plane[x - 1, y - 1] + plane[x + 1, y - 1] +
|
|
184
|
+
plane[x - 1, y + 1] + plane[x + 1, y + 1]
|
|
185
|
+
sides = plane[x - 1, y] + plane[x + 1, y] + plane[x, y - 1] + plane[x, y + 1]
|
|
186
|
+
(w2 * corners) + (w1 * sides) + (w0 * plane[x, y])
|
|
187
|
+
end
|
|
188
|
+
gap = 3.times.reduce(0.5) do |value, channel|
|
|
189
|
+
[value, ((centres[channel] - smoothed[channel]) /
|
|
190
|
+
dc_factors[channel]).abs].max
|
|
191
|
+
end
|
|
192
|
+
factor = [0.0, 3.0 - (4.0 * gap)].max
|
|
193
|
+
3.times do |channel|
|
|
194
|
+
output[channel][x, y] = centres[channel] +
|
|
195
|
+
((smoothed[channel] - centres[channel]) * factor)
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
group.dc.each_index { |channel| group.dc[channel].data.replace(output[channel].data) }
|
|
200
|
+
group
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def self.blit_plane!(source, target, x, y)
|
|
204
|
+
source.height.times do |row|
|
|
205
|
+
target.data[((y + row) * target.width) + x, source.width] = source.data[row * source.width, source.width]
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
private_class_method :blit_plane!
|
|
209
|
+
|
|
210
|
+
def self.place_strategy!(grid, width, height, x, y, strategy)
|
|
211
|
+
if x + strategy.blocks_x > width || y + strategy.blocks_y > height
|
|
212
|
+
raise CorruptError, "AC strategy exceeds group bounds"
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
strategy.blocks_y.times do |dy|
|
|
216
|
+
strategy.blocks_x.times do |dx|
|
|
217
|
+
index = ((y + dy) * width) + x + dx
|
|
218
|
+
raise CorruptError, "overlapping AC strategies" if grid[index]
|
|
219
|
+
|
|
220
|
+
grid[index] = strategy
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
private_class_method :place_strategy!
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
end
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JXL
|
|
4
|
+
module VarDCT
|
|
5
|
+
COEFFICIENT_FREQUENCY_CONTEXT = [
|
|
6
|
+
0xBAD, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
|
|
7
|
+
15, 15, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22,
|
|
8
|
+
23, 23, 23, 23, 24, 24, 24, 24, 25, 25, 25, 25, 26, 26, 26, 26,
|
|
9
|
+
27, 27, 27, 27, 28, 28, 28, 28, 29, 29, 29, 29, 30, 30, 30, 30
|
|
10
|
+
].freeze
|
|
11
|
+
COEFFICIENT_NONZERO_CONTEXT = [
|
|
12
|
+
0xBAD, 0, 31, 62, 62, 93, 93, 93, 93, 123, 123, 123, 123,
|
|
13
|
+
152, 152, 152, 152, 152, 152, 152, 152, 180, 180, 180, 180, 180,
|
|
14
|
+
180, 180, 180, 180, 180, 180, 180, 206, 206, 206, 206, 206, 206,
|
|
15
|
+
206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206,
|
|
16
|
+
206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206
|
|
17
|
+
].freeze
|
|
18
|
+
QuantizedBlock = Data.define(:x, :y, :strategy, :coefficients)
|
|
19
|
+
PassGroup = Data.define(:blocks) do
|
|
20
|
+
def self.merge(groups)
|
|
21
|
+
return groups.first if groups.one?
|
|
22
|
+
|
|
23
|
+
blocks = groups.first.blocks.each_index.map do |index|
|
|
24
|
+
pass_blocks = groups.map { _1.blocks.fetch(index) }
|
|
25
|
+
first = pass_blocks.first
|
|
26
|
+
unless pass_blocks.all? { _1.x == first.x && _1.y == first.y && _1.strategy == first.strategy }
|
|
27
|
+
raise CorruptError, "progressive AC blocks differ between passes"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
coefficients = 3.times.map do |channel|
|
|
31
|
+
first.coefficients[channel].each_index.map do |coefficient|
|
|
32
|
+
pass_blocks.sum { _1.coefficients[channel][coefficient] }
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
QuantizedBlock.new(x: first.x, y: first.y, strategy: first.strategy, coefficients:)
|
|
36
|
+
end
|
|
37
|
+
new(blocks:)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def self.read(reader, frame, lf_global, lf_group, hf_global, pass_index: 0, group_index: 0)
|
|
41
|
+
pass = hf_global.passes.fetch(pass_index)
|
|
42
|
+
selector_bits = (hf_global.histogram_count - 1).bit_length
|
|
43
|
+
selector = selector_bits.zero? ? 0 : reader.read(selector_bits)
|
|
44
|
+
raise CorruptError, "invalid AC histogram selector" if selector >= hf_global.histogram_count
|
|
45
|
+
|
|
46
|
+
context_offset = selector * lf_global.block_context_map.ac_context_count
|
|
47
|
+
decoder = pass.entropy.fork(reader, distance_multiplier: 0)
|
|
48
|
+
blocks = decode_blocks(decoder, frame, lf_global.block_context_map, lf_group,
|
|
49
|
+
pass.orders, context_offset, frame.passes.shifts.fetch(pass_index), group_index)
|
|
50
|
+
decoder.final_state!
|
|
51
|
+
if lf_global.modular_group
|
|
52
|
+
group_dim = 128 << frame.group_size_shift
|
|
53
|
+
groups_x = Num.ceil_div(frame.encoded_width, group_dim)
|
|
54
|
+
dc_groups = Num.ceil_div(frame.blocks_width, group_dim) * Num.ceil_div(frame.blocks_height, group_dim)
|
|
55
|
+
group_count = groups_x * Num.ceil_div(frame.encoded_height, group_dim)
|
|
56
|
+
stream_id = 1 + (3 * dc_groups) + 17 + (group_count * pass_index) + group_index
|
|
57
|
+
Modular::Decoder.decode_grouped_pass!(reader, lf_global.modular_group, frame.passes,
|
|
58
|
+
pass_index:, group_index:, groups_x:, stream_id:)
|
|
59
|
+
end
|
|
60
|
+
new(blocks:)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def self.decode_blocks(decoder, frame, context_map, lf_group, orders, context_offset, shift, group_index)
|
|
64
|
+
frame_width = frame.blocks_width
|
|
65
|
+
frame_height = frame.blocks_height
|
|
66
|
+
group_dim = 128 << frame.group_size_shift
|
|
67
|
+
groups_x = Num.ceil_div(frame.encoded_width, group_dim)
|
|
68
|
+
block_group_dim = group_dim / 8
|
|
69
|
+
x0 = (group_index % groups_x) * block_group_dim
|
|
70
|
+
y0 = (group_index / groups_x) * block_group_dim
|
|
71
|
+
width = [block_group_dim, frame_width - x0].min
|
|
72
|
+
height = [block_group_dim, frame_height - y0].min
|
|
73
|
+
nonzeros = frame.chroma_subsampling.map do |hshift, vshift|
|
|
74
|
+
Plane.new(width >> hshift, height >> vshift)
|
|
75
|
+
end
|
|
76
|
+
blocks = []
|
|
77
|
+
height.times do |y|
|
|
78
|
+
width.times do |x|
|
|
79
|
+
absolute_x = x0 + x
|
|
80
|
+
absolute_y = y0 + y
|
|
81
|
+
next unless lf_group.strategy_first[(absolute_y * frame_width) + absolute_x]
|
|
82
|
+
|
|
83
|
+
strategy = lf_group.strategies[(absolute_y * frame_width) + absolute_x]
|
|
84
|
+
coefficients = Array.new(3) { Array.new(strategy.coefficient_count, 0) }
|
|
85
|
+
[1, 0, 2].each do |channel|
|
|
86
|
+
hshift, vshift = frame.chroma_subsampling[channel]
|
|
87
|
+
next unless absolute_x.nobits?((1 << hshift) - 1) && absolute_y.nobits?((1 << vshift) - 1)
|
|
88
|
+
|
|
89
|
+
decode_channel!(decoder, context_map, lf_group, nonzeros[channel], coefficients[channel],
|
|
90
|
+
orders.fetch([strategy.order, channel]), strategy, channel, x >> hshift, y >> vshift,
|
|
91
|
+
absolute_x, absolute_y, context_offset, shift)
|
|
92
|
+
end
|
|
93
|
+
blocks << QuantizedBlock.new(x: absolute_x, y: absolute_y, strategy:, coefficients:)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
blocks
|
|
97
|
+
end
|
|
98
|
+
private_class_method :decode_blocks
|
|
99
|
+
|
|
100
|
+
def self.decode_channel!(decoder, map, lf_group, nonzeros, coefficients, order,
|
|
101
|
+
strategy, channel, x, y, absolute_x, absolute_y, context_offset, shift)
|
|
102
|
+
predicted = predict_nonzeros(nonzeros, x, y)
|
|
103
|
+
block_context = map.context(lf_group.quant_dc[absolute_x, absolute_y],
|
|
104
|
+
lf_group.quant_field[absolute_x, absolute_y], strategy.order, channel)
|
|
105
|
+
count = decoder.read_uint(context_offset + map.nonzero_context(predicted, block_context))
|
|
106
|
+
covered = strategy.blocks_x * strategy.blocks_y
|
|
107
|
+
raise CorruptError, "too many nonzero AC coefficients" if count > strategy.coefficient_count - covered
|
|
108
|
+
|
|
109
|
+
average = (count + covered - 1) >> strategy.log2_blocks
|
|
110
|
+
strategy.blocks_y.times do |dy|
|
|
111
|
+
strategy.blocks_x.times { |dx| nonzeros[x + dx, y + dy] = average }
|
|
112
|
+
end
|
|
113
|
+
decode_coefficients!(decoder, map, coefficients, order, covered, strategy.log2_blocks,
|
|
114
|
+
block_context, context_offset, count, shift)
|
|
115
|
+
end
|
|
116
|
+
private_class_method :decode_channel!
|
|
117
|
+
|
|
118
|
+
def self.decode_coefficients!(decoder, map, coefficients, order, covered, log2_blocks,
|
|
119
|
+
block_context, context_offset, count, shift)
|
|
120
|
+
previous = count > coefficients.length / 16 ? 0 : 1
|
|
121
|
+
k = covered
|
|
122
|
+
while k < coefficients.length && count.positive?
|
|
123
|
+
context = coefficient_context(count, k, covered, log2_blocks, previous)
|
|
124
|
+
packed = decoder.read_uint(context_offset + map.coefficient_context_offset(block_context) + context)
|
|
125
|
+
coefficients[order[k]] += Num.unpack_signed(packed) << shift
|
|
126
|
+
previous = packed.zero? ? 0 : 1
|
|
127
|
+
count -= previous
|
|
128
|
+
k += 1
|
|
129
|
+
end
|
|
130
|
+
raise CorruptError, "nonzero AC coefficients exceed scan" unless count.zero?
|
|
131
|
+
end
|
|
132
|
+
private_class_method :decode_coefficients!
|
|
133
|
+
|
|
134
|
+
def self.predict_nonzeros(plane, x, y)
|
|
135
|
+
return 32 if x.zero? && y.zero?
|
|
136
|
+
return plane[x, y - 1] if x.zero?
|
|
137
|
+
return plane[x - 1, y] if y.zero?
|
|
138
|
+
|
|
139
|
+
(plane[x, y - 1] + plane[x - 1, y] + 1) / 2
|
|
140
|
+
end
|
|
141
|
+
private_class_method :predict_nonzeros
|
|
142
|
+
|
|
143
|
+
def self.coefficient_context(nonzeros, k, covered, log2_blocks, previous)
|
|
144
|
+
nonzeros = (nonzeros + covered - 1) >> log2_blocks
|
|
145
|
+
k >>= log2_blocks
|
|
146
|
+
raise CorruptError, "invalid AC coefficient context" unless nonzeros.between?(1, 63) && k.between?(1, 63)
|
|
147
|
+
|
|
148
|
+
((COEFFICIENT_NONZERO_CONTEXT[nonzeros] + COEFFICIENT_FREQUENCY_CONTEXT[k]) * 2) + previous
|
|
149
|
+
end
|
|
150
|
+
private_class_method :coefficient_context
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JXL
|
|
4
|
+
module VarDCT
|
|
5
|
+
QUANTIZER_SCALE_DENOMINATOR = 1 << 16
|
|
6
|
+
QUANTIZER_SCALE_DISTRIBUTION = [Bit::Field.bits_offset(11, 1), Bit::Field.bits_offset(11, 2049),
|
|
7
|
+
Bit::Field.bits_offset(12, 4097), Bit::Field.bits_offset(16, 8193)].freeze
|
|
8
|
+
QUANTIZER_DC_DISTRIBUTION = [Bit::Field.val(16), Bit::Field.bits_offset(5, 1),
|
|
9
|
+
Bit::Field.bits_offset(8, 1), Bit::Field.bits_offset(16, 1)].freeze
|
|
10
|
+
|
|
11
|
+
Quantizer = Data.define(:global_scale, :quant_dc) do
|
|
12
|
+
def self.read(reader)
|
|
13
|
+
new(global_scale: Bit::Field.u32(reader, *QUANTIZER_SCALE_DISTRIBUTION),
|
|
14
|
+
quant_dc: Bit::Field.u32(reader, *QUANTIZER_DC_DISTRIBUTION))
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def scale = global_scale.fdiv(QUANTIZER_SCALE_DENOMINATOR)
|
|
18
|
+
def inverse_global_scale = QUANTIZER_SCALE_DENOMINATOR.fdiv(global_scale)
|
|
19
|
+
def inverse_dc = inverse_global_scale / quant_dc
|
|
20
|
+
def inverse_ac(quant) = inverse_global_scale / quant
|
|
21
|
+
|
|
22
|
+
def dc_steps(dequant)
|
|
23
|
+
dequant.dc.map { |value| inverse_dc * value }
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|