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.
Files changed (94) hide show
  1. checksums.yaml +7 -0
  2. data/.rubocop.yml +93 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.md +90 -0
  5. data/Rakefile +31 -0
  6. data/docs/adr/0001-buffer-representation.md +12 -0
  7. data/docs/bench_history.md +11 -0
  8. data/docs/spec_map.md +16 -0
  9. data/exe/cjxl +21 -0
  10. data/exe/djxl +66 -0
  11. data/exe/jxlinfo +31 -0
  12. data/lib/jxl/basic_info.rb +16 -0
  13. data/lib/jxl/bit/field.rb +126 -0
  14. data/lib/jxl/bit/reader.rb +78 -0
  15. data/lib/jxl/bit/writer.rb +46 -0
  16. data/lib/jxl/codestream/reader.rb +68 -0
  17. data/lib/jxl/color/gamut.rb +80 -0
  18. data/lib/jxl/color/icc_profile.rb +243 -0
  19. data/lib/jxl/color/opsin.rb +28 -0
  20. data/lib/jxl/color/transfer.rb +108 -0
  21. data/lib/jxl/color/ycbcr.rb +63 -0
  22. data/lib/jxl/container/box.rb +7 -0
  23. data/lib/jxl/container/parser.rb +134 -0
  24. data/lib/jxl/container/signature.rb +19 -0
  25. data/lib/jxl/dct.rb +81 -0
  26. data/lib/jxl/decoder.rb +85 -0
  27. data/lib/jxl/encoder.rb +180 -0
  28. data/lib/jxl/entropy/ans_distribution.rb +212 -0
  29. data/lib/jxl/entropy/decoder.rb +226 -0
  30. data/lib/jxl/entropy/encoder.rb +69 -0
  31. data/lib/jxl/entropy/hybrid_uint.rb +55 -0
  32. data/lib/jxl/entropy/icc_stream.rb +293 -0
  33. data/lib/jxl/entropy/permutation.rb +64 -0
  34. data/lib/jxl/entropy/prefix_code.rb +177 -0
  35. data/lib/jxl/errors.rb +12 -0
  36. data/lib/jxl/features/noise.rb +140 -0
  37. data/lib/jxl/features/patches.rb +147 -0
  38. data/lib/jxl/features/splines.rb +206 -0
  39. data/lib/jxl/features/spot_colour.rb +29 -0
  40. data/lib/jxl/features/upsampling.rb +99 -0
  41. data/lib/jxl/features/upsampling_weights.bin +0 -0
  42. data/lib/jxl/filter/epf.rb +119 -0
  43. data/lib/jxl/filter/gaborish.rb +36 -0
  44. data/lib/jxl/frame/toc.rb +31 -0
  45. data/lib/jxl/headers/bit_depth.rb +32 -0
  46. data/lib/jxl/headers/colour_encoding.rb +81 -0
  47. data/lib/jxl/headers/custom_transform.rb +46 -0
  48. data/lib/jxl/headers/frame_header.rb +251 -0
  49. data/lib/jxl/headers/image_metadata.rb +128 -0
  50. data/lib/jxl/headers/size_header.rb +29 -0
  51. data/lib/jxl/image.rb +63 -0
  52. data/lib/jxl/io/npy.rb +82 -0
  53. data/lib/jxl/io/pam.rb +14 -0
  54. data/lib/jxl/io/pfm.rb +21 -0
  55. data/lib/jxl/io/pgx.rb +22 -0
  56. data/lib/jxl/io/png.rb +132 -0
  57. data/lib/jxl/io/ppm.rb +63 -0
  58. data/lib/jxl/modular/decoder.rb +371 -0
  59. data/lib/jxl/modular/group_header.rb +82 -0
  60. data/lib/jxl/modular/ma_tree.rb +95 -0
  61. data/lib/jxl/modular/predictor.rb +107 -0
  62. data/lib/jxl/modular/stream.rb +49 -0
  63. data/lib/jxl/modular/transform.rb +235 -0
  64. data/lib/jxl/modular/weighted.rb +85 -0
  65. data/lib/jxl/num.rb +37 -0
  66. data/lib/jxl/plane.rb +35 -0
  67. data/lib/jxl/render/blender.rb +61 -0
  68. data/lib/jxl/render/orientation.rb +42 -0
  69. data/lib/jxl/trace.rb +36 -0
  70. data/lib/jxl/vardct/ac_strategy.rb +65 -0
  71. data/lib/jxl/vardct/afv_basis.bin +0 -0
  72. data/lib/jxl/vardct/block_context_map.rb +63 -0
  73. data/lib/jxl/vardct/chroma_from_luma.rb +27 -0
  74. data/lib/jxl/vardct/coeff_order.rb +35 -0
  75. data/lib/jxl/vardct/decoder.rb +136 -0
  76. data/lib/jxl/vardct/default_dequant.bin.z +0 -0
  77. data/lib/jxl/vardct/dequant.rb +284 -0
  78. data/lib/jxl/vardct/hf_global.rb +30 -0
  79. data/lib/jxl/vardct/lf_global.rb +43 -0
  80. data/lib/jxl/vardct/lf_group.rb +227 -0
  81. data/lib/jxl/vardct/pass_group.rb +153 -0
  82. data/lib/jxl/vardct/quantizer.rb +27 -0
  83. data/lib/jxl/vardct/reconstructor.rb +294 -0
  84. data/lib/jxl/version.rb +5 -0
  85. data/lib/jxl.rb +216 -0
  86. data/tools/bench.rb +20 -0
  87. data/tools/compare_trace.rb +42 -0
  88. data/tools/conformance_report.rb +23 -0
  89. data/tools/fuzz.rb +51 -0
  90. data/tools/gen_afv_basis.rb +12 -0
  91. data/tools/gen_quant_tables.rb +11 -0
  92. data/tools/gen_upsampling_weights.rb +13 -0
  93. data/tools/libjxl_trace.patch +30 -0
  94. 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