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,293 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JXL
|
|
4
|
+
module Entropy
|
|
5
|
+
module ICCStream # rubocop:disable Metrics/ModuleLength
|
|
6
|
+
HEADER_SIZE = 128
|
|
7
|
+
TAGS = %w[cprt wtpt bkpt rXYZ gXYZ bXYZ kXYZ rTRC gTRC bTRC kTRC chad desc chrm dmnd dmdd lumi].freeze
|
|
8
|
+
TYPES = ["XYZ ", "desc", "text", "mluc", "para", "curv", "sf32", "gbd "].freeze
|
|
9
|
+
INITIAL_HEADER = [
|
|
10
|
+
0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 109, 110, 116, 114,
|
|
11
|
+
82, 71, 66, 32, 88, 89, 90, 32, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
12
|
+
0, 0, 0, 0, 97, 99, 115, 112, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
13
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
14
|
+
0, 0, 0, 0, 0, 0, 246, 214, 0, 1, 0, 0, 0, 0, 211, 45,
|
|
15
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
16
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
17
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
|
18
|
+
].freeze
|
|
19
|
+
|
|
20
|
+
module_function
|
|
21
|
+
|
|
22
|
+
def read(reader, max_size: 64 * 1024 * 1024)
|
|
23
|
+
size = Bit::Field.u64(reader)
|
|
24
|
+
raise ResourceLimitError, "compressed ICC profile is too large" if size > max_size
|
|
25
|
+
|
|
26
|
+
decoder = Decoder.read(reader, 41)
|
|
27
|
+
bytes = []
|
|
28
|
+
size.times do |index|
|
|
29
|
+
previous = index.positive? ? bytes[index - 1] : 0
|
|
30
|
+
previous2 = index > 1 ? bytes[index - 2] : 0
|
|
31
|
+
value = decoder.read_uint(context(index, previous, previous2))
|
|
32
|
+
raise CorruptError, "ICC byte is out of range" unless value.between?(0, 255)
|
|
33
|
+
|
|
34
|
+
bytes << value
|
|
35
|
+
end
|
|
36
|
+
decoder.final_state!
|
|
37
|
+
unpredict(bytes, max_size:).pack("C*")
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def unpredict(encoded, max_size: 64 * 1024 * 1024) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
|
|
41
|
+
position = 0
|
|
42
|
+
output_size, position = varint(encoded, position)
|
|
43
|
+
command_size, position = varint(encoded, position)
|
|
44
|
+
raise ResourceLimitError, "decoded ICC profile is too large" if output_size > max_size
|
|
45
|
+
raise CorruptError, "ICC command stream is truncated" if position + command_size > encoded.length
|
|
46
|
+
|
|
47
|
+
command_position = position
|
|
48
|
+
commands_end = position + command_size
|
|
49
|
+
position = commands_end
|
|
50
|
+
output = []
|
|
51
|
+
header = initial_header(output_size)
|
|
52
|
+
HEADER_SIZE.times do |index|
|
|
53
|
+
break if output.length == output_size
|
|
54
|
+
|
|
55
|
+
predict_header!(output, header, index)
|
|
56
|
+
output << ((take(encoded, position) + header[index]) & 255)
|
|
57
|
+
position += 1
|
|
58
|
+
end
|
|
59
|
+
if output.length == output_size
|
|
60
|
+
return finish(output, output_size, position, encoded.length, command_position, commands_end)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
tag_count, command_position = varint(encoded, command_position, commands_end)
|
|
64
|
+
if tag_count.positive?
|
|
65
|
+
tag_count -= 1
|
|
66
|
+
append_u32(output, tag_count)
|
|
67
|
+
previous_start = HEADER_SIZE + (tag_count * 12)
|
|
68
|
+
previous_size = 0
|
|
69
|
+
while command_position < commands_end
|
|
70
|
+
command = encoded[command_position]
|
|
71
|
+
command_position += 1
|
|
72
|
+
tag_code = command & 63
|
|
73
|
+
break if tag_code.zero?
|
|
74
|
+
|
|
75
|
+
tag, position = decode_tag(encoded, position, tag_code)
|
|
76
|
+
output.concat(tag.bytes)
|
|
77
|
+
tag_size = xyz_tag?(tag) ? 20 : previous_size
|
|
78
|
+
if command.anybits?(64)
|
|
79
|
+
tag_start, command_position = varint(encoded, command_position, commands_end)
|
|
80
|
+
else
|
|
81
|
+
tag_start = previous_start + previous_size
|
|
82
|
+
end
|
|
83
|
+
append_u32(output, tag_start)
|
|
84
|
+
tag_size, command_position = varint(encoded, command_position, commands_end) if command.anybits?(128)
|
|
85
|
+
append_u32(output, tag_size)
|
|
86
|
+
previous_start = tag_start
|
|
87
|
+
previous_size = tag_size
|
|
88
|
+
append_companion_tags(output, tag_code, tag_start, tag_size)
|
|
89
|
+
raise CorruptError, "decoded ICC profile exceeds its size" if output.length > output_size
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
while command_position < commands_end
|
|
94
|
+
command = encoded[command_position]
|
|
95
|
+
command_position += 1
|
|
96
|
+
case command
|
|
97
|
+
when 1, 2, 3
|
|
98
|
+
count, command_position = varint(encoded, command_position, commands_end)
|
|
99
|
+
bytes = take_bytes(encoded, position, count)
|
|
100
|
+
bytes = shuffle(bytes, command) unless command == 1
|
|
101
|
+
output.concat(bytes)
|
|
102
|
+
position += count
|
|
103
|
+
when 4
|
|
104
|
+
flags = take(encoded, command_position)
|
|
105
|
+
command_position += 1
|
|
106
|
+
width = (flags & 3) + 1
|
|
107
|
+
order = (flags & 12) >> 2
|
|
108
|
+
raise CorruptError, "invalid ICC predictor" if width == 3 || order == 3
|
|
109
|
+
|
|
110
|
+
stride = width
|
|
111
|
+
stride, command_position = varint(encoded, command_position, commands_end) if flags.anybits?(16)
|
|
112
|
+
if stride < width || output.empty? || ((output.length - 1) >> 2) < stride
|
|
113
|
+
raise CorruptError, "invalid ICC predictor stride"
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
count, command_position = varint(encoded, command_position, commands_end)
|
|
117
|
+
residuals = take_bytes(encoded, position, count)
|
|
118
|
+
residuals = shuffle(residuals, width) if width > 1
|
|
119
|
+
start = output.length
|
|
120
|
+
residuals.each_with_index do |residual, index|
|
|
121
|
+
output << ((linear_prediction(output, start, index, stride, width, order) + residual) & 255)
|
|
122
|
+
end
|
|
123
|
+
position += count
|
|
124
|
+
when 10
|
|
125
|
+
output.concat("XYZ \0\0\0\0".bytes)
|
|
126
|
+
output.concat(take_bytes(encoded, position, 12))
|
|
127
|
+
position += 12
|
|
128
|
+
when 16...(16 + TYPES.length)
|
|
129
|
+
output.concat(TYPES.fetch(command - 16).bytes).push(0, 0, 0, 0)
|
|
130
|
+
else
|
|
131
|
+
raise CorruptError, "unknown ICC command"
|
|
132
|
+
end
|
|
133
|
+
raise CorruptError, "decoded ICC profile exceeds its size" if output.length > output_size
|
|
134
|
+
end
|
|
135
|
+
finish(output, output_size, position, encoded.length, command_position, commands_end)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def varint(bytes, position, limit = bytes.length)
|
|
139
|
+
value = 0
|
|
140
|
+
10.times do |index|
|
|
141
|
+
byte = take(bytes, position, limit)
|
|
142
|
+
position += 1
|
|
143
|
+
raise CorruptError, "ICC varint overflows" if index == 9 && byte.anybits?(126)
|
|
144
|
+
|
|
145
|
+
value |= (byte & 127) << (7 * index)
|
|
146
|
+
return [value, position] if byte.nobits?(128)
|
|
147
|
+
end
|
|
148
|
+
raise CorruptError, "ICC varint is too long"
|
|
149
|
+
end
|
|
150
|
+
private_class_method :varint
|
|
151
|
+
|
|
152
|
+
def take(bytes, position, limit = bytes.length)
|
|
153
|
+
raise CorruptError, "ICC stream is truncated" if position >= limit
|
|
154
|
+
|
|
155
|
+
bytes[position]
|
|
156
|
+
end
|
|
157
|
+
private_class_method :take
|
|
158
|
+
|
|
159
|
+
def take_bytes(bytes, position, count)
|
|
160
|
+
raise CorruptError, "ICC stream is truncated" if count.negative? || position + count > bytes.length
|
|
161
|
+
|
|
162
|
+
bytes.slice(position, count)
|
|
163
|
+
end
|
|
164
|
+
private_class_method :take_bytes
|
|
165
|
+
|
|
166
|
+
def finish(output, output_size, position, size, command_position, commands_end) # rubocop:disable Metrics/ParameterLists
|
|
167
|
+
unless output.length == output_size && position == size && command_position == commands_end
|
|
168
|
+
raise CorruptError, "invalid ICC stream ending"
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
output
|
|
172
|
+
end
|
|
173
|
+
private_class_method :finish
|
|
174
|
+
|
|
175
|
+
def initial_header(size)
|
|
176
|
+
INITIAL_HEADER.dup.tap { _1[0, 4] = [size].pack("N").bytes }
|
|
177
|
+
end
|
|
178
|
+
private_class_method :initial_header
|
|
179
|
+
|
|
180
|
+
def predict_header!(output, header, position) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
|
|
181
|
+
header[80, 4] = output[4, 4] if position == 8
|
|
182
|
+
if position == 41
|
|
183
|
+
header[41, 3] = "PPL".bytes if output[40] == "A".ord
|
|
184
|
+
header[41, 3] = "SFT".bytes if output[40] == "M".ord
|
|
185
|
+
elsif position == 42
|
|
186
|
+
header[42, 2] = "I ".bytes if output[40, 2] == "SG".bytes
|
|
187
|
+
header[42, 2] = "NW".bytes if output[40, 2] == "SU".bytes
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
private_class_method :predict_header!
|
|
191
|
+
|
|
192
|
+
def decode_tag(encoded, position, code)
|
|
193
|
+
return ["rTRC", position] if code == 2
|
|
194
|
+
return ["rXYZ", position] if code == 3
|
|
195
|
+
return [TAGS.fetch(code - 4), position] unless code == 1
|
|
196
|
+
|
|
197
|
+
[take_bytes(encoded, position, 4).pack("C*"), position + 4]
|
|
198
|
+
rescue IndexError
|
|
199
|
+
raise CorruptError, "unknown ICC tag"
|
|
200
|
+
end
|
|
201
|
+
private_class_method :decode_tag
|
|
202
|
+
|
|
203
|
+
def xyz_tag?(tag) = %w[rXYZ gXYZ bXYZ kXYZ wtpt bkpt lumi].include?(tag)
|
|
204
|
+
private_class_method :xyz_tag?
|
|
205
|
+
|
|
206
|
+
def append_u32(output, value)
|
|
207
|
+
raise CorruptError, "ICC value exceeds 32 bits" unless value.between?(0, 0xFFFF_FFFF)
|
|
208
|
+
|
|
209
|
+
output.concat([value].pack("N").bytes)
|
|
210
|
+
end
|
|
211
|
+
private_class_method :append_u32
|
|
212
|
+
|
|
213
|
+
def append_companion_tags(output, code, start, size)
|
|
214
|
+
if code == 2
|
|
215
|
+
%w[gTRC bTRC].each do |tag|
|
|
216
|
+
output.concat(tag.bytes)
|
|
217
|
+
append_u32(output, start)
|
|
218
|
+
append_u32(output, size)
|
|
219
|
+
end
|
|
220
|
+
elsif code == 3
|
|
221
|
+
%w[gXYZ bXYZ].each_with_index do |tag, index|
|
|
222
|
+
output.concat(tag.bytes)
|
|
223
|
+
append_u32(output, start + (size * (index + 1)))
|
|
224
|
+
append_u32(output, size)
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
private_class_method :append_companion_tags
|
|
229
|
+
|
|
230
|
+
def shuffle(data, width)
|
|
231
|
+
height = Num.ceil_div(data.length, width)
|
|
232
|
+
source = 0
|
|
233
|
+
row = 0
|
|
234
|
+
Array.new(data.length) do
|
|
235
|
+
value = data[source]
|
|
236
|
+
source += height
|
|
237
|
+
source = (row += 1) if source >= data.length
|
|
238
|
+
value
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
private_class_method :shuffle
|
|
242
|
+
|
|
243
|
+
def linear_prediction(data, start, index, stride, width, order) # rubocop:disable Metrics/AbcSize,Metrics/ParameterLists
|
|
244
|
+
position = start + index
|
|
245
|
+
values = 3.times.map do |offset|
|
|
246
|
+
base = if width == 1
|
|
247
|
+
position - (stride * (offset + 1))
|
|
248
|
+
else
|
|
249
|
+
start + (index - (index % width)) - (stride * (offset + 1))
|
|
250
|
+
end
|
|
251
|
+
data.slice(base, width).inject(0) { |value, byte| (value << 8) | byte }
|
|
252
|
+
end
|
|
253
|
+
predicted = case order
|
|
254
|
+
when 0 then values[0]
|
|
255
|
+
when 1 then (2 * values[0]) - values[1]
|
|
256
|
+
when 2 then (3 * values[0]) - (3 * values[1]) + values[2]
|
|
257
|
+
end
|
|
258
|
+
shift = width - 1 - (index % width)
|
|
259
|
+
(predicted >> (shift * 8)) & 255
|
|
260
|
+
end
|
|
261
|
+
private_class_method :linear_prediction
|
|
262
|
+
|
|
263
|
+
def context(index, previous, previous2)
|
|
264
|
+
return 0 if index <= 128
|
|
265
|
+
|
|
266
|
+
1 + byte_kind1(previous) + (byte_kind2(previous2) * 8)
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
def byte_kind1(byte) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
|
|
270
|
+
return 0 if byte.between?("a".ord, "z".ord) || byte.between?("A".ord, "Z".ord)
|
|
271
|
+
return 1 if byte.between?("0".ord, "9".ord) || [".".ord, ",".ord].include?(byte)
|
|
272
|
+
return 2 if byte.zero?
|
|
273
|
+
return 3 if byte == 1
|
|
274
|
+
return 4 if byte < 16
|
|
275
|
+
return 6 if byte == 255
|
|
276
|
+
return 5 if byte > 240
|
|
277
|
+
|
|
278
|
+
7
|
|
279
|
+
end
|
|
280
|
+
private_class_method :byte_kind1
|
|
281
|
+
|
|
282
|
+
def byte_kind2(byte)
|
|
283
|
+
return 0 if byte.between?("a".ord, "z".ord) || byte.between?("A".ord, "Z".ord)
|
|
284
|
+
return 1 if byte.between?("0".ord, "9".ord) || [".".ord, ",".ord].include?(byte)
|
|
285
|
+
return 2 if byte < 16
|
|
286
|
+
return 3 if byte > 240
|
|
287
|
+
|
|
288
|
+
4
|
|
289
|
+
end
|
|
290
|
+
private_class_method :byte_kind2
|
|
291
|
+
end
|
|
292
|
+
end
|
|
293
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JXL
|
|
4
|
+
module Entropy
|
|
5
|
+
module Permutation
|
|
6
|
+
module_function
|
|
7
|
+
|
|
8
|
+
def read(reader, size, skip: 0)
|
|
9
|
+
raise ResourceLimitError, "permutation is too large" unless size.between?(1, 65_536)
|
|
10
|
+
|
|
11
|
+
decoder = Decoder.read(reader, 8)
|
|
12
|
+
permutation = read_with_decoder(decoder, size, skip:)
|
|
13
|
+
decoder.final_state!
|
|
14
|
+
permutation
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def read_with_decoder(decoder, size, skip: 0)
|
|
18
|
+
end_position = decoder.read_uint(context(size)) + skip
|
|
19
|
+
raise CorruptError, "invalid permutation size" if end_position > size
|
|
20
|
+
|
|
21
|
+
code = Array.new(size, 0)
|
|
22
|
+
previous = 0
|
|
23
|
+
skip.upto(end_position - 1) do |index|
|
|
24
|
+
code[index] = decoder.read_uint(context(previous))
|
|
25
|
+
previous = code[index]
|
|
26
|
+
raise CorruptError, "invalid Lehmer code" if code[index] >= size - index
|
|
27
|
+
end
|
|
28
|
+
decode_lehmer(code)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def context(value)
|
|
32
|
+
[value.bit_length, 7].min
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def decode_lehmer(code) # rubocop:disable Metrics/AbcSize
|
|
36
|
+
size = code.length
|
|
37
|
+
log_size = Num.ceil_log2(size)
|
|
38
|
+
padded_size = 1 << log_size
|
|
39
|
+
tree = Array.new(padded_size) { |index| (index + 1) & -(index + 1) }
|
|
40
|
+
code.each_with_index.map do |rank0, index|
|
|
41
|
+
raise CorruptError, "invalid Lehmer code" if rank0 + index >= size
|
|
42
|
+
|
|
43
|
+
rank = rank0 + 1
|
|
44
|
+
bit = padded_size
|
|
45
|
+
position = 0
|
|
46
|
+
(log_size + 1).times do
|
|
47
|
+
candidate = position + bit
|
|
48
|
+
bit >>= 1
|
|
49
|
+
next unless tree[candidate - 1] < rank
|
|
50
|
+
|
|
51
|
+
position = candidate
|
|
52
|
+
rank -= tree[candidate - 1]
|
|
53
|
+
end
|
|
54
|
+
update = position + 1
|
|
55
|
+
while update <= padded_size
|
|
56
|
+
tree[update - 1] -= 1
|
|
57
|
+
update += update & -update
|
|
58
|
+
end
|
|
59
|
+
position
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JXL
|
|
4
|
+
module Entropy
|
|
5
|
+
class PrefixCode
|
|
6
|
+
MAX_BITS = 15
|
|
7
|
+
CODE_LENGTH_ORDER = [1, 2, 3, 4, 0, 5, 17, 6, 16, 7, 8, 9, 10, 11, 12, 13, 14, 15].freeze
|
|
8
|
+
CODE_LENGTH_TABLE = [[2, 0], [2, 4], [2, 3], [3, 2],
|
|
9
|
+
[2, 0], [2, 4], [2, 3], [4, 1],
|
|
10
|
+
[2, 0], [2, 4], [2, 3], [3, 2],
|
|
11
|
+
[2, 0], [2, 4], [2, 3], [4, 5]].freeze
|
|
12
|
+
|
|
13
|
+
def initialize(codes, constant = nil)
|
|
14
|
+
@codes = codes
|
|
15
|
+
@constant = constant
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def self.constant(symbol) = new({}, symbol)
|
|
19
|
+
|
|
20
|
+
def read(reader)
|
|
21
|
+
return @constant unless @constant.nil?
|
|
22
|
+
|
|
23
|
+
key = 0
|
|
24
|
+
1.upto(MAX_BITS) do |length|
|
|
25
|
+
key |= reader.read(1) << (length - 1)
|
|
26
|
+
symbol = @codes[length]&.[](key)
|
|
27
|
+
return symbol unless symbol.nil?
|
|
28
|
+
end
|
|
29
|
+
raise CorruptError, "invalid prefix code"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def self.read(reader, alphabet_size)
|
|
33
|
+
raise CorruptError, "invalid prefix alphabet" unless alphabet_size.between?(1, 1 << MAX_BITS)
|
|
34
|
+
|
|
35
|
+
simple_or_skip = reader.read(2)
|
|
36
|
+
return read_simple(reader, alphabet_size) if simple_or_skip == 1
|
|
37
|
+
|
|
38
|
+
lengths = read_complex_lengths(reader, alphabet_size, simple_or_skip)
|
|
39
|
+
from_code_lengths(lengths)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def self.from_code_lengths(lengths) # rubocop:disable Metrics/AbcSize
|
|
43
|
+
symbols = lengths.each_index.select { |symbol| lengths[symbol].positive? }
|
|
44
|
+
raise CorruptError, "empty prefix code" if symbols.empty?
|
|
45
|
+
return new({}, symbols.first) if symbols.one?
|
|
46
|
+
|
|
47
|
+
counts = lengths.tally
|
|
48
|
+
space = 1 << MAX_BITS
|
|
49
|
+
1.upto(MAX_BITS) { |length| space -= counts.fetch(length, 0) << (MAX_BITS - length) }
|
|
50
|
+
raise CorruptError, "incomplete prefix code" unless space.zero?
|
|
51
|
+
|
|
52
|
+
codes = Array.new(MAX_BITS + 1) { {} }
|
|
53
|
+
key = 0
|
|
54
|
+
1.upto(MAX_BITS) do |length|
|
|
55
|
+
symbols.select { |symbol| lengths[symbol] == length }.each do |symbol|
|
|
56
|
+
codes[length][key] = symbol
|
|
57
|
+
key = next_key(key, length)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
new(codes)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def self.read_simple(reader, alphabet_size)
|
|
64
|
+
bit_count = (alphabet_size - 1).bit_length
|
|
65
|
+
count = reader.read(2) + 1
|
|
66
|
+
symbols = Array.new(count) { reader.read(bit_count) }
|
|
67
|
+
raise CorruptError, "invalid simple prefix symbol" if symbols.any? { |symbol| symbol >= alphabet_size }
|
|
68
|
+
raise CorruptError, "duplicate simple prefix symbol" unless symbols.uniq.length == symbols.length
|
|
69
|
+
|
|
70
|
+
shape = count == 4 && !reader.read(1).zero?
|
|
71
|
+
simple_codes(symbols, shape)
|
|
72
|
+
end
|
|
73
|
+
private_class_method :read_simple
|
|
74
|
+
|
|
75
|
+
def self.simple_codes(symbols, alternate_shape)
|
|
76
|
+
case symbols.length
|
|
77
|
+
when 1 then new({}, symbols[0])
|
|
78
|
+
when 2 then from_code_lengths_for(symbols.sort, [1, 1])
|
|
79
|
+
when 3 then new([{}, { 0 => symbols[0] }, { 1 => [symbols[1], symbols[2]].min,
|
|
80
|
+
3 => [symbols[1], symbols[2]].max }])
|
|
81
|
+
when 4
|
|
82
|
+
alternate_shape ? simple_four_alternate(symbols) : from_code_lengths_for(symbols.sort, [2, 2, 2, 2])
|
|
83
|
+
else
|
|
84
|
+
raise InternalError, "invalid simple prefix code"
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
private_class_method :simple_codes
|
|
88
|
+
|
|
89
|
+
def self.simple_four_alternate(symbols)
|
|
90
|
+
tail = symbols.drop(2).sort
|
|
91
|
+
new([{}, { 0 => symbols[0] }, { 1 => symbols[1] }, { 3 => tail[0], 7 => tail[1] }])
|
|
92
|
+
end
|
|
93
|
+
private_class_method :simple_four_alternate
|
|
94
|
+
|
|
95
|
+
def self.from_code_lengths_for(symbols, lengths)
|
|
96
|
+
full = Array.new(symbols.max + 1, 0)
|
|
97
|
+
symbols.zip(lengths) { |symbol, length| full[symbol] = length }
|
|
98
|
+
from_code_lengths(full)
|
|
99
|
+
end
|
|
100
|
+
private_class_method :from_code_lengths_for
|
|
101
|
+
|
|
102
|
+
def self.read_complex_lengths(reader, alphabet_size, skip)
|
|
103
|
+
code_length_lengths = Array.new(18, 0)
|
|
104
|
+
space = 32
|
|
105
|
+
nonzero = 0
|
|
106
|
+
skip.upto(17) do |index|
|
|
107
|
+
break unless space.positive?
|
|
108
|
+
|
|
109
|
+
bits, value = CODE_LENGTH_TABLE.fetch(reader.peek(4))
|
|
110
|
+
reader.skip(bits)
|
|
111
|
+
code_length_lengths[CODE_LENGTH_ORDER[index]] = value
|
|
112
|
+
next if value.zero?
|
|
113
|
+
|
|
114
|
+
space -= 32 >> value
|
|
115
|
+
nonzero += 1
|
|
116
|
+
end
|
|
117
|
+
raise CorruptError, "invalid prefix code-length code" unless nonzero == 1 || space.zero?
|
|
118
|
+
|
|
119
|
+
read_symbol_lengths(reader, alphabet_size, from_code_lengths(code_length_lengths))
|
|
120
|
+
end
|
|
121
|
+
private_class_method :read_complex_lengths
|
|
122
|
+
|
|
123
|
+
def self.read_symbol_lengths(reader, alphabet_size, code)
|
|
124
|
+
lengths = Array.new(alphabet_size, 0)
|
|
125
|
+
symbol = 0
|
|
126
|
+
previous = 8
|
|
127
|
+
repeat = 0
|
|
128
|
+
repeated_length = 0
|
|
129
|
+
space = 1 << MAX_BITS
|
|
130
|
+
while symbol < alphabet_size && space.positive?
|
|
131
|
+
length = code.read(reader)
|
|
132
|
+
if length < 16
|
|
133
|
+
repeat = 0
|
|
134
|
+
lengths[symbol] = length
|
|
135
|
+
symbol += 1
|
|
136
|
+
unless length.zero?
|
|
137
|
+
previous = length
|
|
138
|
+
space -= 1 << (MAX_BITS - length)
|
|
139
|
+
end
|
|
140
|
+
next
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
repeated_length, repeat, symbol, space = repeat_lengths(
|
|
144
|
+
reader, lengths, symbol, length, previous, repeated_length, repeat, space
|
|
145
|
+
)
|
|
146
|
+
end
|
|
147
|
+
raise CorruptError, "incomplete prefix code lengths" unless space.zero?
|
|
148
|
+
|
|
149
|
+
lengths
|
|
150
|
+
end
|
|
151
|
+
private_class_method :read_symbol_lengths
|
|
152
|
+
|
|
153
|
+
def self.repeat_lengths(reader, lengths, symbol, code, previous, old_length, repeat, space) # rubocop:disable Metrics/ParameterLists
|
|
154
|
+
extra_bits = code - 14
|
|
155
|
+
new_length = code == 16 ? previous : 0
|
|
156
|
+
repeat = 0 if old_length != new_length
|
|
157
|
+
old_repeat = repeat
|
|
158
|
+
repeat = ((repeat - 2) << extra_bits) if repeat.positive?
|
|
159
|
+
repeat += reader.read(extra_bits) + 3
|
|
160
|
+
count = repeat - old_repeat
|
|
161
|
+
raise CorruptError, "prefix repeat exceeds alphabet" if symbol + count > lengths.length
|
|
162
|
+
|
|
163
|
+
lengths.fill(new_length, symbol, count)
|
|
164
|
+
space -= count << (MAX_BITS - new_length) unless new_length.zero?
|
|
165
|
+
[new_length, repeat, symbol + count, space]
|
|
166
|
+
end
|
|
167
|
+
private_class_method :repeat_lengths
|
|
168
|
+
|
|
169
|
+
def self.next_key(key, length)
|
|
170
|
+
step = 1 << (length - 1)
|
|
171
|
+
step >>= 1 while key.anybits?(step)
|
|
172
|
+
(key & (step - 1)) + step
|
|
173
|
+
end
|
|
174
|
+
private_class_method :next_key
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|
data/lib/jxl/errors.rb
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JXL
|
|
4
|
+
class Error < StandardError; end
|
|
5
|
+
class FormatError < Error; end
|
|
6
|
+
class SignatureError < FormatError; end
|
|
7
|
+
class TruncatedError < FormatError; end
|
|
8
|
+
class CorruptError < FormatError; end
|
|
9
|
+
class UnsupportedFeatureError < Error; end
|
|
10
|
+
class ResourceLimitError < Error; end
|
|
11
|
+
class InternalError < Error; end
|
|
12
|
+
end
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JXL
|
|
4
|
+
module Features
|
|
5
|
+
module Noise
|
|
6
|
+
MASK64 = (1 << 64) - 1
|
|
7
|
+
GROUP_SIZE = 256
|
|
8
|
+
|
|
9
|
+
class Random
|
|
10
|
+
def initialize(a, b, c, d)
|
|
11
|
+
@s0 = Array.new(8)
|
|
12
|
+
@s1 = Array.new(8)
|
|
13
|
+
@s0[0] = splitmix(((a << 32) + b + 0x9E3779B97F4A7C15) & MASK64)
|
|
14
|
+
@s1[0] = splitmix(((c << 32) + d + 0x9E3779B97F4A7C15) & MASK64)
|
|
15
|
+
1.upto(7) do |index|
|
|
16
|
+
@s0[index] = splitmix(@s0[index - 1])
|
|
17
|
+
@s1[index] = splitmix(@s1[index - 1])
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def fill
|
|
22
|
+
Array.new(8) do |index|
|
|
23
|
+
first = @s0[index]
|
|
24
|
+
second = @s1[index]
|
|
25
|
+
bits = (first + second) & MASK64
|
|
26
|
+
@s0[index] = second
|
|
27
|
+
first ^= (first << 23) & MASK64
|
|
28
|
+
@s1[index] = (first ^ second ^ (first >> 18) ^ (second >> 5)) & MASK64
|
|
29
|
+
bits
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def splitmix(value)
|
|
36
|
+
value = ((value ^ (value >> 30)) * 0xBF58476D1CE4E5B9) & MASK64
|
|
37
|
+
value = ((value ^ (value >> 27)) * 0x94D049BB133111EB) & MASK64
|
|
38
|
+
(value ^ (value >> 31)) & MASK64
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
module_function
|
|
43
|
+
|
|
44
|
+
def apply(channels, lut, correlation)
|
|
45
|
+
return channels unless lut&.any? { _1.abs > 1e-3 }
|
|
46
|
+
|
|
47
|
+
width = channels[0].width
|
|
48
|
+
height = channels[0].height
|
|
49
|
+
random = Array.new(3) { Plane.new(width, height) }
|
|
50
|
+
Num.ceil_div(height, GROUP_SIZE).times do |gy|
|
|
51
|
+
Num.ceil_div(width, GROUP_SIZE).times do |gx|
|
|
52
|
+
fill_group!(random, gx * GROUP_SIZE, gy * GROUP_SIZE)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
filtered = random.map { convolve(_1) }
|
|
56
|
+
add!(channels, filtered, lut, correlation)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def fill_group!(planes, x0, y0)
|
|
60
|
+
width = [GROUP_SIZE, planes[0].width - x0].min
|
|
61
|
+
height = [GROUP_SIZE, planes[0].height - y0].min
|
|
62
|
+
rng = Random.new(1, 0, x0, y0)
|
|
63
|
+
planes.each do |plane|
|
|
64
|
+
height.times do |y|
|
|
65
|
+
values = random_row(rng, width)
|
|
66
|
+
plane.data[((y0 + y) * plane.width) + x0, width] = values
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
private_class_method :fill_group!
|
|
71
|
+
|
|
72
|
+
def random_row(rng, width)
|
|
73
|
+
values = []
|
|
74
|
+
values.concat(random_floats(rng.fill)) while values.length + 16 < width
|
|
75
|
+
values.concat(random_floats(rng.fill))
|
|
76
|
+
values.first(width)
|
|
77
|
+
end
|
|
78
|
+
private_class_method :random_row
|
|
79
|
+
|
|
80
|
+
def random_floats(words)
|
|
81
|
+
words.pack("Q<*").unpack("L<*").map { |bits| [0x3F80_0000 | (bits >> 9)].pack("L<").unpack1("e") }
|
|
82
|
+
end
|
|
83
|
+
private_class_method :random_floats
|
|
84
|
+
|
|
85
|
+
def convolve(source)
|
|
86
|
+
output = Plane.new(source.width, source.height)
|
|
87
|
+
source.height.times do |y|
|
|
88
|
+
source.width.times do |x|
|
|
89
|
+
others = 5.times.sum do |dy|
|
|
90
|
+
5.times.sum do |dx|
|
|
91
|
+
next 0.0 if dx == 2 && dy == 2
|
|
92
|
+
|
|
93
|
+
sample(source, x + dx - 2, y + dy - 2)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
output[x, y] = (0.16 * others) - (3.84 * source[x, y])
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
output
|
|
100
|
+
end
|
|
101
|
+
private_class_method :convolve
|
|
102
|
+
|
|
103
|
+
def add!(channels, random, lut, correlation)
|
|
104
|
+
ytox = correlation.x_ratio(0)
|
|
105
|
+
ytob = correlation.b_ratio(0)
|
|
106
|
+
channels[0].data.length.times do |index|
|
|
107
|
+
x = channels[0].data[index]
|
|
108
|
+
y = channels[1].data[index]
|
|
109
|
+
red = strength(lut, (y + x) * 0.5) * 0.22 *
|
|
110
|
+
((random[2].data[index] * 127 / 128.0) + (random[0].data[index] / 128.0))
|
|
111
|
+
green = strength(lut, (y - x) * 0.5) * 0.22 *
|
|
112
|
+
((random[2].data[index] * 127 / 128.0) + (random[1].data[index] / 128.0))
|
|
113
|
+
combined = red + green
|
|
114
|
+
channels[0].data[index] += red - green + (ytox * combined)
|
|
115
|
+
channels[1].data[index] += combined
|
|
116
|
+
channels[2].data[index] += ytob * combined
|
|
117
|
+
end
|
|
118
|
+
channels
|
|
119
|
+
end
|
|
120
|
+
private_class_method :add!
|
|
121
|
+
|
|
122
|
+
def strength(lut, value)
|
|
123
|
+
scaled = [value * 6, 0.0].max
|
|
124
|
+
index = scaled >= 7 ? 6 : scaled.floor
|
|
125
|
+
fraction = scaled >= 7 ? 1.0 : scaled - index
|
|
126
|
+
Num.clamp(lut[index] + ((lut[index + 1] - lut[index]) * fraction), 0.0, 1.0)
|
|
127
|
+
end
|
|
128
|
+
private_class_method :strength
|
|
129
|
+
|
|
130
|
+
def sample(plane, x, y) = plane[mirror(x, plane.width), mirror(y, plane.height)]
|
|
131
|
+
private_class_method :sample
|
|
132
|
+
|
|
133
|
+
def mirror(value, size)
|
|
134
|
+
value %= 2 * size
|
|
135
|
+
value < size ? value : (2 * size) - value - 1
|
|
136
|
+
end
|
|
137
|
+
private_class_method :mirror
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|