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,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