/src/serenity/Userland/Libraries/LibGfx/ImageFormats/JPEGXLLoader.cpp
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) 2023, Lucas Chollet <lucas.chollet@serenityos.org> |
3 | | * |
4 | | * SPDX-License-Identifier: BSD-2-Clause |
5 | | */ |
6 | | |
7 | | #include <AK/BitStream.h> |
8 | | #include <AK/ConstrainedStream.h> |
9 | | #include <AK/Debug.h> |
10 | | #include <AK/Endian.h> |
11 | | #include <AK/Enumerate.h> |
12 | | #include <AK/FixedArray.h> |
13 | | #include <AK/String.h> |
14 | | #include <LibCompress/Brotli.h> |
15 | | #include <LibGfx/ImageFormats/ExifOrientedBitmap.h> |
16 | | #include <LibGfx/ImageFormats/ISOBMFF/JPEGXLBoxes.h> |
17 | | #include <LibGfx/ImageFormats/ISOBMFF/Reader.h> |
18 | | #include <LibGfx/ImageFormats/JPEGXL/Channel.h> |
19 | | #include <LibGfx/ImageFormats/JPEGXL/Common.h> |
20 | | #include <LibGfx/ImageFormats/JPEGXL/DCTNaturalOrder.h> |
21 | | #include <LibGfx/ImageFormats/JPEGXL/EntropyDecoder.h> |
22 | | #include <LibGfx/ImageFormats/JPEGXL/ModularTransforms.h> |
23 | | #include <LibGfx/ImageFormats/JPEGXL/SelfCorrectingPredictor.h> |
24 | | #include <LibGfx/ImageFormats/JPEGXLLoader.h> |
25 | | #include <LibGfx/Matrix3x3.h> |
26 | | |
27 | | namespace Gfx::JPEGXL { |
28 | | |
29 | | // This is not specified |
30 | | static ErrorOr<void> read_non_aligned(LittleEndianInputBitStream& stream, Bytes bytes) |
31 | 0 | { |
32 | 0 | for (u8& byte : bytes) |
33 | 0 | byte = TRY(stream.read_bits(8)); |
34 | 0 | return {}; |
35 | 0 | } |
36 | | |
37 | | static ErrorOr<String> read_string(LittleEndianInputBitStream& stream) |
38 | 0 | { |
39 | 0 | auto const name_length = U32(0, TRY(stream.read_bits(4)), 16 + TRY(stream.read_bits(5)), 48 + TRY(stream.read_bits(10))); |
40 | 0 | auto string_buffer = TRY(FixedArray<u8>::create(name_length)); |
41 | 0 | TRY(read_non_aligned(stream, string_buffer)); |
42 | 0 | return String::from_utf8(StringView { string_buffer }); |
43 | 0 | } |
44 | | |
45 | | /// D.2 - Image dimensions |
46 | | struct SizeHeader { |
47 | | u32 height {}; |
48 | | u32 width {}; |
49 | | }; |
50 | | |
51 | | static u32 aspect_ratio(u32 height, u32 ratio) |
52 | 0 | { |
53 | 0 | if (ratio == 1) |
54 | 0 | return height; |
55 | 0 | if (ratio == 2) |
56 | 0 | return height * 12 / 10; |
57 | 0 | if (ratio == 3) |
58 | 0 | return height * 4 / 3; |
59 | 0 | if (ratio == 4) |
60 | 0 | return height * 3 / 2; |
61 | 0 | if (ratio == 5) |
62 | 0 | return height * 16 / 9; |
63 | 0 | if (ratio == 6) |
64 | 0 | return height * 5 / 4; |
65 | 0 | if (ratio == 7) |
66 | 0 | return height * 2 / 1; |
67 | 0 | VERIFY_NOT_REACHED(); |
68 | 0 | } |
69 | | |
70 | | static ErrorOr<SizeHeader> read_size_header(LittleEndianInputBitStream& stream) |
71 | 0 | { |
72 | 0 | SizeHeader size {}; |
73 | 0 | auto const div8 = TRY(stream.read_bit()); |
74 | |
|
75 | 0 | if (div8) { |
76 | 0 | auto const h_div8 = 1 + TRY(stream.read_bits(5)); |
77 | 0 | size.height = 8 * h_div8; |
78 | 0 | } else { |
79 | 0 | size.height = U32( |
80 | 0 | 1 + TRY(stream.read_bits(9)), |
81 | 0 | 1 + TRY(stream.read_bits(13)), |
82 | 0 | 1 + TRY(stream.read_bits(18)), |
83 | 0 | 1 + TRY(stream.read_bits(30))); |
84 | 0 | } |
85 | |
|
86 | 0 | auto const ratio = TRY(stream.read_bits(3)); |
87 | |
|
88 | 0 | if (ratio == 0) { |
89 | 0 | if (div8) { |
90 | 0 | auto const w_div8 = 1 + TRY(stream.read_bits(5)); |
91 | 0 | size.width = 8 * w_div8; |
92 | 0 | } else { |
93 | 0 | size.width = U32( |
94 | 0 | 1 + TRY(stream.read_bits(9)), |
95 | 0 | 1 + TRY(stream.read_bits(13)), |
96 | 0 | 1 + TRY(stream.read_bits(18)), |
97 | 0 | 1 + TRY(stream.read_bits(30))); |
98 | 0 | } |
99 | 0 | } else { |
100 | 0 | size.width = aspect_ratio(size.height, ratio); |
101 | 0 | } |
102 | |
|
103 | 0 | return size; |
104 | 0 | } |
105 | | /// |
106 | | |
107 | | /// D.3.5 - BitDepth |
108 | | struct BitDepth { |
109 | | u32 bits_per_sample { 8 }; |
110 | | u8 exp_bits {}; |
111 | | }; |
112 | | |
113 | | static ErrorOr<BitDepth> read_bit_depth(LittleEndianInputBitStream& stream) |
114 | 0 | { |
115 | 0 | BitDepth bit_depth; |
116 | 0 | bool const float_sample = TRY(stream.read_bit()); |
117 | |
|
118 | 0 | if (float_sample) { |
119 | 0 | bit_depth.bits_per_sample = U32(32, 16, 24, 1 + TRY(stream.read_bits(6))); |
120 | 0 | bit_depth.exp_bits = 1 + TRY(stream.read_bits(4)); |
121 | 0 | } else { |
122 | 0 | bit_depth.bits_per_sample = U32(8, 10, 12, 1 + TRY(stream.read_bits(6))); |
123 | 0 | } |
124 | |
|
125 | 0 | return bit_depth; |
126 | 0 | } |
127 | | /// |
128 | | |
129 | | /// E.2 - ColourEncoding |
130 | | struct ColourEncoding { |
131 | | enum class ColourSpace { |
132 | | kRGB = 0, |
133 | | kGrey = 1, |
134 | | kXYB = 2, |
135 | | kUnknown = 3, |
136 | | }; |
137 | | |
138 | | enum class WhitePoint { |
139 | | kD65 = 1, |
140 | | kCustom = 2, |
141 | | kE = 10, |
142 | | kDCI = 11, |
143 | | }; |
144 | | |
145 | | enum class Primaries { |
146 | | kSRGB = 1, |
147 | | kCustom = 2, |
148 | | k2100 = 3, |
149 | | kP3 = 11, |
150 | | }; |
151 | | |
152 | | enum class RenderingIntent { |
153 | | kPerceptual = 0, |
154 | | kRelative = 1, |
155 | | kSaturation = 2, |
156 | | kAbsolute = 3, |
157 | | }; |
158 | | |
159 | | struct Customxy { |
160 | | u32 ux {}; |
161 | | u32 uy {}; |
162 | | }; |
163 | | |
164 | | enum class TransferFunction { |
165 | | k709 = 1, |
166 | | kUnknown = 2, |
167 | | kLinear = 8, |
168 | | kSRGB = 13, |
169 | | kPQ = 16, |
170 | | kDCI = 17, |
171 | | kHLG = 18, |
172 | | }; |
173 | | |
174 | | struct CustomTransferFunction { |
175 | | bool have_gamma { false }; |
176 | | u32 gamma {}; |
177 | | TransferFunction transfer_function { TransferFunction::kSRGB }; |
178 | | }; |
179 | | |
180 | | bool want_icc = false; |
181 | | ColourSpace colour_space { ColourSpace::kRGB }; |
182 | | WhitePoint white_point { WhitePoint::kD65 }; |
183 | | Primaries primaries { Primaries::kSRGB }; |
184 | | |
185 | | Customxy white {}; |
186 | | Customxy red {}; |
187 | | Customxy green {}; |
188 | | Customxy blue {}; |
189 | | |
190 | | CustomTransferFunction tf {}; |
191 | | |
192 | | RenderingIntent rendering_intent { RenderingIntent::kRelative }; |
193 | | }; |
194 | | |
195 | | [[maybe_unused]] static ErrorOr<ColourEncoding::Customxy> read_custom_xy(LittleEndianInputBitStream& stream) |
196 | 0 | { |
197 | 0 | ColourEncoding::Customxy custom_xy; |
198 | |
|
199 | 0 | auto const read_custom = [&stream]() -> ErrorOr<u32> { |
200 | 0 | return U32( |
201 | 0 | TRY(stream.read_bits(19)), |
202 | 0 | 524288 + TRY(stream.read_bits(19)), |
203 | 0 | 1048576 + TRY(stream.read_bits(20)), |
204 | 0 | 2097152 + TRY(stream.read_bits(21))); |
205 | 0 | }; |
206 | |
|
207 | 0 | custom_xy.ux = TRY(read_custom()); |
208 | 0 | custom_xy.uy = TRY(read_custom()); |
209 | |
|
210 | 0 | return custom_xy; |
211 | 0 | } |
212 | | |
213 | | static ErrorOr<ColourEncoding::CustomTransferFunction> read_custom_transfer_function(LittleEndianInputBitStream& stream) |
214 | 0 | { |
215 | 0 | ColourEncoding::CustomTransferFunction custom_transfer_function; |
216 | |
|
217 | 0 | custom_transfer_function.have_gamma = TRY(stream.read_bit()); |
218 | |
|
219 | 0 | if (custom_transfer_function.have_gamma) |
220 | 0 | custom_transfer_function.gamma = TRY(stream.read_bits(24)); |
221 | 0 | else |
222 | 0 | custom_transfer_function.transfer_function = TRY(read_enum<ColourEncoding::TransferFunction>(stream)); |
223 | |
|
224 | 0 | return custom_transfer_function; |
225 | 0 | } |
226 | | |
227 | | static ErrorOr<ColourEncoding> read_colour_encoding(LittleEndianInputBitStream& stream) |
228 | 0 | { |
229 | 0 | ColourEncoding colour_encoding; |
230 | 0 | bool const all_default = TRY(stream.read_bit()); |
231 | |
|
232 | 0 | if (!all_default) { |
233 | 0 | colour_encoding.want_icc = TRY(stream.read_bit()); |
234 | 0 | colour_encoding.colour_space = TRY(read_enum<ColourEncoding::ColourSpace>(stream)); |
235 | |
|
236 | 0 | auto const use_desc = !all_default && !colour_encoding.want_icc; |
237 | 0 | auto const not_xyb = colour_encoding.colour_space != ColourEncoding::ColourSpace::kXYB; |
238 | |
|
239 | 0 | if (use_desc && not_xyb) |
240 | 0 | colour_encoding.white_point = TRY(read_enum<ColourEncoding::WhitePoint>(stream)); |
241 | |
|
242 | 0 | if (colour_encoding.white_point == ColourEncoding::WhitePoint::kCustom) |
243 | 0 | colour_encoding.white = TRY(read_custom_xy(stream)); |
244 | |
|
245 | 0 | auto const has_primaries = use_desc && not_xyb && colour_encoding.colour_space != ColourEncoding::ColourSpace::kGrey; |
246 | |
|
247 | 0 | if (has_primaries) |
248 | 0 | colour_encoding.primaries = TRY(read_enum<ColourEncoding::Primaries>(stream)); |
249 | |
|
250 | 0 | if (colour_encoding.primaries == ColourEncoding::Primaries::kCustom) { |
251 | 0 | colour_encoding.red = TRY(read_custom_xy(stream)); |
252 | 0 | colour_encoding.green = TRY(read_custom_xy(stream)); |
253 | 0 | colour_encoding.blue = TRY(read_custom_xy(stream)); |
254 | 0 | } |
255 | |
|
256 | 0 | if (use_desc) { |
257 | 0 | colour_encoding.tf = TRY(read_custom_transfer_function(stream)); |
258 | 0 | colour_encoding.rendering_intent = TRY(read_enum<ColourEncoding::RenderingIntent>(stream)); |
259 | 0 | } |
260 | 0 | } |
261 | |
|
262 | 0 | return colour_encoding; |
263 | 0 | } |
264 | | /// |
265 | | |
266 | | /// B.3 - Extensions |
267 | | struct Extensions { |
268 | | u64 extensions {}; |
269 | | }; |
270 | | |
271 | | static ErrorOr<Extensions> read_extensions(LittleEndianInputBitStream& stream) |
272 | 0 | { |
273 | 0 | Extensions extensions; |
274 | 0 | extensions.extensions = TRY(U64(stream)); |
275 | |
|
276 | 0 | if (extensions.extensions != 0) |
277 | 0 | TODO(); |
278 | | |
279 | 0 | return extensions; |
280 | 0 | } |
281 | | /// |
282 | | |
283 | | /// K.2 - Non-separable upsampling |
284 | | Array s_d_up2 { |
285 | | -0.01716200, -0.03452303, -0.04022174, -0.02921014, -0.00624645, |
286 | | 0.14111091, 0.28896755, 0.00278718, -0.01610267, 0.56661550, |
287 | | 0.03777607, -0.01986694, -0.03144731, -0.01185068, -0.00213539 |
288 | | }; |
289 | | |
290 | | Array s_d_up4 = { |
291 | | -0.02419067, -0.03491987, -0.03693351, -0.03094285, -0.00529785, |
292 | | -0.01663432, -0.03556863, -0.03888905, -0.03516850, -0.00989469, |
293 | | 0.23651958, 0.33392945, -0.01073543, -0.01313181, -0.03556694, |
294 | | 0.13048175, 0.40103025, 0.03951150, -0.02077584, 0.46914198, |
295 | | -0.00209270, -0.01484589, -0.04064806, 0.18942530, 0.56279892, |
296 | | 0.06674400, -0.02335494, -0.03551682, -0.00754830, -0.02267919, |
297 | | -0.02363578, 0.00315804, -0.03399098, -0.01359519, -0.00091653, |
298 | | -0.00335467, -0.01163294, -0.01610294, -0.00974088, -0.00191622, |
299 | | -0.01095446, -0.03198464, -0.04455121, -0.02799790, -0.00645912, |
300 | | 0.06390599, 0.22963888, 0.00630981, -0.01897349, 0.67537268, |
301 | | 0.08483369, -0.02534994, -0.02205197, -0.01667999, -0.00384443 |
302 | | }; |
303 | | |
304 | | Array s_d_up8 { |
305 | | -0.02928613, -0.03706353, -0.03783812, -0.03324558, -0.00447632, -0.02519406, -0.03752601, -0.03901508, -0.03663285, -0.00646649, |
306 | | -0.02066407, -0.03838633, -0.04002101, -0.03900035, -0.00901973, -0.01626393, -0.03954148, -0.04046620, -0.03979621, -0.01224485, |
307 | | 0.29895328, 0.35757708, -0.02447552, -0.01081748, -0.04314594, 0.23903219, 0.41119301, -0.00573046, -0.01450239, -0.04246845, |
308 | | 0.17567618, 0.45220643, 0.02287757, -0.01936783, -0.03583255, 0.11572472, 0.47416733, 0.06284440, -0.02685066, 0.42720050, |
309 | | -0.02248939, -0.01155273, -0.04562755, 0.28689496, 0.49093869, -0.00007891, -0.01545926, -0.04562659, 0.21238920, 0.53980934, |
310 | | 0.03369474, -0.02070211, -0.03866988, 0.14229550, 0.56593398, 0.08045181, -0.02888298, -0.03680918, -0.00542229, -0.02920477, |
311 | | -0.02788574, -0.02118180, -0.03942402, -0.00775547, -0.02433614, -0.03193943, -0.02030828, -0.04044014, -0.01074016, -0.01930822, |
312 | | -0.03620399, -0.01974125, -0.03919545, -0.01456093, -0.00045072, -0.00360110, -0.01020207, -0.01231907, -0.00638988, -0.00071592, |
313 | | -0.00279122, -0.00957115, -0.01288327, -0.00730937, -0.00107783, -0.00210156, -0.00890705, -0.01317668, -0.00813895, -0.00153491, |
314 | | -0.02128481, -0.04173044, -0.04831487, -0.03293190, -0.00525260, -0.01720322, -0.04052736, -0.05045706, -0.03607317, -0.00738030, |
315 | | -0.01341764, -0.03965629, -0.05151616, -0.03814886, -0.01005819, 0.18968273, 0.33063684, -0.01300105, -0.01372950, -0.04017465, |
316 | | 0.13727832, 0.36402234, 0.01027890, -0.01832107, -0.03365072, 0.08734506, 0.38194295, 0.04338228, -0.02525993, 0.56408126, |
317 | | 0.00458352, -0.01648227, -0.04887868, 0.24585519, 0.62026135, 0.04314807, -0.02213737, -0.04158014, 0.16637289, 0.65027023, |
318 | | 0.09621636, -0.03101388, -0.04082742, -0.00904519, -0.02790922, -0.02117818, 0.00798662, -0.03995711, -0.01243427, -0.02231705, |
319 | | -0.02946266, 0.00992055, -0.03600283, -0.01684920, -0.00111684, -0.00411204, -0.01297130, -0.01723725, -0.01022545, -0.00165306, |
320 | | -0.00313110, -0.01218016, -0.01763266, -0.01125620, -0.00231663, -0.01374149, -0.03797620, -0.05142937, -0.03117307, -0.00581914, |
321 | | -0.01064003, -0.03608089, -0.05272168, -0.03375670, -0.00795586, 0.09628104, 0.27129991, -0.00353779, -0.01734151, -0.03153981, |
322 | | 0.05686230, 0.28500998, 0.02230594, -0.02374955, 0.68214326, 0.05018048, -0.02320852, -0.04383616, 0.18459474, 0.71517975, |
323 | | 0.10805613, -0.03263677, -0.03637639, -0.01394373, -0.02511203, -0.01728636, 0.05407331, -0.02867568, -0.01893131, -0.00240854, |
324 | | -0.00446511, -0.01636187, -0.02377053, -0.01522848, -0.00333334, -0.00819975, -0.02964169, -0.04499287, -0.02745350, -0.00612408, |
325 | | 0.02727416, 0.19446600, 0.00159832, -0.02232473, 0.74982506, 0.11452620, -0.03348048, -0.01605681, -0.02070339, -0.00458223 |
326 | | }; |
327 | | /// |
328 | | |
329 | | /// D.3 - Image metadata |
330 | | |
331 | | struct PreviewHeader { |
332 | | }; |
333 | | |
334 | | struct AnimationHeader { |
335 | | }; |
336 | | |
337 | | struct ExtraChannelInfo { |
338 | | enum class ExtraChannelType { |
339 | | kAlpha = 0, |
340 | | kDepth = 1, |
341 | | kSpotColour = 2, |
342 | | kSelectionMask = 3, |
343 | | kBlack = 4, |
344 | | kCFA = 5, |
345 | | kThermal = 6, |
346 | | kNonOptional = 15, |
347 | | kOptional = 16, |
348 | | }; |
349 | | |
350 | | bool d_alpha { true }; |
351 | | ExtraChannelType type { ExtraChannelType::kAlpha }; |
352 | | BitDepth bit_depth {}; |
353 | | u32 dim_shift {}; |
354 | | String name; |
355 | | bool alpha_associated { false }; |
356 | | }; |
357 | | |
358 | | static ErrorOr<ExtraChannelInfo> read_extra_channel_info(LittleEndianInputBitStream& stream) |
359 | 0 | { |
360 | 0 | ExtraChannelInfo extra_channel_info; |
361 | |
|
362 | 0 | extra_channel_info.d_alpha = TRY(stream.read_bit()); |
363 | |
|
364 | 0 | if (!extra_channel_info.d_alpha) { |
365 | 0 | extra_channel_info.type = TRY(read_enum<ExtraChannelInfo::ExtraChannelType>(stream)); |
366 | 0 | extra_channel_info.bit_depth = TRY(read_bit_depth(stream)); |
367 | 0 | extra_channel_info.dim_shift = U32(0, 3, 4, 1 + TRY(stream.read_bits(3))); |
368 | 0 | extra_channel_info.name = TRY(read_string(stream)); |
369 | |
|
370 | 0 | if (extra_channel_info.type == ExtraChannelInfo::ExtraChannelType::kAlpha) |
371 | 0 | extra_channel_info.alpha_associated = TRY(stream.read_bit()); |
372 | 0 | } |
373 | |
|
374 | 0 | if (extra_channel_info.type == ExtraChannelInfo::ExtraChannelType::kSpotColour) { |
375 | 0 | return Error::from_string_literal("JPEGXLLoader: Read extra channel info for SpotColour"); |
376 | 0 | } |
377 | | |
378 | 0 | if (extra_channel_info.type == ExtraChannelInfo::ExtraChannelType::kCFA) { |
379 | 0 | return Error::from_string_literal("JPEGXLLoader: Read extra channel info for CFA"); |
380 | 0 | } |
381 | | |
382 | 0 | return extra_channel_info; |
383 | 0 | } |
384 | | |
385 | | struct ToneMapping { |
386 | | f32 intensity_target { 255 }; |
387 | | f32 min_nits { 0 }; |
388 | | bool relative_to_max_display { false }; |
389 | | f32 linear_below { 0 }; |
390 | | }; |
391 | | |
392 | | static ErrorOr<ToneMapping> read_tone_mapping(LittleEndianInputBitStream& stream) |
393 | 0 | { |
394 | 0 | ToneMapping tone_mapping; |
395 | 0 | bool const all_default = TRY(stream.read_bit()); |
396 | |
|
397 | 0 | if (!all_default) { |
398 | 0 | tone_mapping.intensity_target = TRY(F16(stream)); |
399 | 0 | tone_mapping.min_nits = TRY(F16(stream)); |
400 | 0 | tone_mapping.relative_to_max_display = TRY(stream.read_bit()); |
401 | 0 | tone_mapping.linear_below = TRY(F16(stream)); |
402 | 0 | } |
403 | |
|
404 | 0 | return tone_mapping; |
405 | 0 | } |
406 | | |
407 | | // L.2.1 - OpsinInverseMatrix |
408 | | struct OpsinInverseMatrix { |
409 | | f32 inv_mat00 = 11.031566901960783; |
410 | | f32 inv_mat01 = -9.866943921568629; |
411 | | f32 inv_mat02 = -0.16462299647058826; |
412 | | f32 inv_mat10 = -3.254147380392157; |
413 | | f32 inv_mat11 = 4.418770392156863; |
414 | | f32 inv_mat12 = -0.16462299647058826; |
415 | | f32 inv_mat20 = -3.6588512862745097; |
416 | | f32 inv_mat21 = 2.7129230470588235; |
417 | | f32 inv_mat22 = 1.9459282392156863; |
418 | | f32 opsin_bias0 = -0.0037930732552754493; |
419 | | f32 opsin_bias1 = -0.0037930732552754493; |
420 | | f32 opsin_bias2 = -0.0037930732552754493; |
421 | | f32 quant_bias0 = 1 - 0.05465007330715401; |
422 | | f32 quant_bias1 = 1 - 0.07005449891748593; |
423 | | f32 quant_bias2 = 1 - 0.049935103337343655; |
424 | | f32 quant_bias_numerator = 0.145; |
425 | | }; |
426 | | |
427 | | static ErrorOr<OpsinInverseMatrix> read_opsin_inverse_matrix(LittleEndianInputBitStream& stream) |
428 | 0 | { |
429 | 0 | OpsinInverseMatrix matrix; |
430 | |
|
431 | 0 | bool all_default = TRY(stream.read_bit()); |
432 | |
|
433 | 0 | if (!all_default) { |
434 | 0 | matrix.inv_mat00 = TRY(F16(stream)); |
435 | 0 | matrix.inv_mat01 = TRY(F16(stream)); |
436 | 0 | matrix.inv_mat02 = TRY(F16(stream)); |
437 | 0 | matrix.inv_mat10 = TRY(F16(stream)); |
438 | 0 | matrix.inv_mat11 = TRY(F16(stream)); |
439 | 0 | matrix.inv_mat12 = TRY(F16(stream)); |
440 | 0 | matrix.inv_mat20 = TRY(F16(stream)); |
441 | 0 | matrix.inv_mat21 = TRY(F16(stream)); |
442 | 0 | matrix.inv_mat22 = TRY(F16(stream)); |
443 | 0 | matrix.opsin_bias0 = TRY(F16(stream)); |
444 | 0 | matrix.opsin_bias1 = TRY(F16(stream)); |
445 | 0 | matrix.opsin_bias2 = TRY(F16(stream)); |
446 | 0 | matrix.quant_bias0 = TRY(F16(stream)); |
447 | 0 | matrix.quant_bias1 = TRY(F16(stream)); |
448 | 0 | matrix.quant_bias2 = TRY(F16(stream)); |
449 | 0 | matrix.quant_bias_numerator = TRY(F16(stream)); |
450 | 0 | } |
451 | |
|
452 | 0 | return matrix; |
453 | 0 | } |
454 | | |
455 | | struct ImageMetadata { |
456 | | u8 orientation { 1 }; |
457 | | Optional<SizeHeader> intrinsic_size; |
458 | | Optional<PreviewHeader> preview; |
459 | | Optional<AnimationHeader> animation; |
460 | | BitDepth bit_depth; |
461 | | bool modular_16bit_buffers { true }; |
462 | | u16 num_extra_channels {}; |
463 | | Vector<ExtraChannelInfo, 4> ec_info; |
464 | | bool xyb_encoded { true }; |
465 | | ColourEncoding colour_encoding; |
466 | | ToneMapping tone_mapping; |
467 | | Extensions extensions; |
468 | | bool default_m; |
469 | | OpsinInverseMatrix opsin_inverse_matrix; |
470 | | u8 cw_mask { 0 }; |
471 | | |
472 | | Array<double, 15> up2_weight = s_d_up2; |
473 | | Array<double, 55> up4_weight = s_d_up4; |
474 | | Array<double, 210> up8_weight = s_d_up8; |
475 | | |
476 | | u16 number_of_color_channels() const |
477 | 0 | { |
478 | 0 | if (!xyb_encoded && colour_encoding.colour_space == ColourEncoding::ColourSpace::kGrey) |
479 | 0 | return 1; |
480 | 0 | return 3; |
481 | 0 | } |
482 | | |
483 | | u16 number_of_channels() const |
484 | 0 | { |
485 | 0 | return number_of_color_channels() + num_extra_channels; |
486 | 0 | } |
487 | | |
488 | | Optional<u16> black_channel() const |
489 | 0 | { |
490 | 0 | return first_extra_channel_matching([](auto& info) { return info.type == ExtraChannelInfo::ExtraChannelType::kBlack; }); |
491 | 0 | } |
492 | | |
493 | | Optional<u16> alpha_channel() const |
494 | 0 | { |
495 | 0 | return first_extra_channel_matching([](auto& info) { return info.type == ExtraChannelInfo::ExtraChannelType::kAlpha; }); |
496 | 0 | } |
497 | | |
498 | | private: |
499 | | Optional<u16> first_extra_channel_matching(auto&& condition) const |
500 | 0 | { |
501 | 0 | for (u16 i = 0; i < ec_info.size(); ++i) { |
502 | 0 | if (condition(ec_info[i])) |
503 | 0 | return i + number_of_color_channels(); |
504 | 0 | } |
505 | 0 | return OptionalNone {}; |
506 | 0 | } Unexecuted instantiation: AK::Optional<unsigned short> Gfx::JPEGXL::ImageMetadata::first_extra_channel_matching<Gfx::JPEGXL::ImageMetadata::black_channel() const::{lambda(auto:1&)#1}>(Gfx::JPEGXL::ImageMetadata::black_channel() const::{lambda(auto:1&)#1}&&) constUnexecuted instantiation: AK::Optional<unsigned short> Gfx::JPEGXL::ImageMetadata::first_extra_channel_matching<Gfx::JPEGXL::ImageMetadata::alpha_channel() const::{lambda(auto:1&)#1}>(Gfx::JPEGXL::ImageMetadata::alpha_channel() const::{lambda(auto:1&)#1}&&) const |
507 | | }; |
508 | | |
509 | | static ErrorOr<void> ensure_metadata_correctness(ImageMetadata const& metadata) |
510 | 0 | { |
511 | | // "This includes CMYK colour spaces; in that case, the RGB components are interpreted as |
512 | | // CMY where 0 means full ink, want_icc is true (see Table E.1), and there is an extra channel |
513 | | // of type kBlack (see Table D.9)." |
514 | 0 | bool should_be_cmyk = any_of(metadata.ec_info, [](auto& info) { return info.type == ExtraChannelInfo::ExtraChannelType::kBlack; }); |
515 | 0 | if (should_be_cmyk && !metadata.colour_encoding.want_icc) |
516 | 0 | return Error::from_string_literal("JPEGXLLoader: Seemingly CMYK image doesn't have an ICC profile"); |
517 | | |
518 | 0 | return {}; |
519 | 0 | } |
520 | | |
521 | | static ErrorOr<ImageMetadata> read_metadata_header(LittleEndianInputBitStream& stream) |
522 | 0 | { |
523 | 0 | ImageMetadata metadata; |
524 | 0 | bool const all_default = TRY(stream.read_bit()); |
525 | |
|
526 | 0 | if (!all_default) { |
527 | 0 | bool const extra_fields = TRY(stream.read_bit()); |
528 | |
|
529 | 0 | if (extra_fields) { |
530 | 0 | metadata.orientation = 1 + TRY(stream.read_bits(3)); |
531 | |
|
532 | 0 | bool const have_intr_size = TRY(stream.read_bit()); |
533 | 0 | if (have_intr_size) |
534 | 0 | metadata.intrinsic_size = TRY(read_size_header(stream)); |
535 | |
|
536 | 0 | bool const have_preview = TRY(stream.read_bit()); |
537 | 0 | if (have_preview) |
538 | 0 | TODO(); |
539 | | |
540 | 0 | bool const have_animation = TRY(stream.read_bit()); |
541 | 0 | if (have_animation) |
542 | 0 | TODO(); |
543 | 0 | } |
544 | | |
545 | 0 | metadata.bit_depth = TRY(read_bit_depth(stream)); |
546 | 0 | metadata.modular_16bit_buffers = TRY(stream.read_bit()); |
547 | 0 | metadata.num_extra_channels = U32(0, 1, 2 + TRY(stream.read_bits(4)), 1 + TRY(stream.read_bits(12))); |
548 | |
|
549 | 0 | for (u16 i {}; i < metadata.num_extra_channels; ++i) |
550 | 0 | metadata.ec_info.append(TRY(read_extra_channel_info(stream))); |
551 | |
|
552 | 0 | metadata.xyb_encoded = TRY(stream.read_bit()); |
553 | |
|
554 | 0 | metadata.colour_encoding = TRY(read_colour_encoding(stream)); |
555 | |
|
556 | 0 | if (extra_fields) |
557 | 0 | metadata.tone_mapping = TRY(read_tone_mapping(stream)); |
558 | |
|
559 | 0 | metadata.extensions = TRY(read_extensions(stream)); |
560 | 0 | } |
561 | | |
562 | 0 | metadata.default_m = TRY(stream.read_bit()); |
563 | |
|
564 | 0 | if (!metadata.default_m && metadata.xyb_encoded) |
565 | 0 | metadata.opsin_inverse_matrix = TRY(read_opsin_inverse_matrix(stream)); |
566 | |
|
567 | 0 | if (!metadata.default_m) |
568 | 0 | metadata.cw_mask = TRY(stream.read_bits(3)); |
569 | |
|
570 | 0 | if (metadata.cw_mask != 0) |
571 | 0 | TODO(); |
572 | | |
573 | 0 | TRY(ensure_metadata_correctness(metadata)); |
574 | |
|
575 | 0 | return metadata; |
576 | 0 | } |
577 | | /// |
578 | | |
579 | | /// Table F.7 — BlendingInfo bundle |
580 | | struct BlendingInfo { |
581 | | enum class SimpleBlendMode : u8 { |
582 | | kReplace = 0, |
583 | | kAdd = 1, |
584 | | kBlend = 2, |
585 | | kMulAdd = 3, |
586 | | kMul = 4, |
587 | | }; |
588 | | |
589 | | // This is a superset of `BlendingInfo::SimpleBlendMode` and defined in `Table K.1 — PatchBlendMode. |
590 | | // It is only used for patches, but having it here allows us to share some code. |
591 | | enum class BlendMode : u8 { |
592 | | kNone = 0, |
593 | | kReplace = 1, |
594 | | kAdd = 2, |
595 | | kMul = 3, |
596 | | kBlendAbove = 4, |
597 | | kBlendBelow = 5, |
598 | | kMulAddAbove = 6, |
599 | | kMulAddBelow = 7, |
600 | | }; |
601 | | |
602 | | static BlendMode to_general_blend_mode(SimpleBlendMode simple) |
603 | 0 | { |
604 | 0 | switch (simple) { |
605 | 0 | case SimpleBlendMode::kReplace: |
606 | 0 | return BlendMode::kReplace; |
607 | 0 | case SimpleBlendMode::kAdd: |
608 | 0 | return BlendMode::kAdd; |
609 | 0 | case SimpleBlendMode::kBlend: |
610 | 0 | return BlendMode::kBlendAbove; |
611 | 0 | case SimpleBlendMode::kMulAdd: |
612 | 0 | return BlendMode::kMulAddAbove; |
613 | 0 | case SimpleBlendMode::kMul: |
614 | 0 | return BlendMode::kMul; |
615 | 0 | } |
616 | 0 | VERIFY_NOT_REACHED(); |
617 | 0 | } |
618 | | |
619 | | BlendMode mode {}; |
620 | | u8 alpha_channel {}; |
621 | | bool clamp { false }; |
622 | | u8 source {}; |
623 | | }; |
624 | | |
625 | | static ErrorOr<BlendingInfo> read_blending_info(LittleEndianInputBitStream& stream, ImageMetadata const& metadata, bool full_frame) |
626 | 0 | { |
627 | 0 | BlendingInfo blending_info; |
628 | |
|
629 | 0 | auto simple = static_cast<BlendingInfo::SimpleBlendMode>(U32(0, 1, 2, 3 + TRY(stream.read_bits(2)))); |
630 | 0 | blending_info.mode = BlendingInfo::to_general_blend_mode(simple); |
631 | |
|
632 | 0 | bool const extra = metadata.num_extra_channels > 0; |
633 | |
|
634 | 0 | if (extra) { |
635 | 0 | auto const blend_or_mul_add = blending_info.mode == BlendingInfo::BlendMode::kBlendAbove |
636 | 0 | || blending_info.mode == BlendingInfo::BlendMode::kMulAddAbove; |
637 | |
|
638 | 0 | if (blend_or_mul_add) |
639 | 0 | blending_info.alpha_channel = U32(0, 1, 2, 3 + TRY(stream.read_bits(3))); |
640 | |
|
641 | 0 | if (blend_or_mul_add || blending_info.mode == BlendingInfo::BlendMode::kMul) |
642 | 0 | blending_info.clamp = TRY(stream.read_bit()); |
643 | 0 | } |
644 | |
|
645 | 0 | if (blending_info.mode != BlendingInfo::BlendMode::kReplace |
646 | 0 | || !full_frame) { |
647 | 0 | blending_info.source = TRY(stream.read_bits(2)); |
648 | 0 | } |
649 | |
|
650 | 0 | return blending_info; |
651 | 0 | } |
652 | | /// |
653 | | |
654 | | // From FrameHeader, but used in RestorationFilter |
655 | | enum class Encoding { |
656 | | kVarDCT = 0, |
657 | | kModular = 1, |
658 | | }; |
659 | | |
660 | | /// J.1 - General |
661 | | struct RestorationFilter { |
662 | | bool gab { true }; |
663 | | bool gab_custom { false }; |
664 | | f32 gab_x_weight1 { 0.115169525 }; |
665 | | f32 gab_x_weight2 { 0.061248592 }; |
666 | | f32 gab_y_weight1 { 0.115169525 }; |
667 | | f32 gab_y_weight2 { 0.061248592 }; |
668 | | f32 gab_b_weight1 { 0.115169525 }; |
669 | | f32 gab_b_weight2 { 0.061248592 }; |
670 | | |
671 | | u8 epf_iters { 2 }; |
672 | | |
673 | | bool epf_sharp_custom { false }; |
674 | | Array<f32, 8> epf_sharp_lut { 0, 1. / 7, 2. / 7, 3. / 7, 4. / 7, 5. / 7, 6. / 7, 1 }; |
675 | | |
676 | | bool epf_weight_custom { false }; |
677 | | Array<f32, 3> epf_channel_scale { 40.0, 5.0, 3.5 }; |
678 | | |
679 | | bool epf_sigma_custom { false }; |
680 | | f32 epf_quant_mul { 0.46 }; |
681 | | f32 epf_pass0_sigma_scale { 0.9 }; |
682 | | f32 epf_pass2_sigma_scale { 6.5 }; |
683 | | f32 epf_border_sad_mul { 2. / 3 }; |
684 | | f32 epf_sigma_for_modular { 1.0 }; |
685 | | |
686 | | Extensions extensions; |
687 | | }; |
688 | | |
689 | | static ErrorOr<RestorationFilter> read_restoration_filter(LittleEndianInputBitStream& stream, Encoding encoding) |
690 | 0 | { |
691 | 0 | RestorationFilter restoration_filter; |
692 | |
|
693 | 0 | auto const all_defaults = TRY(stream.read_bit()); |
694 | |
|
695 | 0 | if (!all_defaults) { |
696 | 0 | restoration_filter.gab = TRY(stream.read_bit()); |
697 | |
|
698 | 0 | if (restoration_filter.gab) { |
699 | 0 | restoration_filter.gab_custom = TRY(stream.read_bit()); |
700 | 0 | if (restoration_filter.gab_custom) { |
701 | 0 | restoration_filter.gab_x_weight1 = TRY(F16(stream)); |
702 | 0 | restoration_filter.gab_x_weight2 = TRY(F16(stream)); |
703 | 0 | restoration_filter.gab_y_weight1 = TRY(F16(stream)); |
704 | 0 | restoration_filter.gab_y_weight2 = TRY(F16(stream)); |
705 | 0 | restoration_filter.gab_b_weight1 = TRY(F16(stream)); |
706 | 0 | restoration_filter.gab_b_weight2 = TRY(F16(stream)); |
707 | 0 | } |
708 | 0 | } |
709 | |
|
710 | 0 | restoration_filter.epf_iters = TRY(stream.read_bits(2)); |
711 | 0 | if (restoration_filter.epf_iters != 0) { |
712 | 0 | if (encoding == Encoding::kVarDCT) { |
713 | 0 | restoration_filter.epf_sharp_custom = TRY(stream.read_bit()); |
714 | 0 | if (restoration_filter.epf_sharp_custom) |
715 | 0 | return Error::from_string_literal("JPEGXLLoader: Implement custom restoration filters"); |
716 | 0 | } |
717 | 0 | restoration_filter.epf_weight_custom = TRY(stream.read_bit()); |
718 | 0 | if (restoration_filter.epf_sharp_custom) |
719 | 0 | return Error::from_string_literal("JPEGXLLoader: Implement custom restoration filters"); |
720 | | |
721 | 0 | restoration_filter.epf_sigma_custom = TRY(stream.read_bit()); |
722 | 0 | if (restoration_filter.epf_sharp_custom) |
723 | 0 | return Error::from_string_literal("JPEGXLLoader: Implement custom restoration filters"); |
724 | | |
725 | 0 | if (encoding == Encoding::kModular) |
726 | 0 | restoration_filter.epf_sigma_for_modular = TRY(F16(stream)); |
727 | 0 | } |
728 | | |
729 | 0 | restoration_filter.extensions = TRY(read_extensions(stream)); |
730 | 0 | } |
731 | | |
732 | 0 | return restoration_filter; |
733 | 0 | } |
734 | | /// |
735 | | |
736 | | /// Table F.6 — Passes bundle |
737 | | struct Passes { |
738 | | u8 num_passes { 1 }; |
739 | | }; |
740 | | |
741 | | static ErrorOr<Passes> read_passes(LittleEndianInputBitStream& stream) |
742 | 0 | { |
743 | 0 | Passes passes; |
744 | |
|
745 | 0 | passes.num_passes = U32(1, 2, 3, 4 + TRY(stream.read_bits(3))); |
746 | |
|
747 | 0 | if (passes.num_passes != 1) { |
748 | 0 | TODO(); |
749 | 0 | } |
750 | | |
751 | 0 | return passes; |
752 | 0 | } |
753 | | /// |
754 | | |
755 | | /// F.2 - FrameHeader |
756 | | struct FrameHeader { |
757 | | enum class FrameType { |
758 | | kRegularFrame = 0, |
759 | | kLFFrame = 1, |
760 | | kReferenceOnly = 2, |
761 | | kSkipProgressive = 3, |
762 | | }; |
763 | | |
764 | | enum class Flags { |
765 | | None = 0, |
766 | | kNoise = 1, |
767 | | kPatches = 1 << 1, |
768 | | kSplines = 1 << 4, |
769 | | kUseLfFrame = 1 << 5, |
770 | | kSkipAdaptiveLFSmoothing = 1 << 7, |
771 | | }; |
772 | | |
773 | | FrameType frame_type { FrameType::kRegularFrame }; |
774 | | Encoding encoding { Encoding::kVarDCT }; |
775 | | Flags flags { Flags::None }; |
776 | | |
777 | | bool do_YCbCr { false }; |
778 | | |
779 | | Array<u8, 3> jpeg_upsampling {}; |
780 | | u8 upsampling {}; |
781 | | FixedArray<u8> ec_upsampling {}; |
782 | | |
783 | | u8 group_size_shift { 1 }; |
784 | 0 | u16 group_dim() const { return 128 << group_size_shift; } |
785 | | u8 x_qm_scale { 3 }; |
786 | | u8 b_qm_scale { 2 }; |
787 | | Passes passes {}; |
788 | | |
789 | | u8 lf_level {}; |
790 | | bool have_crop { false }; |
791 | | i32 x0 {}; |
792 | | i32 y0 {}; |
793 | | u32 width {}; |
794 | | u32 height {}; |
795 | | |
796 | | BlendingInfo blending_info {}; |
797 | | FixedArray<BlendingInfo> ec_blending_info {}; |
798 | | |
799 | | u32 duration {}; |
800 | | |
801 | | bool is_last { true }; |
802 | | u8 save_as_reference {}; |
803 | | bool save_before_ct {}; |
804 | | |
805 | | String name {}; |
806 | | RestorationFilter restoration_filter {}; |
807 | | Extensions extensions {}; |
808 | | }; |
809 | | |
810 | | static int operator&(FrameHeader::Flags first, FrameHeader::Flags second) |
811 | 0 | { |
812 | 0 | return static_cast<int>(first) & static_cast<int>(second); |
813 | 0 | } |
814 | | |
815 | | static ErrorOr<FrameHeader> read_frame_header(LittleEndianInputBitStream& stream, |
816 | | SizeHeader size_header, |
817 | | ImageMetadata const& metadata) |
818 | 0 | { |
819 | 0 | FrameHeader frame_header; |
820 | 0 | bool const all_default = TRY(stream.read_bit()); |
821 | |
|
822 | 0 | if (!all_default) { |
823 | 0 | frame_header.frame_type = static_cast<FrameHeader::FrameType>(TRY(stream.read_bits(2))); |
824 | 0 | frame_header.encoding = static_cast<Encoding>(TRY(stream.read_bits(1))); |
825 | |
|
826 | 0 | frame_header.flags = static_cast<FrameHeader::Flags>(TRY(U64(stream))); |
827 | |
|
828 | 0 | if (!metadata.xyb_encoded) |
829 | 0 | frame_header.do_YCbCr = TRY(stream.read_bit()); |
830 | |
|
831 | 0 | if (!(frame_header.flags & FrameHeader::Flags::kUseLfFrame)) { |
832 | 0 | if (frame_header.do_YCbCr) { |
833 | 0 | frame_header.jpeg_upsampling[0] = TRY(stream.read_bits(2)); |
834 | 0 | frame_header.jpeg_upsampling[1] = TRY(stream.read_bits(2)); |
835 | 0 | frame_header.jpeg_upsampling[2] = TRY(stream.read_bits(2)); |
836 | 0 | } |
837 | |
|
838 | 0 | frame_header.upsampling = U32(1, 2, 4, 8); |
839 | |
|
840 | 0 | frame_header.ec_upsampling = TRY(FixedArray<u8>::create(metadata.num_extra_channels)); |
841 | 0 | for (u16 i {}; i < metadata.num_extra_channels; ++i) |
842 | 0 | frame_header.ec_upsampling[i] = U32(1, 2, 4, 8); |
843 | 0 | } |
844 | |
|
845 | 0 | if (frame_header.encoding == Encoding::kModular) |
846 | 0 | frame_header.group_size_shift = TRY(stream.read_bits(2)); |
847 | | |
848 | | // Set x_qm_scale default value |
849 | 0 | frame_header.x_qm_scale = metadata.xyb_encoded && frame_header.encoding == Encoding::kVarDCT ? 3 : 2; |
850 | |
|
851 | 0 | if (metadata.xyb_encoded && frame_header.encoding == Encoding::kVarDCT) { |
852 | 0 | frame_header.x_qm_scale = TRY(stream.read_bits(3)); |
853 | 0 | frame_header.b_qm_scale = TRY(stream.read_bits(3)); |
854 | 0 | } |
855 | |
|
856 | 0 | if (frame_header.frame_type != FrameHeader::FrameType::kReferenceOnly) |
857 | 0 | frame_header.passes = TRY(read_passes(stream)); |
858 | |
|
859 | 0 | if (frame_header.frame_type == FrameHeader::FrameType::kLFFrame) |
860 | 0 | frame_header.lf_level = 1 + TRY(stream.read_bits(2)); |
861 | |
|
862 | 0 | if (frame_header.frame_type != FrameHeader::FrameType::kLFFrame) |
863 | 0 | frame_header.have_crop = TRY(stream.read_bit()); |
864 | |
|
865 | 0 | if (frame_header.have_crop) { |
866 | 0 | auto const read_crop_dimension = [&]() -> ErrorOr<u32> { |
867 | 0 | return U32(TRY(stream.read_bits(8)), 256 + TRY(stream.read_bits(11)), 2304 + TRY(stream.read_bits(14)), 18688 + TRY(stream.read_bits(30))); |
868 | 0 | }; |
869 | |
|
870 | 0 | if (frame_header.frame_type != FrameHeader::FrameType::kReferenceOnly) { |
871 | 0 | frame_header.x0 = unpack_signed(TRY(read_crop_dimension())); |
872 | 0 | frame_header.y0 = unpack_signed(TRY(read_crop_dimension())); |
873 | 0 | } |
874 | |
|
875 | 0 | frame_header.width = TRY(read_crop_dimension()); |
876 | 0 | frame_header.height = TRY(read_crop_dimension()); |
877 | 0 | } |
878 | |
|
879 | 0 | bool const normal_frame = frame_header.frame_type == FrameHeader::FrameType::kRegularFrame |
880 | 0 | || frame_header.frame_type == FrameHeader::FrameType::kSkipProgressive; |
881 | | |
882 | | // Let full_frame be true if and only if have_crop is false or if the frame area given |
883 | | // by width and height and offsets x0 and y0 completely covers the image area. |
884 | 0 | bool const cover_image_area = frame_header.x0 <= 0 && frame_header.y0 <= 0 |
885 | 0 | && (frame_header.width + frame_header.x0 >= size_header.width) |
886 | 0 | && (frame_header.height + frame_header.y0 == size_header.height); |
887 | 0 | bool const full_frame = !frame_header.have_crop || cover_image_area; |
888 | | |
889 | | // Set default value for is_last |
890 | 0 | frame_header.is_last = frame_header.frame_type == FrameHeader::FrameType::kRegularFrame; |
891 | |
|
892 | 0 | if (normal_frame) { |
893 | 0 | frame_header.blending_info = TRY(read_blending_info(stream, metadata, full_frame)); |
894 | |
|
895 | 0 | frame_header.ec_blending_info = TRY(FixedArray<BlendingInfo>::create(metadata.num_extra_channels)); |
896 | 0 | for (u16 i {}; i < metadata.num_extra_channels; ++i) |
897 | 0 | frame_header.ec_blending_info[i] = TRY(read_blending_info(stream, metadata, full_frame)); |
898 | |
|
899 | 0 | if (metadata.animation.has_value()) |
900 | 0 | TODO(); |
901 | | |
902 | 0 | frame_header.is_last = TRY(stream.read_bit()); |
903 | 0 | } |
904 | | |
905 | 0 | if (frame_header.frame_type != FrameHeader::FrameType::kLFFrame && !frame_header.is_last) |
906 | 0 | frame_header.save_as_reference = TRY(stream.read_bits(2)); |
907 | |
|
908 | 0 | auto const resets_canvas = full_frame && frame_header.blending_info.mode == BlendingInfo::BlendMode::kReplace; |
909 | 0 | auto const can_reference = !frame_header.is_last && (frame_header.duration == 0 || frame_header.save_as_reference != 0) && frame_header.frame_type != FrameHeader::FrameType::kLFFrame; |
910 | |
|
911 | 0 | frame_header.save_before_ct = !normal_frame; |
912 | 0 | if (frame_header.frame_type == FrameHeader::FrameType::kReferenceOnly || (resets_canvas && can_reference)) |
913 | 0 | frame_header.save_before_ct = TRY(stream.read_bit()); |
914 | |
|
915 | 0 | frame_header.name = TRY(read_string(stream)); |
916 | |
|
917 | 0 | frame_header.restoration_filter = TRY(read_restoration_filter(stream, frame_header.encoding)); |
918 | |
|
919 | 0 | frame_header.extensions = TRY(read_extensions(stream)); |
920 | 0 | } |
921 | | |
922 | 0 | return frame_header; |
923 | 0 | } |
924 | | /// |
925 | | |
926 | | /// F.3 TOC |
927 | | struct TOC { |
928 | | FixedArray<u32> entries; |
929 | | FixedArray<u32> group_offsets; |
930 | | }; |
931 | | |
932 | | static u64 num_toc_entries(FrameHeader const& frame_header, u64 num_groups, u64 num_lf_groups) |
933 | 0 | { |
934 | | // F.3.1 - General |
935 | 0 | if (num_groups == 1 && frame_header.passes.num_passes == 1) |
936 | 0 | return 1; |
937 | | |
938 | 0 | return 1 + num_lf_groups + 1 + num_groups * frame_header.passes.num_passes; |
939 | 0 | } |
940 | | |
941 | | // F.3.2 - Decoding permutations |
942 | | static ErrorOr<Vector<u32>> decode_permutations(LittleEndianInputBitStream& stream, EntropyDecoder& decoder, u32 size, u32 skip) |
943 | 0 | { |
944 | | // "Let GetContext(x) denote min(7, ceil(log2(x + 1)))." |
945 | 0 | auto get_context = [](u32 x) -> u32 { |
946 | 0 | return min(7, ceil(log2(x + 1))); |
947 | 0 | }; |
948 | | |
949 | | // "The decoder first decodes an integer end, as specified in C.3.3, |
950 | | // using DecodeHybridVarLenUint(GetContext(size))." |
951 | 0 | auto end = TRY(decoder.decode_hybrid_uint(stream, get_context(size))); |
952 | | |
953 | | // "The value end is at most size − skip." |
954 | 0 | if (end > size - skip) |
955 | 0 | return Error::from_string_literal("JPEGXLLoader: Invalid value for end when decoding permutations"); |
956 | | |
957 | | // "Then a sequence lehmer of size elements is produced as follows. It is zero-initialized." |
958 | 0 | auto lehmer = TRY(FixedArray<u32>::create(size)); |
959 | | |
960 | | // "For each index i in range [skip, skip + end), the value lehmer[i] is set to |
961 | | // DecodeHybridVarLenUint(GetContext(i > skip ? lehmer[i − 1] : 0));" |
962 | 0 | for (u32 i = skip; i < skip + end; ++i) { |
963 | 0 | lehmer[i] = TRY(decoder.decode_hybrid_uint(stream, get_context(i > skip ? lehmer[i - 1] : 0))); |
964 | | // "this value is strictly less than size − i." |
965 | 0 | if (lehmer[i] >= size - i) |
966 | 0 | return Error::from_string_literal("JPEGXLLoader: Decoded permutation is invalid"); |
967 | 0 | } |
968 | | |
969 | | // "The decoder then maintains a sequence of elements temp, initially containing |
970 | | // the numbers [0, size) in increasing order," |
971 | 0 | Vector<u32> temp; |
972 | 0 | TRY(temp.try_ensure_capacity(size)); |
973 | 0 | for (u32 i = 0; i < size; ++i) |
974 | 0 | temp.append(i); |
975 | | |
976 | | // "and a sequence of elements permutation, initially empty." |
977 | 0 | Vector<u32> permutation; |
978 | 0 | TRY(permutation.try_ensure_capacity(size)); |
979 | | |
980 | | // "Then, for each integer i in the range [0, size), the decoder appends to |
981 | | // permutation element temp[lehmer[i]], then removes it from temp, leaving the |
982 | | // relative order of other elements unchanged." |
983 | 0 | for (u32 i = 0; i < size; ++i) { |
984 | 0 | permutation.append(temp[lehmer[i]]); |
985 | 0 | temp.remove(lehmer[i]); |
986 | 0 | } |
987 | | |
988 | | // " Finally, permutation is the decoded permutation." |
989 | 0 | return permutation; |
990 | 0 | } |
991 | | |
992 | | static ErrorOr<TOC> read_toc(LittleEndianInputBitStream& stream, FrameHeader const& frame_header, u64 num_groups, u64 num_lf_groups) |
993 | 0 | { |
994 | 0 | TOC toc; |
995 | |
|
996 | 0 | bool const permuted_toc = TRY(stream.read_bit()); |
997 | |
|
998 | 0 | if (permuted_toc) { |
999 | | // Read permutations |
1000 | 0 | TODO(); |
1001 | 0 | } |
1002 | | |
1003 | | // F.3.3 - Decoding TOC |
1004 | 0 | stream.align_to_byte_boundary(); |
1005 | |
|
1006 | 0 | auto const toc_entries = num_toc_entries(frame_header, num_groups, num_lf_groups); |
1007 | |
|
1008 | 0 | toc.entries = TRY(FixedArray<u32>::create(toc_entries)); |
1009 | 0 | toc.group_offsets = TRY(FixedArray<u32>::create(toc_entries)); |
1010 | |
|
1011 | 0 | for (u32 i {}; i < toc_entries; ++i) { |
1012 | 0 | auto const new_entry = U32( |
1013 | 0 | TRY(stream.read_bits(10)), |
1014 | 0 | 1024 + TRY(stream.read_bits(14)), |
1015 | 0 | 17408 + TRY(stream.read_bits(22)), |
1016 | 0 | 4211712 + TRY(stream.read_bits(30))); |
1017 | |
|
1018 | 0 | toc.entries[i] = new_entry; |
1019 | | |
1020 | | // The decoder then computes an array group_offsets, which has 0 as its first element |
1021 | | // and subsequent group_offsets[i] are the sum of all TOC entries [0, i). |
1022 | 0 | toc.group_offsets[i] = i == 0 ? 0 : toc.group_offsets[i - 1] + toc.entries[i - 1]; |
1023 | 0 | } |
1024 | |
|
1025 | 0 | if (permuted_toc) |
1026 | 0 | TODO(); |
1027 | | |
1028 | 0 | stream.align_to_byte_boundary(); |
1029 | |
|
1030 | 0 | return toc; |
1031 | 0 | } |
1032 | | /// |
1033 | | |
1034 | | /// G.1.2 - LF channel dequantization weights |
1035 | | struct LfChannelDequantization { |
1036 | | f32 m_x_lf_unscaled { 1. / (32 * 128) }; |
1037 | | f32 m_y_lf_unscaled { 1. / (4 * 128) }; |
1038 | | f32 m_b_lf_unscaled { 1. / (2 * 128) }; |
1039 | | }; |
1040 | | |
1041 | | static ErrorOr<LfChannelDequantization> read_lf_channel_dequantization(LittleEndianInputBitStream& stream) |
1042 | 0 | { |
1043 | 0 | LfChannelDequantization lf_channel_dequantization; |
1044 | |
|
1045 | 0 | auto const all_default = TRY(stream.read_bit()); |
1046 | |
|
1047 | 0 | if (!all_default) { |
1048 | 0 | lf_channel_dequantization.m_x_lf_unscaled = TRY(F16(stream)) / 128; |
1049 | 0 | lf_channel_dequantization.m_y_lf_unscaled = TRY(F16(stream)) / 128; |
1050 | 0 | lf_channel_dequantization.m_b_lf_unscaled = TRY(F16(stream)) / 128; |
1051 | 0 | } |
1052 | |
|
1053 | 0 | return lf_channel_dequantization; |
1054 | 0 | } |
1055 | | /// |
1056 | | |
1057 | | /// H.4.2 - MA tree decoding |
1058 | | class MATree { |
1059 | | public: |
1060 | | struct LeafNode { |
1061 | | u32 ctx {}; |
1062 | | u8 predictor {}; |
1063 | | i32 offset {}; |
1064 | | u32 multiplier {}; |
1065 | | }; |
1066 | | |
1067 | | static ErrorOr<MATree> decode(LittleEndianInputBitStream& stream, Optional<EntropyDecoder>& decoder) |
1068 | 0 | { |
1069 | | // G.1.3 - GlobalModular |
1070 | 0 | MATree tree; |
1071 | | |
1072 | | // 1 / 2 Read the 6 pre-clustered distributions |
1073 | 0 | auto const num_distrib = 6; |
1074 | 0 | VERIFY(!decoder.has_value()); |
1075 | 0 | decoder = TRY(EntropyDecoder::create(stream, num_distrib)); |
1076 | | |
1077 | | // 2 / 2 Decode the tree |
1078 | |
|
1079 | 0 | u64 ctx_id = 0; |
1080 | 0 | u64 nodes_left = 1; |
1081 | 0 | tree.m_tree.clear(); |
1082 | |
|
1083 | 0 | while (nodes_left > 0) { |
1084 | 0 | nodes_left--; |
1085 | |
|
1086 | 0 | i32 const property = TRY(decoder->decode_hybrid_uint(stream, 1)) - 1; |
1087 | |
|
1088 | 0 | if (property >= 0) { |
1089 | 0 | DecisionNode decision_node; |
1090 | 0 | decision_node.property = property; |
1091 | 0 | decision_node.value = unpack_signed(TRY(decoder->decode_hybrid_uint(stream, 0))); |
1092 | 0 | decision_node.left_child = tree.m_tree.size() + nodes_left + 1; |
1093 | 0 | decision_node.right_child = tree.m_tree.size() + nodes_left + 2; |
1094 | 0 | tree.m_tree.empend(decision_node); |
1095 | 0 | nodes_left += 2; |
1096 | 0 | } else { |
1097 | 0 | LeafNode leaf_node; |
1098 | 0 | leaf_node.ctx = ctx_id++; |
1099 | 0 | leaf_node.predictor = TRY(decoder->decode_hybrid_uint(stream, 2)); |
1100 | 0 | leaf_node.offset = unpack_signed(TRY(decoder->decode_hybrid_uint(stream, 3))); |
1101 | 0 | auto const mul_log = TRY(decoder->decode_hybrid_uint(stream, 4)); |
1102 | 0 | auto const mul_bits = TRY(decoder->decode_hybrid_uint(stream, 5)); |
1103 | 0 | leaf_node.multiplier = (mul_bits + 1) << mul_log; |
1104 | 0 | tree.m_tree.empend(leaf_node); |
1105 | 0 | } |
1106 | 0 | } |
1107 | 0 | TRY(decoder->ensure_end_state()); |
1108 | | |
1109 | | // Finally, the decoder reads (tree.size() + 1) / 2 pre-clustered distributions D as specified in C.1. |
1110 | |
|
1111 | 0 | auto const num_pre_clustered_distributions = (tree.m_tree.size() + 1) / 2; |
1112 | 0 | decoder = TRY(EntropyDecoder::create(stream, num_pre_clustered_distributions)); |
1113 | |
|
1114 | 0 | tree.save_self_correction_usage(); |
1115 | |
|
1116 | 0 | return tree; |
1117 | 0 | } |
1118 | | |
1119 | | LeafNode get_leaf(Span<i32> properties) const |
1120 | 0 | { |
1121 | | // To find the MA leaf node, the MA tree is traversed, starting at the root node tree[0] |
1122 | | // and for each decision node d, testing if property[d.property] > d.value, proceeding to |
1123 | | // the node tree[d.left_child] if the test evaluates to true and to the node tree[d.right_child] |
1124 | | // otherwise, until a leaf node is reached. |
1125 | |
|
1126 | 0 | DecisionNode node { m_tree[0].get<DecisionNode>() }; |
1127 | 0 | while (true) { |
1128 | 0 | auto const next_node = [this, &properties, &node]() { |
1129 | | // Note: The behavior when trying to access a non-existing property is taken from jxl-oxide |
1130 | 0 | if (node.property < properties.size() && properties[node.property] > node.value) |
1131 | 0 | return m_tree[node.left_child]; |
1132 | 0 | return m_tree[node.right_child]; |
1133 | 0 | }(); |
1134 | |
|
1135 | 0 | if (next_node.has<LeafNode>()) |
1136 | 0 | return next_node.get<LeafNode>(); |
1137 | 0 | node = next_node.get<DecisionNode>(); |
1138 | 0 | } |
1139 | 0 | } |
1140 | | |
1141 | | bool use_self_correcting_predictor() const |
1142 | 0 | { |
1143 | 0 | return m_use_self_correcting_predictor; |
1144 | 0 | } |
1145 | | |
1146 | | private: |
1147 | | void save_self_correction_usage() |
1148 | 0 | { |
1149 | 0 | for (auto const& node : m_tree) { |
1150 | | // We are looking for usage of the Self Correction predictor, so this includes both the |
1151 | | // 'max_error' property and the 'Self-correcting' predictor, They are given as index 15 |
1152 | | // in Table H.4 — Property definitions and index 6 in Table H.3 — Modular predictors respectively. |
1153 | 0 | auto const use_max_error = node.has<DecisionNode>() && node.get<DecisionNode>().property == 15; |
1154 | 0 | auto const use_self_correcting = node.has<LeafNode>() && node.get<LeafNode>().predictor == 6; |
1155 | 0 | if (use_max_error || use_self_correcting) { |
1156 | 0 | m_use_self_correcting_predictor = true; |
1157 | 0 | return; |
1158 | 0 | } |
1159 | 0 | } |
1160 | | |
1161 | 0 | m_use_self_correcting_predictor = false; |
1162 | 0 | } |
1163 | | |
1164 | | struct DecisionNode { |
1165 | | u64 property {}; |
1166 | | i64 value {}; |
1167 | | u64 left_child {}; |
1168 | | u64 right_child {}; |
1169 | | }; |
1170 | | |
1171 | | Vector<Variant<DecisionNode, LeafNode>> m_tree; |
1172 | | |
1173 | | bool m_use_self_correcting_predictor { true }; |
1174 | | }; |
1175 | | /// |
1176 | | |
1177 | | /// Local abstractions to store the decoded image |
1178 | | class BlendedImage { |
1179 | | public: |
1180 | | ErrorOr<void> blend_into(BlendedImage& image, BlendingInfo::BlendMode mode) const |
1181 | 0 | { |
1182 | 0 | if (to_underlying(mode) > 2) |
1183 | 0 | return Error::from_string_literal("JPEGXLLoder: Unsupported blend mode"); |
1184 | | |
1185 | 0 | auto input_rect = active_rectangle(); |
1186 | 0 | auto output_rect = image.active_rectangle(); |
1187 | |
|
1188 | 0 | if (input_rect.size() != output_rect.size()) |
1189 | 0 | return Error::from_string_literal("JPEGXLLoder: Unable to blend image with a different size"); |
1190 | | |
1191 | 0 | for (u32 i = 0; i < channels().size(); ++i) { |
1192 | 0 | auto const& input_channel = channels()[i]; |
1193 | 0 | auto& output_channel = image.channels()[i]; |
1194 | |
|
1195 | 0 | if (mode == BlendingInfo::BlendMode::kNone) |
1196 | 0 | blend_channel<BlendingInfo::BlendMode::kNone>(input_channel, input_rect, output_channel, output_rect); |
1197 | 0 | else if (mode == BlendingInfo::BlendMode::kReplace) |
1198 | 0 | blend_channel<BlendingInfo::BlendMode::kReplace>(input_channel, input_rect, output_channel, output_rect); |
1199 | 0 | else if (mode == BlendingInfo::BlendMode::kAdd) |
1200 | 0 | blend_channel<BlendingInfo::BlendMode::kAdd>(input_channel, input_rect, output_channel, output_rect); |
1201 | 0 | } |
1202 | |
|
1203 | 0 | return {}; |
1204 | 0 | } |
1205 | | |
1206 | | protected: |
1207 | 0 | virtual ~BlendedImage() = default; |
1208 | | |
1209 | | virtual Vector<Channel>& channels() = 0; |
1210 | | virtual Vector<Channel> const& channels() const = 0; |
1211 | | virtual IntRect active_rectangle() const = 0; |
1212 | 0 | IntSize size() const { return active_rectangle().size(); } |
1213 | | |
1214 | | private: |
1215 | | template<BlendingInfo::BlendMode blend_mode> |
1216 | | void blend_channel(Channel const& input_channel, IntRect input_rect, |
1217 | | Channel& output_channel, IntRect output_rect) const |
1218 | 0 | { |
1219 | 0 | for (u32 y = 0; y < static_cast<u32>(input_rect.height()); ++y) { |
1220 | 0 | for (u32 x = 0; x < static_cast<u32>(input_rect.width()); ++x) { |
1221 | 0 | auto const old_sample = output_channel.get(x + output_rect.x(), y + output_rect.y()); |
1222 | 0 | auto const new_sample = input_channel.get(x + input_rect.x(), y + input_rect.y()); |
1223 | |
|
1224 | 0 | auto const sample = [&]() { |
1225 | | // Table F.8 — BlendMode (BlendingInfo.mode) |
1226 | | if constexpr (blend_mode == BlendingInfo::BlendMode::kNone) |
1227 | 0 | return old_sample; |
1228 | | if constexpr (blend_mode == BlendingInfo::BlendMode::kReplace) |
1229 | 0 | return new_sample; |
1230 | | if constexpr (blend_mode == BlendingInfo::BlendMode::kAdd) |
1231 | 0 | return old_sample + new_sample; |
1232 | 0 | }(); Unexecuted instantiation: Gfx::JPEGXL::BlendedImage::blend_channel<(Gfx::JPEGXL::BlendingInfo::BlendMode)0>(Gfx::JPEGXL::Detail::Channel<int> const&, Gfx::Rect<int>, Gfx::JPEGXL::Detail::Channel<int>&, Gfx::Rect<int>) const::{lambda()#1}::operator()() constUnexecuted instantiation: Gfx::JPEGXL::BlendedImage::blend_channel<(Gfx::JPEGXL::BlendingInfo::BlendMode)1>(Gfx::JPEGXL::Detail::Channel<int> const&, Gfx::Rect<int>, Gfx::JPEGXL::Detail::Channel<int>&, Gfx::Rect<int>) const::{lambda()#1}::operator()() constUnexecuted instantiation: Gfx::JPEGXL::BlendedImage::blend_channel<(Gfx::JPEGXL::BlendingInfo::BlendMode)2>(Gfx::JPEGXL::Detail::Channel<int> const&, Gfx::Rect<int>, Gfx::JPEGXL::Detail::Channel<int>&, Gfx::Rect<int>) const::{lambda()#1}::operator()() const |
1233 | 0 | output_channel.set(x + output_rect.x(), y + output_rect.y(), sample); |
1234 | 0 | } |
1235 | 0 | } |
1236 | 0 | } Unexecuted instantiation: void Gfx::JPEGXL::BlendedImage::blend_channel<(Gfx::JPEGXL::BlendingInfo::BlendMode)0>(Gfx::JPEGXL::Detail::Channel<int> const&, Gfx::Rect<int>, Gfx::JPEGXL::Detail::Channel<int>&, Gfx::Rect<int>) const Unexecuted instantiation: void Gfx::JPEGXL::BlendedImage::blend_channel<(Gfx::JPEGXL::BlendingInfo::BlendMode)1>(Gfx::JPEGXL::Detail::Channel<int> const&, Gfx::Rect<int>, Gfx::JPEGXL::Detail::Channel<int>&, Gfx::Rect<int>) const Unexecuted instantiation: void Gfx::JPEGXL::BlendedImage::blend_channel<(Gfx::JPEGXL::BlendingInfo::BlendMode)2>(Gfx::JPEGXL::Detail::Channel<int> const&, Gfx::Rect<int>, Gfx::JPEGXL::Detail::Channel<int>&, Gfx::Rect<int>) const |
1237 | | }; |
1238 | | |
1239 | | class ImageView : public BlendedImage { |
1240 | | public: |
1241 | | ImageView(Vector<Channel>& channels, IntRect active_rect) |
1242 | 0 | : m_channels_view(channels) |
1243 | 0 | , m_active_rect(active_rect) |
1244 | 0 | { |
1245 | 0 | } |
1246 | | |
1247 | | private: |
1248 | | virtual Vector<Channel> const& channels() const override |
1249 | 0 | { |
1250 | 0 | return m_channels_view; |
1251 | 0 | } |
1252 | | |
1253 | | virtual Vector<Channel>& channels() override |
1254 | 0 | { |
1255 | 0 | return m_channels_view; |
1256 | 0 | } |
1257 | | |
1258 | | virtual IntRect active_rectangle() const override |
1259 | 0 | { |
1260 | 0 | return m_active_rect; |
1261 | 0 | } |
1262 | | |
1263 | | Vector<Channel>& m_channels_view; |
1264 | | IntRect m_active_rect; |
1265 | | }; |
1266 | | |
1267 | | class Image : public BlendedImage { |
1268 | | public: |
1269 | | static ErrorOr<Image> create(IntSize size, ImageMetadata const& metadata) |
1270 | 0 | { |
1271 | 0 | Image image {}; |
1272 | |
|
1273 | 0 | for (u16 i = 0; i < metadata.number_of_channels(); ++i) { |
1274 | 0 | if (i < metadata.number_of_color_channels()) { |
1275 | 0 | TRY(image.m_channels.try_append(TRY(Channel::create(ChannelInfo::from_size(size))))); |
1276 | 0 | } else { |
1277 | 0 | auto const dim_shift = metadata.ec_info[i - metadata.number_of_color_channels()].dim_shift; |
1278 | 0 | TRY(image.m_channels.try_append(TRY(Channel::create( |
1279 | 0 | { |
1280 | 0 | .width = static_cast<u32>(size.width() >> dim_shift), |
1281 | 0 | .height = static_cast<u32>(size.height() >> dim_shift), |
1282 | 0 | })))); |
1283 | 0 | } |
1284 | 0 | } |
1285 | |
|
1286 | 0 | return image; |
1287 | 0 | } |
1288 | | |
1289 | | static ErrorOr<Image> adopt_channels(Vector<Channel>&& channels) |
1290 | 0 | { |
1291 | 0 | if (channels.size() > 1) { |
1292 | 0 | if (any_of(channels, [&](auto const& channel) { |
1293 | 0 | return channel.width() != channels[0].width() || channel.height() != channels[0].height(); |
1294 | 0 | })) { |
1295 | 0 | return Error::from_string_literal("JPEGXLLoader: One of the Global Modular channel has a different size"); |
1296 | 0 | } |
1297 | 0 | } |
1298 | 0 | return Image { move(channels) }; |
1299 | 0 | } |
1300 | | |
1301 | | ErrorOr<ImageView> get_subimage(IntRect rectangle) |
1302 | 0 | { |
1303 | 0 | if (rectangle.right() > size().width() |
1304 | 0 | || rectangle.bottom() > size().height()) |
1305 | 0 | return Error::from_string_literal("JPEGXLLoader: Can't create subimage from out-of-bounds rectangle"); |
1306 | | |
1307 | 0 | return ImageView { m_channels, rectangle }; |
1308 | 0 | } |
1309 | | |
1310 | | ErrorOr<NonnullRefPtr<CMYKBitmap>> to_cmyk_bitmap(ImageMetadata const& metadata) const |
1311 | 0 | { |
1312 | 0 | auto const width = m_channels[0].width(); |
1313 | 0 | auto const height = m_channels[0].height(); |
1314 | |
|
1315 | 0 | if (metadata.bit_depth.bits_per_sample != 8) |
1316 | 0 | return Error::from_string_literal("JPEGXLLoader: Unsupported bit-depth for CMYK image"); |
1317 | | |
1318 | 0 | auto const orientation = static_cast<TIFF::Orientation>(metadata.orientation); |
1319 | 0 | auto oriented_bitmap = TRY(ExifOrientedCMYKBitmap::create(orientation, { width, height })); |
1320 | |
|
1321 | 0 | auto const black_channel = *metadata.black_channel(); |
1322 | |
|
1323 | 0 | for (u32 y {}; y < height; ++y) { |
1324 | 0 | for (u32 x {}; x < width; ++x) { |
1325 | 0 | CMYK const color = CMYK( |
1326 | 0 | 255 - clamp(m_channels[0].get(x, y), 0, 255), |
1327 | 0 | 255 - clamp(m_channels[1].get(x, y), 0, 255), |
1328 | 0 | 255 - clamp(m_channels[2].get(x, y), 0, 255), |
1329 | 0 | 255 - clamp(m_channels[black_channel].get(x, y), 0, 255)); |
1330 | 0 | oriented_bitmap.set_pixel(x, y, color); |
1331 | 0 | } |
1332 | 0 | } |
1333 | |
|
1334 | 0 | return oriented_bitmap.bitmap(); |
1335 | 0 | } |
1336 | | |
1337 | | ErrorOr<NonnullRefPtr<Bitmap>> to_bitmap(ImageMetadata const& metadata) const |
1338 | 0 | { |
1339 | 0 | auto const width = m_channels[0].width(); |
1340 | 0 | auto const height = m_channels[0].height(); |
1341 | |
|
1342 | 0 | auto const orientation = static_cast<TIFF::Orientation>(metadata.orientation); |
1343 | 0 | auto oriented_bitmap = TRY(ExifOrientedBitmap::create(orientation, { width, height }, BitmapFormat::BGRA8888)); |
1344 | |
|
1345 | 0 | auto const alpha_channel = metadata.alpha_channel(); |
1346 | |
|
1347 | 0 | auto const bits_per_sample = metadata.bit_depth.bits_per_sample; |
1348 | 0 | VERIFY(bits_per_sample >= 8); |
1349 | 0 | for (u32 y {}; y < height; ++y) { |
1350 | 0 | for (u32 x {}; x < width; ++x) { |
1351 | 0 | auto const to_u8 = [&, bits_per_sample](i32 sample) -> u8 { |
1352 | | // FIXME: Don't truncate the result to 8 bits |
1353 | 0 | static constexpr auto maximum_supported_bit_depth = 8; |
1354 | 0 | if (bits_per_sample > maximum_supported_bit_depth) |
1355 | 0 | sample >>= (bits_per_sample - maximum_supported_bit_depth); |
1356 | |
|
1357 | 0 | return clamp(sample + .5, 0, (1 << maximum_supported_bit_depth) - 1); |
1358 | 0 | }; |
1359 | |
|
1360 | 0 | auto const color = [&]() -> Color { |
1361 | 0 | if (metadata.number_of_color_channels() == 1) { |
1362 | 0 | auto gray = to_u8(m_channels[0].get(x, y)); |
1363 | 0 | return { gray, gray, gray }; |
1364 | 0 | } |
1365 | | |
1366 | 0 | if (!alpha_channel.has_value()) { |
1367 | 0 | return { to_u8(m_channels[0].get(x, y)), |
1368 | 0 | to_u8(m_channels[1].get(x, y)), |
1369 | 0 | to_u8(m_channels[2].get(x, y)) }; |
1370 | 0 | } |
1371 | | |
1372 | 0 | return { |
1373 | 0 | to_u8(m_channels[0].get(x, y)), |
1374 | 0 | to_u8(m_channels[1].get(x, y)), |
1375 | 0 | to_u8(m_channels[2].get(x, y)), |
1376 | 0 | to_u8(m_channels[*alpha_channel].get(x, y)), |
1377 | 0 | }; |
1378 | 0 | }(); |
1379 | 0 | oriented_bitmap.set_pixel(x, y, color.value()); |
1380 | 0 | } |
1381 | 0 | } |
1382 | |
|
1383 | 0 | return oriented_bitmap.bitmap(); |
1384 | 0 | } |
1385 | | |
1386 | | virtual Vector<Channel> const& channels() const override |
1387 | 0 | { |
1388 | 0 | return m_channels; |
1389 | 0 | } |
1390 | | |
1391 | | virtual Vector<Channel>& channels() override |
1392 | 0 | { |
1393 | 0 | return m_channels; |
1394 | 0 | } |
1395 | | |
1396 | | IntRect rect() const |
1397 | 0 | { |
1398 | 0 | return active_rectangle(); |
1399 | 0 | } |
1400 | | |
1401 | | private: |
1402 | 0 | Image() = default; |
1403 | | |
1404 | | Image(Vector<Channel>&& channels) |
1405 | 0 | : m_channels(move(channels)) |
1406 | 0 | { |
1407 | 0 | } |
1408 | | |
1409 | | IntRect active_rectangle() const override |
1410 | 0 | { |
1411 | 0 | return IntRect(0, 0, m_channels[0].width(), m_channels[0].height()); |
1412 | 0 | } |
1413 | | |
1414 | | Vector<Channel> m_channels; |
1415 | | }; |
1416 | | /// |
1417 | | |
1418 | | /// H.2 - Image decoding |
1419 | | |
1420 | | static ErrorOr<void> add_default_squeeze_params(TransformInfo& tr, Span<ChannelInfo> channels, u32 nb_meta_channels) |
1421 | 0 | { |
1422 | | // H.6.2.1 Parameters - "The default parameters (the case when sp.size() == 0) are specified by the following code:" |
1423 | |
|
1424 | 0 | auto first = nb_meta_channels; |
1425 | 0 | auto count = channels.size() - first; |
1426 | 0 | auto w = channels[first].width; |
1427 | 0 | auto h = channels[first].height; |
1428 | 0 | SqueezeParams param; |
1429 | 0 | if (count > 2 && channels[first + 1].width == w && channels[first + 1].height == h) { |
1430 | 0 | param.begin_c = first + 1; |
1431 | 0 | param.num_c = 2; |
1432 | 0 | param.in_place = false; |
1433 | 0 | param.horizontal = true; |
1434 | 0 | tr.sp.append(param); |
1435 | 0 | param.horizontal = false; |
1436 | 0 | tr.sp.append(param); |
1437 | 0 | } |
1438 | 0 | param.begin_c = first; |
1439 | 0 | param.num_c = count; |
1440 | 0 | param.in_place = true; |
1441 | 0 | if (h >= w && h > 8) { |
1442 | 0 | param.horizontal = false; |
1443 | 0 | tr.sp.append(param); |
1444 | 0 | h = (h + 1) / 2; |
1445 | 0 | } |
1446 | 0 | while (w > 8 || h > 8) { |
1447 | 0 | if (w > 8) { |
1448 | 0 | param.horizontal = true; |
1449 | 0 | tr.sp.append(param); |
1450 | 0 | w = (w + 1) / 2; |
1451 | 0 | } |
1452 | 0 | if (h > 8) { |
1453 | 0 | param.horizontal = false; |
1454 | 0 | tr.sp.append(param); |
1455 | 0 | h = (h + 1) / 2; |
1456 | 0 | } |
1457 | 0 | } |
1458 | 0 | return {}; |
1459 | 0 | } |
1460 | | |
1461 | | struct ModularData { |
1462 | | bool use_global_tree {}; |
1463 | | WPHeader wp_params {}; |
1464 | | Vector<TransformInfo> transform {}; |
1465 | | |
1466 | | // Initially, nb_meta_channels is set to zero, but transformations can modify this value. |
1467 | | u32 nb_meta_channels {}; |
1468 | | |
1469 | | Vector<Channel> channels {}; |
1470 | | |
1471 | | ErrorOr<void> create_channels(Span<ChannelInfo> frame_size) |
1472 | 0 | { |
1473 | 0 | Vector<ChannelInfo> channel_infos {}; |
1474 | 0 | TRY(channel_infos.try_extend(frame_size)); |
1475 | |
|
1476 | 0 | for (auto& tr : transform) { |
1477 | 0 | if (tr.tr == TransformInfo::TransformId::kPalette) { |
1478 | | // Let end_c = begin_c + num_c − 1. When updating the channel list as described in H.2, channels begin_c to end_c, |
1479 | | // which all have the same dimensions, are replaced with two new channels: |
1480 | | // - one meta-channel, inserted at the beginning of the channel list and has dimensions width = nb_colours and height = num_c and hshift = vshift = −1. |
1481 | | // This channel represents the colours or deltas of the palette. |
1482 | | // - one channel (at the same position in the channel list as the original channels, same dimensions) which contains palette indices. |
1483 | |
|
1484 | 0 | auto original_dimensions = channel_infos[tr.begin_c]; |
1485 | 0 | channel_infos.remove(tr.begin_c, tr.num_c); |
1486 | 0 | TRY(channel_infos.try_insert(tr.begin_c, original_dimensions)); |
1487 | 0 | TRY(channel_infos.try_prepend({ .width = tr.nb_colours, .height = tr.num_c, .hshift = -1, .vshift = -1 })); |
1488 | |
|
1489 | 0 | if (tr.begin_c < nb_meta_channels) |
1490 | 0 | nb_meta_channels += 2 - tr.begin_c; |
1491 | 0 | else |
1492 | 0 | nb_meta_channels += 1; |
1493 | 0 | } else if (tr.tr == TransformInfo::TransformId::kSqueeze) { |
1494 | 0 | if (tr.sp.is_empty()) |
1495 | 0 | TRY(add_default_squeeze_params(tr, channel_infos, nb_meta_channels)); |
1496 | | |
1497 | | // "Let begin = sp[i].begin_c and end = begin + sp[i].num_c − 1. |
1498 | | // The channel list is modified as specified by the following code:" |
1499 | 0 | for (u32 i = 0; i < tr.sp.size(); i++) { |
1500 | 0 | auto begin = tr.sp[i].begin_c; |
1501 | 0 | auto end = begin + tr.sp[i].num_c - 1; |
1502 | 0 | auto r = tr.sp[i].in_place ? end + 1 : channel_infos.size(); |
1503 | 0 | if (begin < nb_meta_channels) { |
1504 | | /* sp[i].in_place is true */ |
1505 | | /* end < nb_meta_channels */ |
1506 | 0 | if (!tr.sp[i].in_place || end >= nb_meta_channels) |
1507 | 0 | return Error::from_string_literal("JPEGXLLoader: Invalid values in the squeeze transform"); |
1508 | 0 | nb_meta_channels += tr.sp[i].num_c; |
1509 | 0 | } |
1510 | 0 | for (u32 c = begin; c <= end; c++) { |
1511 | 0 | auto w = channel_infos[c].width; |
1512 | 0 | auto h = channel_infos[c].height; |
1513 | | /* w > 0 and h > 0 */ |
1514 | 0 | if (w == 0 || h == 0) |
1515 | 0 | return Error::from_string_literal("JPEGXLLoader: Can't apply the squeeze transform on a channel with a null dimension"); |
1516 | | |
1517 | 0 | ChannelInfo residu; |
1518 | 0 | if (tr.sp[i].horizontal) { |
1519 | 0 | channel_infos[c].width = (w + 1) / 2; |
1520 | 0 | if (channel_infos[c].hshift >= 0) |
1521 | 0 | channel_infos[c].hshift++; |
1522 | 0 | residu = channel_infos[c]; |
1523 | 0 | residu.width = w / 2; |
1524 | 0 | } else { |
1525 | 0 | channel_infos[c].height = (h + 1) / 2; |
1526 | 0 | if (channel_infos[c].vshift >= 0) |
1527 | 0 | channel_infos[c].vshift++; |
1528 | 0 | residu = channel_infos[c]; |
1529 | 0 | residu.height = h / 2; |
1530 | 0 | } |
1531 | | /* Insert residu into channel at index r + c − begin */ |
1532 | 0 | TRY(channel_infos.try_insert(r + c - begin, residu)); |
1533 | 0 | } |
1534 | 0 | } |
1535 | 0 | } |
1536 | 0 | } |
1537 | | |
1538 | 0 | TRY(channels.try_ensure_capacity(channel_infos.size())); |
1539 | 0 | for (u32 i = 0; i < channel_infos.size(); ++i) |
1540 | 0 | channels.append(TRY(Channel::create(channel_infos[i]))); |
1541 | |
|
1542 | 0 | return {}; |
1543 | 0 | } |
1544 | | }; |
1545 | | |
1546 | | static constexpr u32 nb_base_predictors = 16; |
1547 | | |
1548 | | static void get_properties(FixedArray<i32>& properties, Span<Channel> channels, u16 i, u32 x, u32 y, i32 max_error) |
1549 | 0 | { |
1550 | | // Table H.4 - Property definitions |
1551 | 0 | properties[0] = i; |
1552 | 0 | properties[2] = y; |
1553 | 0 | properties[3] = x; |
1554 | |
|
1555 | 0 | i32 const W = x > 0 ? channels[i].get(x - 1, y) : (y > 0 ? channels[i].get(x, y - 1) : 0); |
1556 | 0 | i32 const N = y > 0 ? channels[i].get(x, y - 1) : W; |
1557 | 0 | i32 const NW = x > 0 && y > 0 ? channels[i].get(x - 1, y - 1) : W; |
1558 | 0 | i32 const NE = x + 1 < channels[i].width() && y > 0 ? channels[i].get(x + 1, y - 1) : N; |
1559 | 0 | i32 const NN = y > 1 ? channels[i].get(x, y - 2) : N; |
1560 | 0 | i32 const WW = x > 1 ? channels[i].get(x - 2, y) : W; |
1561 | |
|
1562 | 0 | properties[4] = abs(N); |
1563 | 0 | properties[5] = abs(W); |
1564 | 0 | properties[6] = N; |
1565 | 0 | properties[7] = W; |
1566 | | |
1567 | | // x > 0 ? W - /* (the value of property 9 at position (x - 1, y)) */ : W |
1568 | 0 | if (x > 0) { |
1569 | 0 | auto const x_1 = x - 1; |
1570 | 0 | i32 const W_x_1 = x_1 > 0 ? channels[i].get(x_1 - 1, y) : (y > 0 ? channels[i].get(x_1, y - 1) : 0); |
1571 | 0 | i32 const N_x_1 = y > 0 ? channels[i].get(x_1, y - 1) : W_x_1; |
1572 | 0 | i32 const NW_x_1 = x_1 > 0 && y > 0 ? channels[i].get(x_1 - 1, y - 1) : W_x_1; |
1573 | 0 | properties[8] = W - (W_x_1 + N_x_1 - NW_x_1); |
1574 | 0 | } else { |
1575 | 0 | properties[8] = W; |
1576 | 0 | } |
1577 | |
|
1578 | 0 | properties[9] = W + N - NW; |
1579 | 0 | properties[10] = W - NW; |
1580 | 0 | properties[11] = NW - N; |
1581 | 0 | properties[12] = N - NE; |
1582 | 0 | properties[13] = N - NN; |
1583 | 0 | properties[14] = W - WW; |
1584 | |
|
1585 | 0 | properties[15] = max_error; |
1586 | |
|
1587 | 0 | for (i16 j = i - 1; j >= 0; j--) { |
1588 | 0 | if (channels[j].width() != channels[i].width()) |
1589 | 0 | continue; |
1590 | 0 | if (channels[j].height() != channels[i].height()) |
1591 | 0 | continue; |
1592 | 0 | if (channels[j].hshift() != channels[i].hshift()) |
1593 | 0 | continue; |
1594 | 0 | if (channels[j].vshift() != channels[i].vshift()) |
1595 | 0 | continue; |
1596 | 0 | auto rC = channels[j].get(x, y); |
1597 | 0 | auto rW = (x > 0 ? channels[j].get(x - 1, y) : 0); |
1598 | 0 | auto rN = (y > 0 ? channels[j].get(x, y - 1) : rW); |
1599 | 0 | auto rNW = (x > 0 && y > 0 ? channels[j].get(x - 1, y - 1) : rW); |
1600 | 0 | auto rG = clamp(rW + rN - rNW, min(rW, rN), max(rW, rN)); |
1601 | 0 | properties[nb_base_predictors + (i - 1 - j) * 4 + 0] = abs(rC); |
1602 | 0 | properties[nb_base_predictors + (i - 1 - j) * 4 + 1] = rC; |
1603 | 0 | properties[nb_base_predictors + (i - 1 - j) * 4 + 2] = abs(rC - rG); |
1604 | 0 | properties[nb_base_predictors + (i - 1 - j) * 4 + 3] = rC - rG; |
1605 | 0 | } |
1606 | 0 | } |
1607 | | |
1608 | | struct ModularOptions { |
1609 | | Span<ChannelInfo> channels_info; |
1610 | | Optional<EntropyDecoder>& decoder; |
1611 | | MATree const& global_tree; |
1612 | | u32 group_dim {}; |
1613 | | u32 stream_index {}; |
1614 | | |
1615 | | enum class ApplyTransformations : u8 { |
1616 | | No, |
1617 | | Yes, |
1618 | | }; |
1619 | | |
1620 | | ApplyTransformations apply_transformations { ApplyTransformations::Yes }; |
1621 | | u32 bit_depth {}; |
1622 | | }; |
1623 | | |
1624 | | static ErrorOr<ModularData> read_modular_bitstream(LittleEndianInputBitStream& stream, |
1625 | | ModularOptions&& options) |
1626 | 0 | { |
1627 | 0 | auto [channels_info, |
1628 | 0 | decoder, |
1629 | 0 | global_tree, |
1630 | 0 | group_dim, |
1631 | 0 | stream_index, |
1632 | 0 | should_apply_transformation, |
1633 | 0 | bit_depth] |
1634 | 0 | = options; |
1635 | |
|
1636 | 0 | ModularData modular_data; |
1637 | |
|
1638 | 0 | modular_data.use_global_tree = TRY(stream.read_bit()); |
1639 | 0 | modular_data.wp_params = TRY(read_self_correcting_predictor(stream)); |
1640 | 0 | auto const nb_transforms = U32(0, 1, 2 + TRY(stream.read_bits(4)), 18 + TRY(stream.read_bits(8))); |
1641 | |
|
1642 | 0 | TRY(modular_data.transform.try_resize(nb_transforms)); |
1643 | 0 | for (u32 i {}; i < nb_transforms; ++i) |
1644 | 0 | modular_data.transform[i] = TRY(read_transform_info(stream)); |
1645 | |
|
1646 | 0 | TRY(modular_data.create_channels(channels_info)); |
1647 | | |
1648 | | // "However, the decoder only decodes the first nb_meta_channels channels and any further channels |
1649 | | // that have a width and height that are both at most group_dim. At that point, it stops decoding." |
1650 | 0 | u32 first_non_decoded_index = NumericLimits<u32>::max(); |
1651 | 0 | auto will_be_decoded = [&](u32 index, Channel const& channel) { |
1652 | 0 | if (channel.width() == 0 || channel.height() == 0) |
1653 | 0 | return false; |
1654 | 0 | if (index < modular_data.nb_meta_channels) |
1655 | 0 | return true; |
1656 | 0 | if (index >= first_non_decoded_index) |
1657 | 0 | return false; |
1658 | 0 | if (channel.width() <= group_dim && channel.height() <= group_dim) |
1659 | 0 | return true; |
1660 | 0 | first_non_decoded_index = index; |
1661 | 0 | return false; |
1662 | 0 | }; |
1663 | |
|
1664 | | if constexpr (JPEGXL_DEBUG) { |
1665 | | dbgln("Decoding modular sub-stream ({} tree, {} transforms, stream_index={}):", |
1666 | | modular_data.use_global_tree ? "global"sv : "local"sv, |
1667 | | nb_transforms, |
1668 | | stream_index); |
1669 | | |
1670 | | for (auto const& tr : modular_data.transform) { |
1671 | | switch (tr.tr) { |
1672 | | case TransformInfo::TransformId::kRCT: |
1673 | | dbgln("* RCT: begin_c={} - rct_type={}", tr.begin_c, tr.rct_type); |
1674 | | break; |
1675 | | case TransformInfo::TransformId::kPalette: |
1676 | | dbgln("* Palette: begin_c={} - num_c={} - nb_colours={} - nb_deltas={} - d_pred={}", |
1677 | | tr.begin_c, tr.num_c, tr.nb_colours, tr.nb_deltas, tr.d_pred); |
1678 | | break; |
1679 | | case TransformInfo::TransformId::kSqueeze: |
1680 | | dbgln("* Squeeze: num_sp={}", tr.sp.size()); |
1681 | | break; |
1682 | | } |
1683 | | } |
1684 | | for (auto const& [i, channel] : enumerate(modular_data.channels)) |
1685 | | dbgln("- Channel {}: {}x{}{}", i, channel.width(), channel.height(), will_be_decoded(i, channel) ? ""sv : " - skipped"sv); |
1686 | | } |
1687 | |
|
1688 | 0 | Optional<MATree> local_tree; |
1689 | 0 | if (!modular_data.use_global_tree) |
1690 | 0 | TODO(); |
1691 | | |
1692 | | // where the dist_multiplier from C.3.3 is set to the largest channel width amongst all channels |
1693 | | // that are to be decoded. |
1694 | 0 | auto const dist_multiplier = [&]() { |
1695 | 0 | u32 dist_multiplier {}; |
1696 | 0 | for (auto [i, channel] : enumerate(modular_data.channels)) { |
1697 | 0 | if (will_be_decoded(i, channel) && channel.width() > dist_multiplier) |
1698 | 0 | dist_multiplier = channel.width(); |
1699 | 0 | } |
1700 | 0 | return dist_multiplier; |
1701 | 0 | }(); |
1702 | 0 | decoder->set_dist_multiplier(dist_multiplier); |
1703 | | |
1704 | | // The decoder then starts an entropy-coded stream (C.1) and decodes the data for each channel |
1705 | | // (in ascending order of index) as specified in H.3, skipping any channels having width or height |
1706 | | // zero. Finally, the inverse transformations are applied (from last to first) as described in H.6. |
1707 | |
|
1708 | 0 | auto properties = TRY(FixedArray<i32>::create(nb_base_predictors + modular_data.channels.size() * 4)); |
1709 | 0 | properties[1] = stream_index; |
1710 | |
|
1711 | 0 | auto const& tree = local_tree.has_value() ? *local_tree : global_tree; |
1712 | 0 | for (auto [i, channel] : enumerate(modular_data.channels)) { |
1713 | 0 | if (!will_be_decoded(i, channel)) |
1714 | 0 | continue; |
1715 | | |
1716 | 0 | auto self_correcting_data = TRY(SelfCorrectingData::create(modular_data.wp_params, channel.width())); |
1717 | |
|
1718 | 0 | for (u32 y {}; y < channel.height(); y++) { |
1719 | 0 | for (u32 x {}; x < channel.width(); x++) { |
1720 | 0 | auto const neighborhood = retrieve_neighborhood(channel, x, y); |
1721 | |
|
1722 | 0 | SelfCorrectingData::Predictions self_prediction {}; |
1723 | 0 | if (tree.use_self_correcting_predictor()) |
1724 | 0 | self_prediction = self_correcting_data.compute_predictions(neighborhood, x); |
1725 | |
|
1726 | 0 | get_properties(properties, modular_data.channels, i, x, y, self_prediction.max_error); |
1727 | 0 | auto const leaf_node = tree.get_leaf(properties); |
1728 | 0 | auto diff = unpack_signed(TRY(decoder->decode_hybrid_uint(stream, leaf_node.ctx))); |
1729 | 0 | diff = (diff * leaf_node.multiplier) + leaf_node.offset; |
1730 | 0 | auto const total = diff + prediction(neighborhood, self_prediction.prediction, leaf_node.predictor); |
1731 | |
|
1732 | 0 | if (tree.use_self_correcting_predictor()) |
1733 | 0 | self_correcting_data.compute_errors(x, total); |
1734 | 0 | channel.set(x, y, total); |
1735 | 0 | } |
1736 | |
|
1737 | 0 | self_correcting_data.register_next_row(); |
1738 | 0 | } |
1739 | |
|
1740 | 0 | channel.set_decoded(true); |
1741 | 0 | } |
1742 | 0 | TRY(decoder->ensure_end_state()); |
1743 | |
|
1744 | 0 | if (should_apply_transformation == ModularOptions::ApplyTransformations::Yes) { |
1745 | 0 | for (auto const& tr : modular_data.transform.in_reverse()) |
1746 | 0 | TRY(apply_transformation(modular_data.channels, tr, bit_depth, modular_data.wp_params)); |
1747 | 0 | } |
1748 | |
|
1749 | 0 | return modular_data; |
1750 | 0 | } |
1751 | | /// |
1752 | | |
1753 | | /// G.1.2 - LF channel dequantization weights |
1754 | | struct GlobalModular { |
1755 | | Optional<EntropyDecoder> decoder; |
1756 | | MATree ma_tree; |
1757 | | ModularData modular_data; |
1758 | | }; |
1759 | | |
1760 | | static ErrorOr<GlobalModular> read_global_modular(LittleEndianInputBitStream& stream, |
1761 | | IntSize frame_size, |
1762 | | FrameHeader const& frame_header, |
1763 | | ImageMetadata const& metadata) |
1764 | 0 | { |
1765 | 0 | GlobalModular global_modular; |
1766 | |
|
1767 | 0 | auto const decode_ma_tree = TRY(stream.read_bit()); |
1768 | |
|
1769 | 0 | if (decode_ma_tree) |
1770 | 0 | global_modular.ma_tree = TRY(MATree::decode(stream, global_modular.decoder)); |
1771 | | |
1772 | | // The decoder then decodes a modular sub-bitstream (Annex H), where |
1773 | | // the number of channels is computed as follows: |
1774 | |
|
1775 | 0 | auto num_channels = metadata.num_extra_channels; |
1776 | 0 | if (frame_header.encoding == Encoding::kModular) { |
1777 | 0 | if (!frame_header.do_YCbCr && !metadata.xyb_encoded |
1778 | 0 | && metadata.colour_encoding.colour_space == ColourEncoding::ColourSpace::kGrey) { |
1779 | 0 | num_channels += 1; |
1780 | 0 | } else { |
1781 | 0 | num_channels += 3; |
1782 | 0 | } |
1783 | 0 | } |
1784 | |
|
1785 | 0 | auto channels = TRY(FixedArray<ChannelInfo>::create(num_channels)); |
1786 | 0 | channels.fill_with(ChannelInfo::from_size(frame_size)); |
1787 | |
|
1788 | 0 | if (channels.is_empty()) |
1789 | 0 | return global_modular; |
1790 | | |
1791 | | // "No inverse transforms are applied yet." |
1792 | 0 | global_modular.modular_data = TRY(read_modular_bitstream(stream, |
1793 | 0 | { |
1794 | 0 | .channels_info = channels, |
1795 | 0 | .decoder = global_modular.decoder, |
1796 | 0 | .global_tree = global_modular.ma_tree, |
1797 | 0 | .group_dim = frame_header.group_dim(), |
1798 | 0 | .stream_index = 0, |
1799 | 0 | .apply_transformations = ModularOptions::ApplyTransformations::No, |
1800 | 0 | .bit_depth = metadata.bit_depth.bits_per_sample, |
1801 | 0 | })); |
1802 | |
|
1803 | 0 | return global_modular; |
1804 | 0 | } |
1805 | | /// |
1806 | | |
1807 | | /// K.3.1 Patches decoding |
1808 | | struct Patch { |
1809 | | u32 width {}; |
1810 | | u32 height {}; |
1811 | | |
1812 | | u32 ref {}; |
1813 | | |
1814 | | u32 x0 {}; |
1815 | | u32 y0 {}; |
1816 | | |
1817 | | u32 count {}; |
1818 | | |
1819 | | // x[] and y[] in the spec |
1820 | | FixedArray<IntPoint> positions; |
1821 | | |
1822 | | // "blending: arrays of count blend mode information structures, which consists of arrays of mode, alpha_channel and clamp" |
1823 | | FixedArray<FixedArray<BlendingInfo>> blending; |
1824 | | }; |
1825 | | |
1826 | | static ErrorOr<Patch> read_patch(LittleEndianInputBitStream& stream, EntropyDecoder& decoder, u32 num_extra_channels) |
1827 | 0 | { |
1828 | 0 | Patch patch; |
1829 | 0 | patch.ref = TRY(decoder.decode_hybrid_uint(stream, 1)); |
1830 | 0 | patch.x0 = TRY(decoder.decode_hybrid_uint(stream, 3)); |
1831 | 0 | patch.y0 = TRY(decoder.decode_hybrid_uint(stream, 3)); |
1832 | 0 | patch.width = TRY(decoder.decode_hybrid_uint(stream, 2)) + 1; |
1833 | 0 | patch.height = TRY(decoder.decode_hybrid_uint(stream, 2)) + 1; |
1834 | 0 | patch.count = TRY(decoder.decode_hybrid_uint(stream, 7)) + 1; |
1835 | |
|
1836 | 0 | patch.positions = TRY(FixedArray<IntPoint>::create(patch.count)); |
1837 | 0 | patch.blending = TRY(FixedArray<FixedArray<BlendingInfo>>::create(patch.count)); |
1838 | 0 | for (auto& array : patch.blending) |
1839 | 0 | array = TRY(FixedArray<BlendingInfo>::create(num_extra_channels + 1)); |
1840 | |
|
1841 | 0 | for (u32 j = 0; j < patch.count; j++) { |
1842 | 0 | if (j == 0) { |
1843 | 0 | auto position = IntPoint { |
1844 | 0 | TRY(decoder.decode_hybrid_uint(stream, 4)), |
1845 | 0 | TRY(decoder.decode_hybrid_uint(stream, 4)), |
1846 | 0 | }; |
1847 | 0 | patch.positions[j] = position; |
1848 | 0 | } else { |
1849 | 0 | auto position = IntPoint { |
1850 | 0 | unpack_signed(TRY(decoder.decode_hybrid_uint(stream, 6))) + patch.positions[j - 1].x(), |
1851 | 0 | unpack_signed(TRY(decoder.decode_hybrid_uint(stream, 6))) + patch.positions[j - 1].y(), |
1852 | 0 | }; |
1853 | 0 | patch.positions[j] = position; |
1854 | 0 | } |
1855 | | |
1856 | | // FIXME: Bail out if this condition is not respected |
1857 | | /* the width x height rectangle with top-left coordinates (x, y) |
1858 | | is fully contained within the frame */ |
1859 | |
|
1860 | 0 | for (u32 k = 0; k < num_extra_channels + 1; k++) { |
1861 | 0 | u8 mode = TRY(decoder.decode_hybrid_uint(stream, 5)); |
1862 | | |
1863 | | /* mode < 8 */ |
1864 | 0 | if (mode >= 8) |
1865 | 0 | return Error::from_string_literal("JPEGXLLoader: Invalid mode when reading patches"); |
1866 | 0 | patch.blending[j][k].mode = static_cast<BlendingInfo::BlendMode>(mode); |
1867 | | // FIXME: The condition is supposed to be "/* there is more than 1 alpha channel */" |
1868 | | // rather than num_extra_channels > 1 |
1869 | 0 | if (mode > 3 && num_extra_channels > 1) { |
1870 | 0 | patch.blending[j][k].alpha_channel = TRY(decoder.decode_hybrid_uint(stream, 8)); |
1871 | | // FIXME: Ensure that condition |
1872 | | /* this is a valid index of an extra channel */ |
1873 | 0 | } |
1874 | 0 | if (mode > 2) |
1875 | 0 | patch.blending[j][k].clamp = TRY(decoder.decode_hybrid_uint(stream, 9)); |
1876 | 0 | } |
1877 | 0 | } |
1878 | | |
1879 | 0 | return patch; |
1880 | 0 | } |
1881 | | |
1882 | | static ErrorOr<FixedArray<Patch>> read_patches(LittleEndianInputBitStream& stream, u32 num_extra_channels) |
1883 | 0 | { |
1884 | 0 | auto decoder = TRY(EntropyDecoder::create(stream, 10)); |
1885 | 0 | u32 const num_patches = TRY(decoder.decode_hybrid_uint(stream, 0)); |
1886 | |
|
1887 | 0 | auto patches = TRY(FixedArray<Patch>::create(num_patches)); |
1888 | 0 | for (auto& patch : patches) |
1889 | 0 | patch = TRY(read_patch(stream, decoder, num_extra_channels)); |
1890 | |
|
1891 | 0 | TRY(decoder.ensure_end_state()); |
1892 | 0 | return patches; |
1893 | 0 | } |
1894 | | /// |
1895 | | |
1896 | | /// I.2.1 - Quantizer |
1897 | | struct Quantizer { |
1898 | | u32 global_scale {}; |
1899 | | u32 quant_lf {}; |
1900 | | }; |
1901 | | |
1902 | | static ErrorOr<Quantizer> read_quantizer(LittleEndianInputBitStream& stream) |
1903 | 0 | { |
1904 | 0 | Quantizer quantizer; |
1905 | 0 | quantizer.global_scale = U32(1 + TRY(stream.read_bits(11)), 2049 + TRY(stream.read_bits(11)), 4097 + TRY(stream.read_bits(12)), 8193 + TRY(stream.read_bits(16))); |
1906 | 0 | quantizer.quant_lf = U32(16, 1 + TRY(stream.read_bits(5)), 1 + TRY(stream.read_bits(8)), 1 + TRY(stream.read_bits(16))); |
1907 | |
|
1908 | 0 | return quantizer; |
1909 | 0 | } |
1910 | | /// |
1911 | | |
1912 | | /// I.2.2 - HF block context decoding |
1913 | | struct HFBlockContext { |
1914 | | Vector<u32> block_ctx_map {}; |
1915 | | Vector<u32> qf_thresholds {}; |
1916 | | Array<Vector<i32>, 3> lf_thresholds {}; |
1917 | | }; |
1918 | | |
1919 | | static ErrorOr<HFBlockContext> read_hf_block_context(LittleEndianInputBitStream& stream) |
1920 | 0 | { |
1921 | 0 | HFBlockContext hf_block_context; |
1922 | |
|
1923 | 0 | if (TRY(stream.read_bit())) { |
1924 | 0 | hf_block_context.block_ctx_map = { 0, 1, 2, 2, 3, 3, 4, 5, 6, 6, 6, 6, 6, |
1925 | 0 | 7, 8, 9, 9, 10, 11, 12, 13, 14, 14, 14, 14, 14, |
1926 | 0 | 7, 8, 9, 9, 10, 11, 12, 13, 14, 14, 14, 14, 14 }; |
1927 | 0 | } else { |
1928 | 0 | Array<u8, 3> nb_lf_thr {}; |
1929 | |
|
1930 | 0 | for (u8 i = 0; i < 3; i++) { |
1931 | 0 | nb_lf_thr[i] = TRY(stream.read_bits(4)); |
1932 | 0 | for (u8 j = 0; j < nb_lf_thr[i]; j++) { |
1933 | 0 | i32 t = unpack_signed(U32(TRY(stream.read_bits(4)), 16 + TRY(stream.read_bits(8)), 272 + TRY(stream.read_bits(16)), 65808 + TRY(stream.read_bits(32)))); |
1934 | 0 | TRY(hf_block_context.lf_thresholds[i].try_append(t)); |
1935 | 0 | } |
1936 | 0 | } |
1937 | |
|
1938 | 0 | u8 nb_qf_thr = TRY(stream.read_bits(4)); |
1939 | 0 | for (u8 i = 0; i < nb_qf_thr; i++) { |
1940 | 0 | u32 t = 1 + U32(TRY(stream.read_bits(2)), 4 + TRY(stream.read_bits(3)), 12 + TRY(stream.read_bits(5)), 44 + TRY(stream.read_bits(8))); |
1941 | 0 | TRY(hf_block_context.qf_thresholds.try_append(t)); |
1942 | 0 | } |
1943 | |
|
1944 | 0 | u32 bsize = 39 * (nb_qf_thr + 1) * (nb_lf_thr[0] + 1) * (nb_lf_thr[1] + 1) * (nb_lf_thr[2] + 1); |
1945 | |
|
1946 | 0 | if (bsize > 39 * 64) |
1947 | 0 | return Error::from_string_literal("JPEGXLLoader: Invalid bsize in read HF Block Context"); |
1948 | | |
1949 | | /* num_dist = bsize <= 39 * 64 and the resulting num_clusters <= 16 */ |
1950 | 0 | auto [clusters, num_clusters] = TRY(read_pre_clustered_distributions(stream, bsize)); |
1951 | 0 | hf_block_context.block_ctx_map = move(clusters); |
1952 | 0 | if (num_clusters > 16) |
1953 | 0 | return Error::from_string_literal("JPEGXLLoader: Invalid num_clusters in HF Block Context"); |
1954 | 0 | } |
1955 | | |
1956 | 0 | return hf_block_context; |
1957 | 0 | } |
1958 | | /// |
1959 | | |
1960 | | /// I.2.3 - LF channel correlation factors |
1961 | | struct LfChannelCorrelation { |
1962 | | u32 colour_factor { 84 }; |
1963 | | f32 base_correlation_x { 0.0 }; |
1964 | | f32 base_correlation_b { 1.0 }; |
1965 | | u8 x_factor_lf { 128 }; |
1966 | | u8 b_factor_lf { 128 }; |
1967 | | }; |
1968 | | |
1969 | | static ErrorOr<LfChannelCorrelation> read_lf_channel_correlation(LittleEndianInputBitStream& stream) |
1970 | 0 | { |
1971 | 0 | LfChannelCorrelation lf_channel_correlation; |
1972 | |
|
1973 | 0 | bool all_default = TRY(stream.read_bit()); |
1974 | 0 | if (!all_default) { |
1975 | 0 | lf_channel_correlation.colour_factor = U32(84, 256, 2 + TRY(stream.read_bits(8)), 258 + TRY(stream.read_bits(16))); |
1976 | 0 | lf_channel_correlation.base_correlation_x = TRY(F16(stream)); |
1977 | 0 | lf_channel_correlation.base_correlation_b = TRY(F16(stream)); |
1978 | 0 | lf_channel_correlation.x_factor_lf = TRY(F16(stream)); |
1979 | 0 | lf_channel_correlation.b_factor_lf = TRY(F16(stream)); |
1980 | 0 | } |
1981 | |
|
1982 | 0 | return lf_channel_correlation; |
1983 | 0 | } |
1984 | | /// |
1985 | | |
1986 | | /// G.1 - LfGlobal |
1987 | | struct LfGlobal { |
1988 | | FixedArray<Patch> patches; |
1989 | | LfChannelDequantization lf_dequant; |
1990 | | Quantizer quantizer; |
1991 | | HFBlockContext hf_block_ctx; |
1992 | | LfChannelCorrelation lf_chan_corr; |
1993 | | GlobalModular gmodular; |
1994 | | }; |
1995 | | |
1996 | | static ErrorOr<LfGlobal> read_lf_global(LittleEndianInputBitStream& stream, |
1997 | | IntSize frame_size, |
1998 | | FrameHeader const& frame_header, |
1999 | | ImageMetadata const& metadata) |
2000 | 0 | { |
2001 | 0 | LfGlobal lf_global; |
2002 | |
|
2003 | 0 | if (frame_header.flags != FrameHeader::Flags::None) { |
2004 | 0 | if (frame_header.flags & FrameHeader::Flags::kPatches) { |
2005 | 0 | lf_global.patches = TRY(read_patches(stream, metadata.num_extra_channels)); |
2006 | 0 | } |
2007 | 0 | if (frame_header.flags & FrameHeader::Flags::kSplines) { |
2008 | 0 | return Error::from_string_literal("JPEGXLLoader: Implement Splines"); |
2009 | 0 | } |
2010 | 0 | if (frame_header.flags & FrameHeader::Flags::kNoise) { |
2011 | 0 | return Error::from_string_literal("JPEGXLLoader: Implement Noise"); |
2012 | 0 | } |
2013 | 0 | } |
2014 | | |
2015 | 0 | lf_global.lf_dequant = TRY(read_lf_channel_dequantization(stream)); |
2016 | |
|
2017 | 0 | if (frame_header.encoding == Encoding::kVarDCT) { |
2018 | 0 | lf_global.quantizer = TRY(read_quantizer(stream)); |
2019 | 0 | lf_global.hf_block_ctx = TRY(read_hf_block_context(stream)); |
2020 | 0 | lf_global.lf_chan_corr = TRY(read_lf_channel_correlation(stream)); |
2021 | 0 | } |
2022 | |
|
2023 | 0 | lf_global.gmodular = TRY(read_global_modular(stream, frame_size, frame_header, metadata)); |
2024 | |
|
2025 | 0 | return lf_global; |
2026 | 0 | } |
2027 | | /// |
2028 | | |
2029 | | /// Helpers to decode groups for the GlobalModular |
2030 | | static IntRect rect_for_group(ChannelInfo const& info, u32 group_dim, u32 group_index) |
2031 | 0 | { |
2032 | 0 | u32 horizontal_group_dim = group_dim >> info.hshift; |
2033 | 0 | u32 vertical_group_dim = group_dim >> info.vshift; |
2034 | |
|
2035 | 0 | IntRect rect(0, 0, horizontal_group_dim, vertical_group_dim); |
2036 | |
|
2037 | 0 | auto nb_groups_per_row = (info.width + horizontal_group_dim - 1) / horizontal_group_dim; |
2038 | 0 | auto group_x = group_index % nb_groups_per_row; |
2039 | 0 | rect.set_x(group_x * horizontal_group_dim); |
2040 | 0 | if (group_x == nb_groups_per_row - 1 && info.width % horizontal_group_dim != 0) { |
2041 | 0 | rect.set_width(info.width % horizontal_group_dim); |
2042 | 0 | } |
2043 | |
|
2044 | 0 | auto nb_groups_per_column = (info.height + vertical_group_dim - 1) / vertical_group_dim; |
2045 | 0 | auto group_y = group_index / nb_groups_per_row; |
2046 | 0 | rect.set_y(group_y * vertical_group_dim); |
2047 | 0 | if (group_y == nb_groups_per_column - 1 && info.height % vertical_group_dim != 0) { |
2048 | 0 | rect.set_height(info.height % vertical_group_dim); |
2049 | 0 | } |
2050 | |
|
2051 | 0 | return rect; |
2052 | 0 | } |
2053 | | |
2054 | | struct GroupOptions { |
2055 | | GlobalModular& global_modular; |
2056 | | FrameHeader const& frame_header; |
2057 | | u32 group_index {}; |
2058 | | u32 stream_index {}; |
2059 | | u32 bit_depth {}; |
2060 | | u32 group_dim {}; |
2061 | | }; |
2062 | | |
2063 | | template<CallableAs<bool, Channel const&> F1, CallableAs<void, ChannelInfo const&> F2> |
2064 | | static ErrorOr<void> read_group_data( |
2065 | | LittleEndianInputBitStream& stream, |
2066 | | GroupOptions&& options, |
2067 | | F1&& match_decode_conditions, |
2068 | | F2&& debug_print) |
2069 | 0 | { |
2070 | 0 | auto& [global_modular, frame_header, group_index, stream_index, bit_depth, group_dim] = options; |
2071 | |
|
2072 | 0 | Vector<ChannelInfo> channels_info; |
2073 | 0 | Vector<Channel&> original_channels; |
2074 | 0 | auto& channels = global_modular.modular_data.channels; |
2075 | 0 | for (auto& channel : channels) { |
2076 | 0 | if (!match_decode_conditions(channel)) |
2077 | 0 | continue; |
2078 | | |
2079 | 0 | auto rect_size = rect_for_group(channel.info(), group_dim, group_index).size(); |
2080 | 0 | TRY(channels_info.try_append({ |
2081 | 0 | .width = static_cast<u32>(rect_size.width()), |
2082 | 0 | .height = static_cast<u32>(rect_size.height()), |
2083 | 0 | .hshift = channel.hshift(), |
2084 | 0 | .vshift = channel.vshift(), |
2085 | 0 | })); |
2086 | 0 | TRY(original_channels.try_append(channel)); |
2087 | 0 | } |
2088 | 0 | if (channels_info.is_empty()) |
2089 | 0 | return {}; |
2090 | | |
2091 | | if constexpr (JPEGXL_DEBUG) |
2092 | | debug_print(original_channels[0].info()); |
2093 | |
|
2094 | 0 | auto decoded = TRY(read_modular_bitstream(stream, |
2095 | 0 | { |
2096 | 0 | .channels_info = channels_info, |
2097 | 0 | .decoder = global_modular.decoder, |
2098 | 0 | .global_tree = global_modular.ma_tree, |
2099 | 0 | .group_dim = group_dim, |
2100 | 0 | .stream_index = stream_index, |
2101 | 0 | .apply_transformations = ModularOptions::ApplyTransformations::Yes, |
2102 | 0 | .bit_depth = bit_depth, |
2103 | 0 | })); |
2104 | | |
2105 | | // The decoded modular group data is then copied into the partially decoded GlobalModular image in the corresponding positions. |
2106 | 0 | for (u32 i = 0; i < original_channels.size(); ++i) { |
2107 | 0 | auto destination = rect_for_group(original_channels[i].info(), group_dim, group_index); |
2108 | 0 | original_channels[i].copy_from(destination, decoded.channels[i]); |
2109 | 0 | } |
2110 | |
|
2111 | 0 | return {}; |
2112 | 0 | } Unexecuted instantiation: JPEGXLLoader.cpp:_ZN3Gfx6JPEGXLL15read_group_dataITkN2AK8Concepts10CallableAsIbRKNS0_6Detail7ChannelIiEEEEZNS0_L13read_lf_groupERNS2_26LittleEndianInputBitStreamEONS0_14LFGroupOptionsEONS0_20LFGroupVarDCTOptionsEE3$_0TkNS4_IvRKNS0_11ChannelInfoEEEZNS0_L13read_lf_groupESB_SD_SF_E3$_1EENS2_7ErrorOrIvNS2_5ErrorEEESB_ONS0_12GroupOptionsEOT_OT0_ Unexecuted instantiation: JPEGXLLoader.cpp:_ZN3Gfx6JPEGXLL15read_group_dataITkN2AK8Concepts10CallableAsIbRKNS0_6Detail7ChannelIiEEEEZNS0_L23read_modular_group_dataERNS2_26LittleEndianInputBitStreamERNS0_16PassGroupOptionsERKNS0_23PassGroupModularOptionsEE3$_0TkNS4_IvRKNS0_11ChannelInfoEEEZNS0_L23read_modular_group_dataESB_SD_SG_E3$_1EENS2_7ErrorOrIvNS2_5ErrorEEESB_ONS0_12GroupOptionsEOT_OT0_ |
2113 | | /// |
2114 | | |
2115 | | /// G.2 - LfGroup |
2116 | | static constexpr i32 DCT_UNINITIALIZED = -2; |
2117 | | static constexpr i32 DCT_COVERED = -1; |
2118 | | |
2119 | | struct VarDCTLfGroup { |
2120 | | Channel x_from_y; |
2121 | | Channel b_from_y; |
2122 | | // dct_select hold DCT information in the top-left corner of every varblock. |
2123 | | // -1 means occupied by a varblock but non top-left. |
2124 | | // -2 is the default value, which shouldn't be found after proper initialization. |
2125 | | Channel dct_select; |
2126 | | Channel hf_mul; |
2127 | | Channel sharpness; |
2128 | | }; |
2129 | | |
2130 | | struct LFGroupOptions { |
2131 | | GlobalModular& global_modular; |
2132 | | FrameHeader const& frame_header; |
2133 | | u32 group_index {}; |
2134 | | u32 stream_index {}; |
2135 | | u32 bit_depth {}; |
2136 | | }; |
2137 | | |
2138 | | // G.2.2 - LF coefficients |
2139 | | static ErrorOr<void> read_lf_coefficients(LittleEndianInputBitStream&, FrameHeader const& frame_header) |
2140 | 0 | { |
2141 | | // "If the kUseLfFrame flag in frame_header is set, this subclause is skipped" |
2142 | 0 | if (frame_header.flags & FrameHeader::Flags::kUseLfFrame) |
2143 | 0 | return {}; |
2144 | | |
2145 | 0 | return Error::from_string_literal("JPEGXLLoader: Implement reading LF coefficients"); |
2146 | 0 | } |
2147 | | |
2148 | | // I.1 - Transform types |
2149 | | enum class TransformType : u8 { |
2150 | | DCT8x8 = 0, |
2151 | | Hornuss = 1, |
2152 | | DCT2x2 = 2, |
2153 | | DCT4x4 = 3, |
2154 | | DCT16x16 = 4, |
2155 | | DCT32x32 = 5, |
2156 | | DCT16x8 = 6, |
2157 | | DCT8x16 = 7, |
2158 | | DCT32x8 = 8, |
2159 | | DCT8x32 = 9, |
2160 | | DCT32x16 = 10, |
2161 | | DCT16x32 = 11, |
2162 | | DCT4x8 = 12, |
2163 | | DCT8x4 = 13, |
2164 | | AFV0 = 14, |
2165 | | AFV1 = 15, |
2166 | | AFV2 = 16, |
2167 | | AFV3 = 17, |
2168 | | DCT64x64 = 18, |
2169 | | DCT64x32 = 19, |
2170 | | DCT32x64 = 20, |
2171 | | DCT128x128 = 21, |
2172 | | DCT128x64 = 22, |
2173 | | DCT64x128 = 23, |
2174 | | DCT256x256 = 24, |
2175 | | DCT256x128 = 25, |
2176 | | DCT128x256 = 26, |
2177 | | }; |
2178 | | |
2179 | | // NOTE: In the spec, DCT matrices use "matrices order" so DCT16x8 is actually |
2180 | | // 16 rows and 8 columns. This function return the size in "image order" |
2181 | | // with columns first and rows in second. |
2182 | | static Size<u32> dct_select_to_dct_size(TransformType t) |
2183 | 0 | { |
2184 | 0 | switch (t) { |
2185 | 0 | case TransformType::DCT8x8: |
2186 | 0 | case TransformType::Hornuss: |
2187 | 0 | case TransformType::DCT2x2: |
2188 | 0 | case TransformType::DCT4x4: |
2189 | 0 | return { 1, 1 }; |
2190 | 0 | case TransformType::DCT16x16: |
2191 | 0 | return { 2, 2 }; |
2192 | 0 | case TransformType::DCT32x32: |
2193 | 0 | return { 4, 4 }; |
2194 | 0 | case TransformType::DCT16x8: |
2195 | 0 | return { 1, 2 }; |
2196 | 0 | case TransformType::DCT8x16: |
2197 | 0 | return { 2, 1 }; |
2198 | 0 | case TransformType::DCT32x8: |
2199 | 0 | return { 1, 4 }; |
2200 | 0 | case TransformType::DCT8x32: |
2201 | 0 | return { 4, 1 }; |
2202 | 0 | case TransformType::DCT32x16: |
2203 | 0 | return { 2, 4 }; |
2204 | 0 | case TransformType::DCT16x32: |
2205 | 0 | return { 4, 2 }; |
2206 | 0 | case TransformType::DCT4x8: |
2207 | 0 | case TransformType::DCT8x4: |
2208 | 0 | return { 1, 1 }; |
2209 | 0 | case TransformType::AFV0: |
2210 | 0 | case TransformType::AFV1: |
2211 | 0 | case TransformType::AFV2: |
2212 | 0 | case TransformType::AFV3: |
2213 | 0 | return { 1, 1 }; |
2214 | 0 | case TransformType::DCT64x64: |
2215 | 0 | return { 8, 8 }; |
2216 | 0 | case TransformType::DCT64x32: |
2217 | 0 | return { 4, 8 }; |
2218 | 0 | case TransformType::DCT32x64: |
2219 | 0 | return { 8, 4 }; |
2220 | 0 | case TransformType::DCT128x128: |
2221 | 0 | return { 16, 16 }; |
2222 | 0 | case TransformType::DCT128x64: |
2223 | 0 | return { 8, 16 }; |
2224 | 0 | case TransformType::DCT64x128: |
2225 | 0 | return { 16, 8 }; |
2226 | 0 | case TransformType::DCT256x256: |
2227 | 0 | return { 32, 32 }; |
2228 | 0 | case TransformType::DCT256x128: |
2229 | 0 | return { 16, 32 }; |
2230 | 0 | case TransformType::DCT128x256: |
2231 | 0 | return { 32, 16 }; |
2232 | 0 | default: |
2233 | 0 | VERIFY_NOT_REACHED(); |
2234 | 0 | } |
2235 | 0 | } |
2236 | | |
2237 | | static Size<u32> dct_select_to_image_size(TransformType t) |
2238 | 0 | { |
2239 | 0 | return dct_select_to_dct_size(t).scaled(8); |
2240 | 0 | } |
2241 | | |
2242 | | // Table I.7 — Order ID for DctSelect values |
2243 | | static u8 dct_select_to_order_id(TransformType t) |
2244 | 0 | { |
2245 | 0 | switch (t) { |
2246 | 0 | case TransformType::DCT8x8: |
2247 | 0 | return 0; |
2248 | 0 | case TransformType::Hornuss: |
2249 | 0 | case TransformType::DCT2x2: |
2250 | 0 | case TransformType::DCT4x4: |
2251 | 0 | case TransformType::DCT4x8: |
2252 | 0 | case TransformType::DCT8x4: |
2253 | 0 | case TransformType::AFV0: |
2254 | 0 | case TransformType::AFV1: |
2255 | 0 | case TransformType::AFV2: |
2256 | 0 | case TransformType::AFV3: |
2257 | 0 | return 1; |
2258 | 0 | case TransformType::DCT16x16: |
2259 | 0 | return 2; |
2260 | 0 | case TransformType::DCT32x32: |
2261 | 0 | return 3; |
2262 | 0 | case TransformType::DCT16x8: |
2263 | 0 | case TransformType::DCT8x16: |
2264 | 0 | return 4; |
2265 | 0 | case TransformType::DCT32x8: |
2266 | 0 | case TransformType::DCT8x32: |
2267 | 0 | return 5; |
2268 | 0 | case TransformType::DCT32x16: |
2269 | 0 | case TransformType::DCT16x32: |
2270 | 0 | return 6; |
2271 | 0 | case TransformType::DCT64x64: |
2272 | 0 | return 7; |
2273 | 0 | case TransformType::DCT64x32: |
2274 | 0 | case TransformType::DCT32x64: |
2275 | 0 | return 8; |
2276 | 0 | case TransformType::DCT128x128: |
2277 | 0 | return 9; |
2278 | 0 | case TransformType::DCT128x64: |
2279 | 0 | case TransformType::DCT64x128: |
2280 | 0 | return 10; |
2281 | 0 | case TransformType::DCT256x256: |
2282 | 0 | return 11; |
2283 | 0 | case TransformType::DCT256x128: |
2284 | 0 | case TransformType::DCT128x256: |
2285 | 0 | return 12; |
2286 | 0 | default: |
2287 | 0 | VERIFY_NOT_REACHED(); |
2288 | 0 | } |
2289 | 0 | } |
2290 | | |
2291 | | struct LFGroupVarDCTOptions { |
2292 | | Vector<Optional<VarDCTLfGroup>>& group_data; |
2293 | | IntSize frame_size; |
2294 | | u32 num_lf_group {}; |
2295 | | }; |
2296 | | |
2297 | | // G.2.4 - HF metadata |
2298 | | static ErrorOr<void> read_hf_metadata(LittleEndianInputBitStream& stream, |
2299 | | LFGroupOptions& options, |
2300 | | LFGroupVarDCTOptions const& var_dct_options, |
2301 | | u32 lf_group_dim) |
2302 | 0 | { |
2303 | |
|
2304 | 0 | auto group_size = rect_for_group(ChannelInfo::from_size(var_dct_options.frame_size), lf_group_dim, options.group_index).size(); |
2305 | | |
2306 | | // "The decoder reads nb_blocks = 1 + u(ceil(log2(ceil(width / 8) * ceil(height / 8))))." |
2307 | 0 | u32 nb_blocks = 1 + TRY(stream.read_bits(ceil(log2(ceil_div(group_size.width(), 8) * ceil_div(group_size.height(), 8))))); |
2308 | | |
2309 | | // "Then, the decoder reads a Modular sub-bitstream as described in Annex H, for an image with four channels." |
2310 | 0 | Vector<ChannelInfo> channels_info; |
2311 | 0 | TRY(channels_info.try_ensure_capacity(4)); |
2312 | | // "the first two channels have ceil(height / 64) rows and ceil(width / 64) columns" |
2313 | 0 | auto color_correlation_channels_size = IntSize { ceil_div(group_size.width(), 64), ceil_div(group_size.height(), 64) }; |
2314 | 0 | channels_info.unchecked_append(ChannelInfo::from_size(color_correlation_channels_size)); |
2315 | 0 | channels_info.unchecked_append(ChannelInfo::from_size(color_correlation_channels_size)); |
2316 | | // "the third channel has two rows and nb_blocks columns" |
2317 | 0 | channels_info.unchecked_append(ChannelInfo::from_size(IntSize(nb_blocks, 2))); |
2318 | | // "and the fourth channel has ceil(height / 8) rows and ceil(width / 8) columns" |
2319 | 0 | channels_info.unchecked_append(ChannelInfo::from_size({ ceil_div(group_size.width(), 8), ceil_div(group_size.height(), 8) })); |
2320 | | |
2321 | | // "The stream index is defined as follows: |
2322 | | // - for ModularLfGroup: 1 + num_lf_groups + LF group index; |
2323 | | // - for HFMetadata: 1 + 2 * num_lf_groups + LF group index;" |
2324 | | // We pass ModularLfGroup's stream index in LFGroupOptions, so we |
2325 | | // just need to add `num_lf_groups` here. |
2326 | 0 | auto stream_index = options.stream_index + var_dct_options.num_lf_group; |
2327 | |
|
2328 | 0 | auto decoded_channels = TRY(read_modular_bitstream(stream, |
2329 | 0 | { |
2330 | 0 | .channels_info = channels_info, |
2331 | 0 | .decoder = options.global_modular.decoder, |
2332 | 0 | .global_tree = options.global_modular.ma_tree, |
2333 | 0 | .group_dim = lf_group_dim, |
2334 | 0 | .stream_index = stream_index, |
2335 | 0 | .apply_transformations = ModularOptions::ApplyTransformations::Yes, |
2336 | 0 | .bit_depth = options.bit_depth, |
2337 | 0 | })) |
2338 | 0 | .channels; |
2339 | | |
2340 | | // "The DctSelect and HfMul fields are derived from the first and second rows of BlockInfo. |
2341 | | // These two fields have ceil(height / 8) rows and ceil(width / 8) columns." |
2342 | 0 | auto derived_size = IntSize(ceil_div(group_size.width(), 8), ceil_div(group_size.height(), 8)); |
2343 | 0 | auto dct_select = TRY(Channel::create(ChannelInfo::from_size(derived_size))); |
2344 | 0 | auto hf_mul = TRY(Channel::create(ChannelInfo::from_size(derived_size))); |
2345 | |
|
2346 | 0 | dct_select.fill(DCT_UNINITIALIZED); |
2347 | |
|
2348 | 0 | i32 x = 0; |
2349 | 0 | i32 y = 0; |
2350 | 0 | auto update_next_valid_position = [&]() { |
2351 | | // "This position is the earliest block in raster order that is not already covered by |
2352 | | // other varblocks. The positioned varblock is completely contained in the current LF |
2353 | | // group, does not cross group boundaries, and also does not overlap with |
2354 | | // already-positioned varblocks." |
2355 | | |
2356 | | // FIXME: There has to be a smarter way of doing this. |
2357 | 0 | while (dct_select.get(x, y) != DCT_UNINITIALIZED) { |
2358 | 0 | if (x == derived_size.width() - 1) { |
2359 | 0 | x = 0; |
2360 | 0 | y += 1; |
2361 | 0 | continue; |
2362 | 0 | } |
2363 | 0 | ++x; |
2364 | 0 | } |
2365 | 0 | }; |
2366 | | |
2367 | | // "They are reconstructed by iterating over the columns of BlockInfo to obtain a varblock |
2368 | | // transform type type (the sample at the first row) and a quantization multiplier mul (the |
2369 | | // sample at the second row)." |
2370 | 0 | auto const& block_info = decoded_channels[2]; |
2371 | 0 | for (u32 column = 0; column < nb_blocks; ++column) { |
2372 | 0 | auto type = block_info.get(column, 0); |
2373 | 0 | if (type > 26) |
2374 | 0 | return Error::from_string_literal("JPEGXLLoader: Invalid DctSelect value"); |
2375 | | |
2376 | 0 | auto mul = block_info.get(column, 1); |
2377 | | |
2378 | | // "The type is a DctSelect sample and is stored at the coordinates of the top-left |
2379 | | // 8 × 8 rectangle of the varblock." |
2380 | 0 | dct_select.set(x, y, type); |
2381 | | // "The HfMul sample is stored at the same position and gets the value 1 + mul." |
2382 | 0 | hf_mul.set(x, y, 1 + mul); |
2383 | | |
2384 | | // We fill the whole surface of the varblock as a way to check that |
2385 | | // varblocks don't overlap. |
2386 | 0 | auto dct_size = dct_select_to_dct_size(static_cast<TransformType>(type)); |
2387 | 0 | for (u8 y_offset = 0; y_offset < dct_size.height(); ++y_offset) { |
2388 | 0 | for (u8 x_offset = 0; x_offset < dct_size.width(); ++x_offset) { |
2389 | 0 | if (y_offset == 0 && x_offset == 0) |
2390 | 0 | continue; |
2391 | 0 | if (dct_select.get(x + x_offset, y + y_offset) != DCT_UNINITIALIZED) |
2392 | 0 | return Error::from_string_literal("JPEGXLLoader: Invalid varblocks pattern"); |
2393 | 0 | dct_select.set(x + x_offset, y + y_offset, DCT_COVERED); |
2394 | 0 | } |
2395 | 0 | } |
2396 | 0 | if (column != nb_blocks - 1) |
2397 | 0 | update_next_valid_position(); |
2398 | 0 | } |
2399 | | |
2400 | | // FIXME: Ensure that dct_select contains no DCT_UNINITIALIZED. |
2401 | | |
2402 | 0 | var_dct_options.group_data[options.group_index] = VarDCTLfGroup { |
2403 | 0 | .x_from_y = move(decoded_channels[0]), |
2404 | 0 | .b_from_y = move(decoded_channels[1]), |
2405 | 0 | .dct_select = move(dct_select), |
2406 | 0 | .hf_mul = move(hf_mul), |
2407 | 0 | .sharpness = move(decoded_channels[2]), |
2408 | 0 | }; |
2409 | 0 | return {}; |
2410 | 0 | } |
2411 | | |
2412 | | static ErrorOr<void> read_lf_group(LittleEndianInputBitStream& stream, |
2413 | | LFGroupOptions&& options, |
2414 | | LFGroupVarDCTOptions&& var_dct_options) |
2415 | 0 | { |
2416 | 0 | auto const& [global_modular, frame_header, group_index, stream_index, bit_depth] = options; |
2417 | |
|
2418 | 0 | if (options.frame_header.encoding == Encoding::kVarDCT) { |
2419 | 0 | if (var_dct_options.group_data.is_empty()) |
2420 | 0 | TRY(var_dct_options.group_data.try_resize(var_dct_options.num_lf_group)); |
2421 | 0 | } |
2422 | | |
2423 | | // LF coefficients |
2424 | 0 | if (frame_header.encoding == Encoding::kVarDCT) |
2425 | 0 | TRY(read_lf_coefficients(stream, frame_header)); |
2426 | | |
2427 | | // ModularLfGroup |
2428 | 0 | u32 lf_group_dim = frame_header.group_dim() * 8; |
2429 | |
|
2430 | 0 | auto match_decoding_conditions = [](Channel const& channel) { |
2431 | 0 | if (channel.decoded()) |
2432 | 0 | return false; |
2433 | 0 | if (channel.hshift() < 3 || channel.vshift() < 3) |
2434 | 0 | return false; |
2435 | 0 | return true; |
2436 | 0 | }; |
2437 | 0 | TRY(read_group_data( |
2438 | 0 | stream, |
2439 | 0 | GroupOptions { |
2440 | 0 | .global_modular = global_modular, |
2441 | 0 | .frame_header = frame_header, |
2442 | 0 | .group_index = group_index, |
2443 | 0 | .stream_index = stream_index, |
2444 | 0 | .bit_depth = bit_depth, |
2445 | 0 | .group_dim = lf_group_dim }, |
2446 | 0 | move(match_decoding_conditions), |
2447 | 0 | [&](auto const& first_channel) { dbgln("Decoding LFGroup {} for rectangle {}", group_index, rect_for_group(first_channel, lf_group_dim, group_index)); })); |
2448 | | |
2449 | | // HF metadata |
2450 | 0 | if (options.frame_header.encoding == Encoding::kVarDCT) |
2451 | 0 | TRY(read_hf_metadata(stream, options, var_dct_options, lf_group_dim)); |
2452 | |
|
2453 | 0 | return {}; |
2454 | 0 | } |
2455 | | /// |
2456 | | |
2457 | | /// G.3 - HfGlobal |
2458 | | struct HfGlobalPassMetadata { |
2459 | | // I.3.1 - HF coefficient order |
2460 | | // 13 Order ID and 3 color component. |
2461 | | // These spans refer to either the static, default values or |
2462 | | // a Vector of backing_data. |
2463 | | DCTOrderDescription order; |
2464 | | Vector<Vector<Point<u32>>> backing_data; |
2465 | | |
2466 | | // I.3.3 - HF coefficient histograms |
2467 | | u32 nb_block_ctx {}; |
2468 | | EntropyDecoder decoder; |
2469 | | }; |
2470 | | |
2471 | | struct HfGlobal { |
2472 | | // Dequantization matrices. |
2473 | | u32 num_hf_presets {}; |
2474 | | FixedArray<HfGlobalPassMetadata> hf_passes; |
2475 | | }; |
2476 | | |
2477 | | // I.2.4 - Dequantization matrices |
2478 | | static ErrorOr<void> read_quantization_matrices(LittleEndianInputBitStream& stream) |
2479 | 0 | { |
2480 | | // "First, the decoder reads a Bool(). If this is true, all matrices have their default encoding." |
2481 | 0 | bool is_default = TRY(stream.read_bit()); |
2482 | |
|
2483 | 0 | if (!is_default) |
2484 | 0 | return Error::from_string_literal("JPEGXLLoader: Implement reading quantization matrices"); |
2485 | | |
2486 | 0 | return {}; |
2487 | 0 | } |
2488 | | |
2489 | | // I.3 - HfPass |
2490 | | static ErrorOr<void> read_hf_passes(LittleEndianInputBitStream& stream, LfGlobal const& lf_global, HfGlobal& hf_global) |
2491 | 0 | { |
2492 | | // I.3.1 - HF coefficient order |
2493 | | |
2494 | | // "The decoder first reads used_orders as U32(0x5F, 0x13, 0x00, u(13))." |
2495 | 0 | u32 used_orders = U32(0x5F, 0x13, 0x00, TRY(stream.read_bits(13))); |
2496 | | |
2497 | | // "If used_orders != 0, it reads 8 pre-clustered distributions as specified in C.1." |
2498 | 0 | Optional<EntropyDecoder> decoder; |
2499 | 0 | if (used_orders != 0) |
2500 | 0 | decoder = TRY(EntropyDecoder::create(stream, 8)); |
2501 | | |
2502 | | // "It then reads HF coefficient orders order[p][b][c] as specified by the code below, |
2503 | | // where p is the index of the current pass, b is an Order ID (see Table I.7), c is a |
2504 | | // component index, and natural_coeff_order[b] is the natural coefficient order for Order |
2505 | | // ID b, as specified in I.3.2." |
2506 | 0 | auto const& natural_coeff_order = *TRY(DCTNaturalOrder::the()); |
2507 | 0 | for (auto& pass_data : hf_global.hf_passes) { |
2508 | 0 | for (u8 b = 0; b < 13; b++) { |
2509 | 0 | for (u8 c = 0; c < 3; c++) { |
2510 | 0 | if ((used_orders & (1 << b)) != 0) { |
2511 | | // "DecodePermutation(b) is defined as follows. The decoder reads a permutation |
2512 | | // nat_ord_perm from a single stream (shared during the above loop) as specified |
2513 | | // in F.3.2, where size is the number of coefficients covered by transforms with |
2514 | | // Order ID b (so size == natural_coeff_order[b].size()) and skip = size / 64. |
2515 | 0 | auto size = natural_coeff_order[b][c].size(); |
2516 | 0 | auto nat_ord_perm = TRY(decode_permutations(stream, *decoder, size, size / 64)); |
2517 | |
|
2518 | 0 | Vector<Point<u32>> local_order; |
2519 | 0 | TRY(local_order.try_resize(size)); |
2520 | 0 | pass_data.order[b][c] = local_order.span(); |
2521 | 0 | TRY(pass_data.backing_data.try_append(move(local_order))); |
2522 | |
|
2523 | 0 | for (u32 i = 0; i < nat_ord_perm.size(); ++i) |
2524 | 0 | pass_data.order[b][c][i] = natural_coeff_order[b][c][nat_ord_perm[i]]; |
2525 | 0 | } else { |
2526 | 0 | pass_data.order[b][c] = natural_coeff_order[b][c]; |
2527 | 0 | } |
2528 | 0 | } |
2529 | 0 | } |
2530 | | |
2531 | | // I.3.3 - HF coefficient histograms |
2532 | | // "Let nb_block_ctx be equal to max(block_ctx_map) + 1." |
2533 | 0 | auto max = lf_global.hf_block_ctx.block_ctx_map[0]; |
2534 | 0 | for (auto v : lf_global.hf_block_ctx.block_ctx_map) { |
2535 | 0 | if (v > max) |
2536 | 0 | max = v; |
2537 | 0 | } |
2538 | 0 | pass_data.nb_block_ctx = max + 1; |
2539 | | |
2540 | | // "The decoder reads a histogram with 495 * num_hf_presets * nb_block_ctx |
2541 | | // pre-clustered distributions D from the codestream as specified in C.1." |
2542 | 0 | auto distributions = 495 * hf_global.num_hf_presets * pass_data.nb_block_ctx; |
2543 | 0 | pass_data.decoder = TRY(EntropyDecoder::create(stream, distributions)); |
2544 | 0 | } |
2545 | |
|
2546 | 0 | if (decoder.has_value()) |
2547 | 0 | TRY(decoder->ensure_end_state()); |
2548 | |
|
2549 | 0 | return {}; |
2550 | 0 | } |
2551 | | |
2552 | | static ErrorOr<HfGlobal> read_hf_global(LittleEndianInputBitStream& stream, LfGlobal const& lf_global, u32 num_groups, u32 num_passes) |
2553 | 0 | { |
2554 | 0 | HfGlobal hf_global; |
2555 | |
|
2556 | 0 | TRY(read_quantization_matrices(stream)); |
2557 | | |
2558 | | // I.2.6 - Number of HF decoding presets |
2559 | | // "The decoder reads num_hf_presets as u(ceil(log2(num_groups))) + 1." |
2560 | 0 | hf_global.num_hf_presets = TRY(stream.read_bits(ceil(log2(num_groups)))) + 1; |
2561 | |
|
2562 | 0 | hf_global.hf_passes = TRY(FixedArray<HfGlobalPassMetadata>::create(num_passes)); |
2563 | 0 | TRY(read_hf_passes(stream, lf_global, hf_global)); |
2564 | |
|
2565 | 0 | return hf_global; |
2566 | 0 | } |
2567 | | /// |
2568 | | |
2569 | | /// G.3.2 - PassGroup |
2570 | | struct PassGroupOptions { |
2571 | | GlobalModular& global_modular; |
2572 | | FrameHeader const& frame_header; |
2573 | | u32 group_index; |
2574 | | u32 pass_index; |
2575 | | u32 stream_index; |
2576 | | }; |
2577 | | |
2578 | | struct PassGroupModularOptions { |
2579 | | u32 bit_depth {}; |
2580 | | }; |
2581 | | |
2582 | | // G.4.2 - Modular group data |
2583 | | static ErrorOr<void> read_modular_group_data(LittleEndianInputBitStream& stream, |
2584 | | PassGroupOptions& options, |
2585 | | PassGroupModularOptions const& modular_options) |
2586 | 0 | { |
2587 | 0 | auto& [global_modular, frame_header, group_index, pass_index, stream_index] = options; |
2588 | |
|
2589 | 0 | i8 max_shift = 3; |
2590 | 0 | i8 min_shift = 0; |
2591 | |
|
2592 | 0 | if (pass_index != 0) |
2593 | 0 | return Error::from_string_literal("JPEGXLLoader: Subsequent passes are not supported yet"); |
2594 | | |
2595 | | // for every remaining channel in the partially decoded GlobalModular image (i.e. it is not a meta-channel, |
2596 | | // the channel dimensions exceed group_dim × group_dim, and hshift < 3 or vshift < 3, and the channel has |
2597 | | // not been already decoded in a previous pass) |
2598 | 0 | auto match_decoding_conditions = [&](auto const& channel) { |
2599 | 0 | if (channel.decoded()) |
2600 | 0 | return false; |
2601 | 0 | auto channel_min_shift = min(channel.hshift(), channel.vshift()); |
2602 | 0 | if (channel_min_shift < min_shift || channel_min_shift >= max_shift) |
2603 | 0 | return false; |
2604 | 0 | return true; |
2605 | 0 | }; |
2606 | |
|
2607 | 0 | TRY(read_group_data(stream, |
2608 | 0 | { |
2609 | 0 | .global_modular = global_modular, |
2610 | 0 | .frame_header = frame_header, |
2611 | 0 | .group_index = group_index, |
2612 | 0 | .stream_index = stream_index, |
2613 | 0 | .bit_depth = modular_options.bit_depth, |
2614 | 0 | .group_dim = frame_header.group_dim(), |
2615 | 0 | }, |
2616 | 0 | move(match_decoding_conditions), |
2617 | 0 | [&](auto const& first_channel) { dbgln_if(JPEGXL_DEBUG, "Decoding pass {} for rectangle {}", options.pass_index, rect_for_group(first_channel, frame_header.group_dim(), group_index)); })); |
2618 | |
|
2619 | 0 | return {}; |
2620 | 0 | } |
2621 | | |
2622 | | struct PassGroupVarDCTOptions { |
2623 | | LfGlobal const& lf_global; |
2624 | | Vector<Optional<VarDCTLfGroup>> const& lf_groups; |
2625 | | HfGlobal& hf_global; |
2626 | | }; |
2627 | | |
2628 | | static constexpr Array CoeffFreqContext = to_array<u8>({ 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, |
2629 | | 15, 15, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, |
2630 | | 23, 23, 23, 23, 24, 24, 24, 24, 25, 25, 25, 25, 26, 26, 26, 26, |
2631 | | 27, 27, 27, 27, 28, 28, 28, 28, 29, 29, 29, 29, 30, 30, 30, 30 }); |
2632 | | |
2633 | | static constexpr Array CoeffNumNonzeroContext = to_array<u8>({ 0, 0, 31, 62, 62, 93, 93, 93, 93, 123, 123, 123, 123, |
2634 | | 152, 152, 152, 152, 152, 152, 152, 152, 180, 180, 180, 180, 180, |
2635 | | 180, 180, 180, 180, 180, 180, 180, 206, 206, 206, 206, 206, 206, |
2636 | | 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, |
2637 | | 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206 }); |
2638 | | |
2639 | | // I.4 - Decoding of quantized HF coefficients |
2640 | | static ErrorOr<void> read_hf_coefficients(LittleEndianInputBitStream& stream, |
2641 | | PassGroupOptions const& options, |
2642 | | PassGroupVarDCTOptions&& var_dct_options) |
2643 | 0 | { |
2644 | 0 | auto& hf_global = var_dct_options.hf_global; |
2645 | 0 | auto& hf_pass = hf_global.hf_passes[options.pass_index]; |
2646 | 0 | auto const& hf_group = *var_dct_options.lf_groups[options.group_index]; |
2647 | |
|
2648 | 0 | auto nb_block_ctx = hf_pass.nb_block_ctx; |
2649 | |
|
2650 | 0 | auto hfp = TRY(stream.read_bits(ceil(log2(hf_global.num_hf_presets)))); |
2651 | 0 | u32 clusters_size = 495 * nb_block_ctx; |
2652 | 0 | u32 offset = clusters_size * hfp; |
2653 | |
|
2654 | 0 | Optional<ScopeGuard<Function<void()>>> restore_histogram; |
2655 | 0 | TRY(hf_pass.decoder.temporarily_restrict_histogram(restore_histogram, offset, clusters_size)); |
2656 | 0 | auto& decoder = hf_pass.decoder; |
2657 | | |
2658 | | // "After selecting the histogram and coefficient order, the decoder reads symbols |
2659 | | // from an entropy-coded stream, as specified in C.3.3." |
2660 | | |
2661 | | // "The decoder proceeds by decoding varblocks in raster order;" |
2662 | 0 | auto const& varblock_description = hf_group.dct_select; |
2663 | 0 | auto const& order = hf_pass.order; |
2664 | |
|
2665 | 0 | Array<Channel, 3> non_zeros_channels = { |
2666 | 0 | TRY(Channel::create(varblock_description.info())), |
2667 | 0 | TRY(Channel::create(varblock_description.info())), |
2668 | 0 | TRY(Channel::create(varblock_description.info())) |
2669 | 0 | }; |
2670 | | |
2671 | | // "If the kUseLfFrame flag in frame_header is set [...] the quantized LF coefficients LfQuant are all set to −∞, that is, |
2672 | | // regardless of lf_thresholds, the value of lf_idx at the end of the function BlockContext() (I.4) is always equal to zero." |
2673 | 0 | Array<i32, 3> qdc; |
2674 | 0 | qdc.fill(NumericLimits<i32>::min()); |
2675 | |
|
2676 | 0 | for (u32 y = 0; y < varblock_description.height(); ++y) { |
2677 | 0 | for (u32 x = 0; x < varblock_description.width(); ++x) { |
2678 | 0 | auto dct_type = varblock_description.get(x, y); |
2679 | 0 | if (dct_type == DCT_UNINITIALIZED || dct_type == DCT_COVERED) |
2680 | 0 | continue; |
2681 | | // "For each varblock of size W × H," |
2682 | 0 | auto transform_type = static_cast<TransformType>(dct_type); |
2683 | 0 | auto varblock_size = dct_select_to_image_size(transform_type); |
2684 | 0 | auto W = varblock_size.width(); |
2685 | 0 | auto H = varblock_size.height(); |
2686 | | // "covering num_blocks = (W / 8) * (H / 8) blocks," |
2687 | 0 | u32 num_blocks = (W / 8) * (H / 8); |
2688 | | |
2689 | | // "s is the Order ID (see Table I.7) of the DctSelect value" |
2690 | 0 | auto s = dct_select_to_order_id(transform_type); |
2691 | | // "qf is the HfMul value for the current varblock" |
2692 | 0 | u32 qf = hf_group.hf_mul.get(x, y); |
2693 | | |
2694 | | // FIXME: Implement this for in-frame LF coefficients. |
2695 | | // "qdc[3] are the quantized LF values of LfQuant (G.2.2) corresponding to |
2696 | | // (the top-left 8×8 block within) the current varblock (taking into account jpeg_upsampling if needed)." |
2697 | | |
2698 | | // "The lists of thresholds qf_thresholds and lf_thresholds[3], and block_ctx_map are as decoded in LfGlobal" |
2699 | 0 | auto const& qf_thresholds = var_dct_options.lf_global.hf_block_ctx.qf_thresholds; |
2700 | 0 | auto const& lf_thresholds = var_dct_options.lf_global.hf_block_ctx.lf_thresholds; |
2701 | 0 | auto const& block_ctx_map = var_dct_options.lf_global.hf_block_ctx.block_ctx_map; |
2702 | | |
2703 | | // "for each varblock it reads channels Y, X, then B;" |
2704 | | // "where c is the current channel (with 0=X, 1=Y, 2=B)" - from the second paragraph of I.4 |
2705 | 0 | for (u8 c : { 1, 0, 2 }) { |
2706 | 0 | auto BlockContext = [&]() -> u32 { |
2707 | 0 | u32 idx = (c < 2 ? c ^ 1 : 2) * 13 + s; |
2708 | 0 | idx *= (qf_thresholds.size() + 1); |
2709 | 0 | for (auto t : qf_thresholds) |
2710 | 0 | if (qf > t) |
2711 | 0 | idx++; |
2712 | 0 | for (u8 i = 0; i < 3; i++) |
2713 | 0 | idx *= (lf_thresholds[i].size() + 1); |
2714 | 0 | u32 lf_idx = 0; |
2715 | 0 | for (auto t : lf_thresholds[0]) |
2716 | 0 | if (qdc[0] > t) |
2717 | 0 | lf_idx++; |
2718 | 0 | lf_idx *= (lf_thresholds[2].size() + 1); |
2719 | 0 | for (auto t : lf_thresholds[2]) |
2720 | 0 | if (qdc[2] > t) |
2721 | 0 | lf_idx++; |
2722 | 0 | lf_idx *= (lf_thresholds[1].size() + 1); |
2723 | 0 | for (auto t : lf_thresholds[1]) |
2724 | 0 | if (qdc[1] > t) |
2725 | 0 | lf_idx++; |
2726 | 0 | return block_ctx_map[idx + lf_idx]; |
2727 | 0 | }; |
2728 | |
|
2729 | 0 | auto NonZerosContext = [&](u32 predicted) -> u32 { |
2730 | 0 | if (predicted > 64) |
2731 | 0 | predicted = 64; |
2732 | 0 | if (predicted < 8) |
2733 | 0 | return BlockContext() + nb_block_ctx * predicted; |
2734 | 0 | return BlockContext() + nb_block_ctx * (4 + predicted / 2); |
2735 | 0 | }; |
2736 | |
|
2737 | 0 | auto NonZeros = [&](u32 x, u32 y) -> i32& { |
2738 | 0 | return non_zeros_channels[c].get(x, y); |
2739 | 0 | }; |
2740 | |
|
2741 | 0 | auto PredictedNonZeros = [&](u32 x, u32 y) -> u32 { |
2742 | 0 | if (x == 0 and y == 0) |
2743 | 0 | return 32; |
2744 | 0 | if (x == 0) |
2745 | 0 | return NonZeros(x, y - 1); |
2746 | 0 | if (y == 0) |
2747 | 0 | return NonZeros(x - 1, y); |
2748 | 0 | return (NonZeros(x, y - 1) + NonZeros(x - 1, y) + 1) >> 1; |
2749 | 0 | }; |
2750 | | |
2751 | | // "the decoder reads an integer non_zeros using |
2752 | | // DecodeHybridVarLenUint(NonZerosContext(PredictedNonZeros(x, y)) + offset)." |
2753 | 0 | u32 context = NonZerosContext(PredictedNonZeros(x, y)); |
2754 | 0 | auto non_zeros = TRY(decoder.decode_hybrid_uint(stream, context)); |
2755 | | |
2756 | | // The decoder then sets the NonZeros(x, y) value for each block in the |
2757 | | // current varblock as follows: for each i in [0, W / 8) and j in [0, H / 8), |
2758 | | // NonZeros(x + i, y + j) is set to (non_zeros + num_blocks − 1) Idiv num_blocks. |
2759 | 0 | for (u32 j = 0; j < H / 8; ++j) { |
2760 | 0 | for (u32 i = 0; i < W / 8; ++i) |
2761 | 0 | NonZeros(x + i, y + j) = (non_zeros + num_blocks - 1) / num_blocks; |
2762 | 0 | } |
2763 | | |
2764 | | // "If non_zeros reaches 0, the decoder stops decoding further coefficients for the current block." |
2765 | 0 | if (non_zeros == 0) |
2766 | 0 | continue; |
2767 | | |
2768 | 0 | auto CoefficientContext = [&](u32 k, u32 non_zeros, u32 num_blocks, u32 prev) -> u32 { |
2769 | 0 | non_zeros = (non_zeros + num_blocks - 1) / num_blocks; |
2770 | 0 | k = k / num_blocks; |
2771 | 0 | return (CoeffNumNonzeroContext[non_zeros] + CoeffFreqContext[k]) * 2 + prev + BlockContext() * 458 + 37 * nb_block_ctx; |
2772 | 0 | }; |
2773 | | |
2774 | | // "Let size = W * H." |
2775 | 0 | auto size = W * H; |
2776 | | // "For k in the range [num_blocks, size)" |
2777 | 0 | u32 last_ucoeff {}; |
2778 | 0 | for (u32 k = num_blocks; k < size; ++k) { |
2779 | | // "the decoder reads an integer ucoeff from the codestream, using |
2780 | | // DecodeHybridVarLenUint(CoefficientContext(k, non_zeros, num_blocks, size, prev) + offset), |
2781 | | // where prev is computed as specified in the following code:" |
2782 | 0 | auto prev = [&]() -> u32 { |
2783 | 0 | if (k == num_blocks) { |
2784 | 0 | if (non_zeros > size / 16) |
2785 | 0 | return 0; |
2786 | 0 | else |
2787 | 0 | return 1; |
2788 | 0 | } else { |
2789 | 0 | if (last_ucoeff == 0) |
2790 | 0 | return 0; |
2791 | 0 | else |
2792 | 0 | return 1; |
2793 | 0 | } |
2794 | 0 | }(); |
2795 | |
|
2796 | 0 | auto ucoeff = TRY(decoder.decode_hybrid_uint(stream, CoefficientContext(k, non_zeros, num_blocks, prev) + offset)); |
2797 | 0 | last_ucoeff = ucoeff; |
2798 | | |
2799 | | // "The decoder then sets the quantized HF coefficient in the position corresponding to index |
2800 | | // order[p][s][c][k] to UnpackSigned(ucoeff), where p is the index of the current pass and s |
2801 | | // and c are the Order ID and current channel index as above." |
2802 | 0 | auto destination = order[s][c][k]; |
2803 | | // FIXME: Actually do something with the decoded data. |
2804 | 0 | (void)destination; |
2805 | | |
2806 | | // "If ucoeff != 0, the decoder decreases non_zeros by 1." |
2807 | 0 | if (ucoeff != 0) |
2808 | 0 | non_zeros -= 1; |
2809 | | // "If non_zeros reaches 0, the decoder stops decoding further coefficients for the current block." |
2810 | 0 | if (non_zeros == 0) |
2811 | 0 | break; |
2812 | 0 | } |
2813 | 0 | } |
2814 | 0 | } |
2815 | 0 | } |
2816 | | |
2817 | 0 | TRY(decoder.ensure_end_state()); |
2818 | |
|
2819 | 0 | return {}; |
2820 | 0 | } |
2821 | | |
2822 | | // G.4.1 - General |
2823 | | static ErrorOr<void> read_pass_group(LittleEndianInputBitStream& stream, |
2824 | | PassGroupOptions&& options, |
2825 | | PassGroupModularOptions&& modular_options, |
2826 | | PassGroupVarDCTOptions&& var_dct_options) |
2827 | 0 | { |
2828 | 0 | if (options.frame_header.encoding == Encoding::kVarDCT) |
2829 | 0 | TRY(read_hf_coefficients(stream, options, move(var_dct_options))); |
2830 | |
|
2831 | 0 | TRY(read_modular_group_data(stream, options, modular_options)); |
2832 | |
|
2833 | 0 | return {}; |
2834 | 0 | } |
2835 | | /// |
2836 | | |
2837 | | /// Table F.1 — Frame bundle |
2838 | | struct Frame { |
2839 | | FrameHeader frame_header; |
2840 | | TOC toc; |
2841 | | LfGlobal lf_global; |
2842 | | Vector<Optional<VarDCTLfGroup>> lf_groups; |
2843 | | HfGlobal hf_global; |
2844 | | |
2845 | | u64 width {}; |
2846 | | u64 height {}; |
2847 | | |
2848 | | u32 num_groups {}; |
2849 | | u32 num_lf_groups {}; |
2850 | | |
2851 | | Optional<Image> image {}; |
2852 | | }; |
2853 | | |
2854 | | class AutoDepletingConstrainedStream : public ConstrainedStream { |
2855 | | public: |
2856 | | AutoDepletingConstrainedStream(MaybeOwned<Stream> stream, u64 limit) |
2857 | 0 | : ConstrainedStream(move(stream), limit) |
2858 | 0 | { |
2859 | 0 | } |
2860 | | |
2861 | | ~AutoDepletingConstrainedStream() |
2862 | 0 | { |
2863 | 0 | dbgln_if(JPEGXL_DEBUG, "Discarding {} remaining bytes", remaining()); |
2864 | 0 | if (discard(remaining()).is_error()) |
2865 | 0 | dbgln("JPEGXLLoader: Corrupted stream, reached EOF"); |
2866 | 0 | } |
2867 | | }; |
2868 | | |
2869 | | static ErrorOr<Frame> read_frame(LittleEndianInputBitStream& stream, |
2870 | | SizeHeader const& size_header, |
2871 | | ImageMetadata const& metadata) |
2872 | 0 | { |
2873 | | // F.1 - General |
2874 | | // Each Frame is byte-aligned by invoking ZeroPadToByte() (B.2.7) |
2875 | 0 | stream.align_to_byte_boundary(); |
2876 | |
|
2877 | 0 | Frame frame; |
2878 | |
|
2879 | 0 | frame.frame_header = TRY(read_frame_header(stream, size_header, metadata)); |
2880 | |
|
2881 | 0 | if (!frame.frame_header.have_crop) { |
2882 | 0 | frame.width = size_header.width; |
2883 | 0 | frame.height = size_header.height; |
2884 | 0 | } else { |
2885 | 0 | frame.width = frame.frame_header.width; |
2886 | 0 | frame.height = frame.frame_header.height; |
2887 | 0 | } |
2888 | |
|
2889 | 0 | if (frame.frame_header.upsampling > 1) { |
2890 | 0 | frame.width = ceil_div(frame.width, frame.frame_header.upsampling); |
2891 | 0 | frame.height = ceil_div(frame.height, frame.frame_header.upsampling); |
2892 | 0 | } |
2893 | | |
2894 | | // "If lf_level > 0 (which is also a field in frame_header), then |
2895 | | // width = ceil(width / (1 << (3 * lf_level))) and height = ceil(height / (1 << (3 * lf_level)))." |
2896 | 0 | if (frame.frame_header.lf_level > 0) { |
2897 | 0 | frame.width = ceil_div(frame.width, 1u << (3 * frame.frame_header.lf_level)); |
2898 | 0 | frame.height = ceil_div(frame.height, 1u << (3 * frame.frame_header.lf_level)); |
2899 | 0 | } |
2900 | |
|
2901 | 0 | dbgln_if(JPEGXL_DEBUG, "Frame{}: {}x{} {} - {} - flags({}){}"sv, |
2902 | 0 | frame.frame_header.name.is_empty() ? ""sv : MUST(String::formatted(" \"{}\"", frame.frame_header.name)), |
2903 | 0 | frame.width, frame.height, |
2904 | 0 | frame.frame_header.encoding, |
2905 | 0 | frame.frame_header.frame_type, |
2906 | 0 | to_underlying(frame.frame_header.flags), |
2907 | 0 | frame.frame_header.is_last ? " - is_last"sv : ""sv); |
2908 | |
|
2909 | 0 | auto const group_dim = frame.frame_header.group_dim(); |
2910 | 0 | auto const frame_width = static_cast<double>(frame.width); |
2911 | 0 | auto const frame_height = static_cast<double>(frame.height); |
2912 | 0 | frame.num_groups = ceil(frame_width / group_dim) * ceil(frame_height / group_dim); |
2913 | 0 | frame.num_lf_groups = ceil(frame_width / (group_dim * 8)) * ceil(frame_height / (group_dim * 8)); |
2914 | |
|
2915 | 0 | frame.toc = TRY(read_toc(stream, frame.frame_header, frame.num_groups, frame.num_lf_groups)); |
2916 | |
|
2917 | | if constexpr (JPEGXL_DEBUG) { |
2918 | | dbgln("TOC: index | size | offset"); |
2919 | | for (u32 i {}; i < frame.toc.entries.size(); ++i) |
2920 | | dbgln(" {:5} | {:5} | {:6}", i, frame.toc.entries[i], frame.toc.group_offsets[i]); |
2921 | | } |
2922 | |
|
2923 | 0 | auto bits_per_sample = metadata.bit_depth.bits_per_sample; |
2924 | 0 | IntSize frame_size { frame.width, frame.height }; |
2925 | |
|
2926 | 0 | auto get_stream_for_section = [&](LittleEndianInputBitStream& stream, u32 section_index) -> ErrorOr<MaybeOwned<LittleEndianInputBitStream>> { |
2927 | | // "If num_groups == 1 and num_passes == 1, then there is a single TOC entry and a single section |
2928 | | // containing all frame data structures." |
2929 | 0 | if (frame.num_groups == 1 && frame.frame_header.passes.num_passes == 1) |
2930 | 0 | return MaybeOwned(stream); |
2931 | 0 | auto section_size = frame.toc.entries[section_index]; |
2932 | 0 | if (stream.align_to_byte_boundary() != 0) |
2933 | 0 | return Error::from_string_literal("JPEGXLLoader: Padding bits between sections must all be zeros"); |
2934 | 0 | auto constrained_stream = make<AutoDepletingConstrainedStream>(MaybeOwned<Stream>(stream), section_size); |
2935 | 0 | return TRY(try_make<LittleEndianInputBitStream>(move(constrained_stream))); |
2936 | 0 | }; |
2937 | |
|
2938 | 0 | { |
2939 | 0 | auto lf_stream = TRY(get_stream_for_section(stream, 0)); |
2940 | 0 | frame.lf_global = TRY(read_lf_global(*lf_stream, frame_size, frame.frame_header, metadata)); |
2941 | 0 | } |
2942 | |
|
2943 | 0 | for (u32 i {}; i < frame.num_lf_groups; ++i) { |
2944 | 0 | auto lf_stream = TRY(get_stream_for_section(stream, 1 + i)); |
2945 | | // From H.4.1, "The stream index is defined as follows: [...] for ModularLfGroup: 1 + num_lf_groups + LF group index;" |
2946 | 0 | TRY(read_lf_group(*lf_stream, |
2947 | 0 | { |
2948 | 0 | .global_modular = frame.lf_global.gmodular, |
2949 | 0 | .frame_header = frame.frame_header, |
2950 | 0 | .group_index = i, |
2951 | 0 | .stream_index = 1 + frame.num_lf_groups + i, |
2952 | 0 | .bit_depth = bits_per_sample, |
2953 | 0 | }, |
2954 | 0 | { |
2955 | 0 | .group_data = frame.lf_groups, |
2956 | 0 | .frame_size = frame_size, |
2957 | 0 | .num_lf_group = frame.num_lf_groups, |
2958 | 0 | })); |
2959 | 0 | } |
2960 | |
|
2961 | 0 | { |
2962 | 0 | auto hf_global_stream = TRY(get_stream_for_section(stream, 1 + frame.num_lf_groups)); |
2963 | 0 | if (frame.frame_header.encoding == Encoding::kVarDCT) |
2964 | 0 | frame.hf_global = TRY(read_hf_global(stream, frame.lf_global, frame.num_groups, frame.frame_header.passes.num_passes)); |
2965 | 0 | } |
2966 | |
|
2967 | 0 | for (u32 pass_index {}; pass_index < frame.frame_header.passes.num_passes; ++pass_index) { |
2968 | 0 | for (u32 group_index {}; group_index < frame.num_groups; ++group_index) { |
2969 | 0 | auto toc_section_number = 2 + frame.num_lf_groups + pass_index * frame.num_groups + group_index; |
2970 | 0 | auto pass_stream = TRY(get_stream_for_section(stream, toc_section_number)); |
2971 | | |
2972 | | // From H.4.1, ModularGroup: 1 + 3 * num_lf_groups + 17 + num_groups * pass index + group index |
2973 | 0 | u32 stream_index = 1 + 3 * frame.num_lf_groups + 17 + frame.num_groups * pass_index + group_index; |
2974 | 0 | TRY(read_pass_group(*pass_stream, |
2975 | 0 | { |
2976 | 0 | .global_modular = frame.lf_global.gmodular, |
2977 | 0 | .frame_header = frame.frame_header, |
2978 | 0 | .group_index = group_index, |
2979 | 0 | .pass_index = pass_index, |
2980 | 0 | .stream_index = stream_index, |
2981 | 0 | }, |
2982 | 0 | { .bit_depth = bits_per_sample }, |
2983 | 0 | { |
2984 | 0 | .lf_global = frame.lf_global, |
2985 | 0 | .lf_groups = frame.lf_groups, |
2986 | 0 | .hf_global = frame.hf_global, |
2987 | 0 | })); |
2988 | 0 | } |
2989 | 0 | } |
2990 | | |
2991 | | // G.4.2 - Modular group data |
2992 | | // When all modular groups are decoded, the inverse transforms are applied to |
2993 | | // the at that point fully decoded GlobalModular image, as specified in H.6. |
2994 | 0 | auto& channels = frame.lf_global.gmodular.modular_data.channels; |
2995 | 0 | auto const& transform_infos = frame.lf_global.gmodular.modular_data.transform; |
2996 | 0 | for (auto const& transformation : transform_infos.in_reverse()) |
2997 | 0 | TRY(apply_transformation(channels, transformation, bits_per_sample, frame.lf_global.gmodular.modular_data.wp_params)); |
2998 | |
|
2999 | 0 | if (frame.frame_header.encoding == Encoding::kVarDCT) { |
3000 | 0 | channels.prepend(TRY(Channel::create(ChannelInfo::from_size(frame_size)))); |
3001 | 0 | channels.prepend(TRY(Channel::create(ChannelInfo::from_size(frame_size)))); |
3002 | 0 | channels.prepend(TRY(Channel::create(ChannelInfo::from_size(frame_size)))); |
3003 | 0 | } |
3004 | |
|
3005 | 0 | frame.image = TRY(Image::adopt_channels(move(channels))); |
3006 | |
|
3007 | 0 | return frame; |
3008 | 0 | } |
3009 | | /// |
3010 | | |
3011 | | /// J - Restoration filters |
3012 | | |
3013 | | // J.3 Gabor-like transform |
3014 | | using GaborWeights = Array<float, 2>; |
3015 | | |
3016 | | static FloatMatrix3x3 construct_gabor_like_filter(GaborWeights weights) |
3017 | 0 | { |
3018 | 0 | FloatMatrix3x3 filter {}; |
3019 | | |
3020 | | // "the unnormalized weight for the center is 1" |
3021 | 0 | filter(1, 1) = 1; |
3022 | | |
3023 | | // "its four neighbours (top, bottom, left, right) are restoration_filter.gab_C_weight1" |
3024 | 0 | filter(0, 1) = weights[0]; |
3025 | 0 | filter(1, 0) = weights[0]; |
3026 | 0 | filter(1, 2) = weights[0]; |
3027 | 0 | filter(2, 1) = weights[0]; |
3028 | | |
3029 | | // "and the four corners (top-left, top-right, bottom-left, bottom-right) are restoration_filter.gab_C_weight2." |
3030 | 0 | filter(0, 0) = weights[1]; |
3031 | 0 | filter(0, 2) = weights[1]; |
3032 | 0 | filter(2, 0) = weights[1]; |
3033 | 0 | filter(2, 2) = weights[1]; |
3034 | | |
3035 | | // These weights are rescaled uniformly before convolution, such that the nine kernel weights sum to 1. |
3036 | 0 | return filter / filter.element_sum(); |
3037 | 0 | } |
3038 | | |
3039 | | static FloatMatrix3x3 extract_matrix_from_channel(FloatChannel const& channel, u32 x, u32 y) |
3040 | 0 | { |
3041 | 0 | FloatMatrix3x3 m; |
3042 | 0 | auto x_minus_1 = x == 0 ? mirror_1d(x, channel.width()) : x - 1; |
3043 | 0 | auto x_plus_1 = x == channel.width() - 1 ? mirror_1d(x, channel.width()) : x + 1; |
3044 | |
|
3045 | 0 | auto y_minus_1 = y == 0 ? mirror_1d(y, channel.height()) : y - 1; |
3046 | 0 | auto y_plus_1 = y == channel.height() - 1 ? mirror_1d(y, channel.height()) : y + 1; |
3047 | |
|
3048 | 0 | m(0, 0) = channel.get(x_minus_1, y_minus_1); |
3049 | 0 | m(0, 1) = channel.get(x, y_minus_1); |
3050 | 0 | m(0, 2) = channel.get(x_plus_1, y_minus_1); |
3051 | 0 | m(1, 0) = channel.get(x_minus_1, y); |
3052 | 0 | m(1, 1) = channel.get(x, y); |
3053 | 0 | m(1, 2) = channel.get(x_plus_1, y); |
3054 | 0 | m(2, 0) = channel.get(x_minus_1, y_plus_1); |
3055 | 0 | m(2, 1) = channel.get(x, y_plus_1); |
3056 | 0 | m(2, 2) = channel.get(x_plus_1, y_plus_1); |
3057 | |
|
3058 | 0 | return m; |
3059 | 0 | } |
3060 | | |
3061 | | static ErrorOr<void> apply_gabor_like_on_channel(FloatChannel& channel, GaborWeights weights) |
3062 | 0 | { |
3063 | 0 | auto filter = construct_gabor_like_filter(weights); |
3064 | 0 | auto out = TRY(channel.copy()); |
3065 | 0 | for (u32 y = 0; y < channel.height(); ++y) { |
3066 | 0 | for (u32 x = 0; x < channel.width(); ++x) { |
3067 | 0 | auto source = extract_matrix_from_channel(channel, x, y); |
3068 | 0 | auto result = source.hadamard_product(filter).element_sum(); |
3069 | 0 | out.set(x, y, result); |
3070 | 0 | } |
3071 | 0 | } |
3072 | 0 | channel = move(out); |
3073 | 0 | return {}; |
3074 | 0 | } |
3075 | | |
3076 | | static ErrorOr<void> apply_gabor_like_filter(RestorationFilter const& restoration_filter, Span<FloatChannel> channels) |
3077 | 0 | { |
3078 | 0 | VERIFY(channels.size() == 3); |
3079 | | |
3080 | 0 | Array<GaborWeights, 3> weights { |
3081 | 0 | GaborWeights { restoration_filter.gab_x_weight1, restoration_filter.gab_x_weight2 }, |
3082 | 0 | GaborWeights { restoration_filter.gab_y_weight1, restoration_filter.gab_y_weight2 }, |
3083 | 0 | GaborWeights { restoration_filter.gab_b_weight1, restoration_filter.gab_b_weight2 }, |
3084 | 0 | }; |
3085 | 0 | for (auto [i, channel] : enumerate(channels)) |
3086 | 0 | TRY(apply_gabor_like_on_channel(channel, weights[i])); |
3087 | 0 | return {}; |
3088 | 0 | } |
3089 | | |
3090 | | // J.4 - Edge-preserving filter |
3091 | | |
3092 | | // J.4.2 - Distances |
3093 | | static f32 DistanceStep0and1(RestorationFilter const& rf, Span<FloatChannel const> input, u32 x, u32 y, i8 cx, i8 cy) |
3094 | 0 | { |
3095 | 0 | f32 dist = 0; |
3096 | 0 | auto coords = to_array<IntPoint>({ { 0, 0 }, { -1, 0 }, { 1, 0 }, { 0, -1 }, { 0, 1 } }); |
3097 | 0 | for (u8 c = 0; c < 3; c++) { |
3098 | 0 | for (auto coord : coords) { |
3099 | 0 | auto ix = coord.x(); |
3100 | 0 | auto iy = coord.y(); |
3101 | 0 | dist += abs(input[c].get_mirrored(x + ix, y + iy) - input[c].get_mirrored(x + cx + ix, y + cy + iy)) * rf.epf_channel_scale[c]; |
3102 | 0 | } |
3103 | 0 | } |
3104 | 0 | return dist; |
3105 | 0 | } |
3106 | | |
3107 | | static f32 DistanceStep2(RestorationFilter const& rf, Span<FloatChannel const> input, u32 x, u32 y, i8 cx, i8 cy) |
3108 | 0 | { |
3109 | 0 | f32 dist = 0; |
3110 | 0 | for (u8 c = 0; c < 3; c++) { |
3111 | 0 | dist += abs(input[c].get_mirrored(x, y) - input[c].get_mirrored(x + cx, y + cy)) * rf.epf_channel_scale[c]; |
3112 | 0 | } |
3113 | 0 | return dist; |
3114 | 0 | } |
3115 | | |
3116 | | // J.4.3 - Weights |
3117 | | static f32 Weight(RestorationFilter const& rf, f32 step, f32 distance, f32 sigma, u32 x, u32 y) |
3118 | 0 | { |
3119 | | // "step = /* 0 if first step, 1 if second step, 2 if third step */;" |
3120 | 0 | Array<f32, 3> step_multiplier = { 1.65f * rf.epf_pass0_sigma_scale, 1.65f * 1, 1.65f * rf.epf_pass2_sigma_scale }; |
3121 | 0 | f32 position_multiplier {}; |
3122 | | // "either coordinate of the reference sample is 0 or 7 UMod 8." |
3123 | 0 | if (x % 8 == 0 || x % 8 == 7 || y % 8 == 0 || y % 8 == 7) |
3124 | 0 | position_multiplier = rf.epf_border_sad_mul; |
3125 | 0 | else |
3126 | 0 | position_multiplier = 1; |
3127 | 0 | f32 inv_sigma = step_multiplier[step] * 4 * (1 - sqrt(0.5f)) / sigma; |
3128 | 0 | f32 scaled_distance = position_multiplier * distance; |
3129 | 0 | f32 v = 1 - scaled_distance * inv_sigma; |
3130 | 0 | if (v <= 0) |
3131 | 0 | return 0; |
3132 | 0 | return v; |
3133 | 0 | } |
3134 | | |
3135 | | // J.4.4 - Weighted average |
3136 | | static void apply_epf_step_on_pixel(RestorationFilter const& rf, Span<FloatChannel const> input, Span<FloatChannel> output, u32 step, f32 sigma, u32 x, u32 y) |
3137 | 0 | { |
3138 | 0 | auto kernel_coords = [&]() { |
3139 | 0 | if (step == 0) { |
3140 | 0 | static constexpr Array points = to_array<IntPoint>({ { 0, 0 }, { -1, 0 }, { 1, 0 }, { 0, -1 }, { 0, 1 }, |
3141 | 0 | { 1, -1 }, { 1, 1 }, { -1, 1 }, { -1, -1 }, { -2, 0 }, |
3142 | 0 | { 2, 0 }, { 0, 2 }, { 0, -2 } }); |
3143 | 0 | return points.span(); |
3144 | 0 | } |
3145 | 0 | static constexpr Array points = to_array<IntPoint>({ { 0, 0 }, { -1, 0 }, { 1, 0 }, { 0, -1 }, { 0, 1 } }); |
3146 | 0 | return points.span(); |
3147 | 0 | }(); |
3148 | |
|
3149 | 0 | f32 sum_weights = 0; |
3150 | 0 | Array<f32, 3> sum_channels = { 0, 0, 0 }; |
3151 | 0 | for (auto coord : kernel_coords) { |
3152 | 0 | auto ix = coord.x(); |
3153 | 0 | auto iy = coord.y(); |
3154 | 0 | f32 distance {}; |
3155 | 0 | if (step == 0 || step == 1) { |
3156 | 0 | distance = DistanceStep0and1(rf, input, x, y, ix, iy); |
3157 | 0 | } else { |
3158 | 0 | distance = DistanceStep2(rf, input, x, y, ix, iy); |
3159 | 0 | } |
3160 | 0 | f32 weight = Weight(rf, step, distance, sigma, x, y); |
3161 | 0 | sum_weights += weight; |
3162 | 0 | for (u8 c = 0; c < 3; c++) { |
3163 | 0 | sum_channels[c] += input[c].get_mirrored(x + ix, y + iy) * weight; |
3164 | 0 | } |
3165 | 0 | } |
3166 | 0 | for (u8 c = 0; c < 3; c++) { |
3167 | 0 | output[c].set(x, y, sum_channels[c] / sum_weights); |
3168 | 0 | } |
3169 | 0 | } |
3170 | | |
3171 | | // J.4.1 - General |
3172 | | static void apply_epf_step(RestorationFilter const& rf, Span<FloatChannel const> input, Span<FloatChannel> output, u32 step, f32 sigma) |
3173 | 0 | { |
3174 | 0 | for (u64 y = 0; y < input[0].height(); ++y) { |
3175 | 0 | for (u64 x = 0; x < input[0].width(); ++x) |
3176 | 0 | apply_epf_step_on_pixel(rf, input, output, step, sigma, x, y); |
3177 | 0 | } |
3178 | 0 | } |
3179 | | |
3180 | | static ErrorOr<void> apply_epf_filter(FrameHeader const& frame_header, Span<FloatChannel> channels) |
3181 | 0 | { |
3182 | | // "sigma is then computed as specified by the following code if the frame encoding is kVarDCT, else it is set to rf.epf_sigma_for_modular." |
3183 | 0 | if (frame_header.encoding == Encoding::kVarDCT) |
3184 | 0 | return Error::from_string_literal("FIXME: Compute epf's sigma for VarDCT frames."); |
3185 | 0 | f32 sigma = frame_header.restoration_filter.epf_sigma_for_modular; |
3186 | | |
3187 | | // "The output of each step is used as an input for the following step." |
3188 | 0 | Vector<FloatChannel> next_input; |
3189 | 0 | for (u8 i = 0; i < channels.size(); ++i) |
3190 | 0 | TRY(next_input.try_append(TRY(channels[i].copy()))); |
3191 | | |
3192 | | // "The first step is only done if rf.epf_iters == 3." |
3193 | 0 | if (frame_header.restoration_filter.epf_iters == 3) { |
3194 | 0 | apply_epf_step(frame_header.restoration_filter, next_input, channels, 0, sigma); |
3195 | 0 | next_input.clear(); |
3196 | 0 | for (u8 i = 0; i < channels.size(); ++i) |
3197 | 0 | TRY(next_input.try_append(TRY(channels[i].copy()))); |
3198 | 0 | } |
3199 | | |
3200 | | // "The second step is always done (if rf.epf_iters > 0)." |
3201 | 0 | if (frame_header.restoration_filter.epf_iters > 0) { |
3202 | 0 | apply_epf_step(frame_header.restoration_filter, next_input, channels, 1, sigma); |
3203 | 0 | next_input.clear(); |
3204 | 0 | for (u8 i = 0; i < channels.size(); ++i) |
3205 | 0 | TRY(next_input.try_append(TRY(channels[i].copy()))); |
3206 | 0 | } |
3207 | | |
3208 | | // "The third step is only done if rf.epf_iters >= 2." |
3209 | 0 | if (frame_header.restoration_filter.epf_iters >= 2) |
3210 | 0 | apply_epf_step(frame_header.restoration_filter, next_input, channels, 2, sigma); |
3211 | |
|
3212 | 0 | return {}; |
3213 | 0 | } |
3214 | | |
3215 | | struct SplitChannels { |
3216 | | Vector<FloatChannel> color_channels {}; |
3217 | | Vector<Channel> extra_channels {}; |
3218 | | }; |
3219 | | |
3220 | | template<typename T2, typename T1> |
3221 | | static ErrorOr<Vector<Detail::Channel<T2>>> convert_channels(Span<Detail::Channel<T1>> const& channels, u8 bits_per_sample) |
3222 | 0 | { |
3223 | 0 | Vector<Detail::Channel<T2>> new_channels; |
3224 | 0 | TRY(new_channels.try_ensure_capacity(channels.size())); |
3225 | 0 | for (u32 i = 0; i < channels.size(); ++i) |
3226 | 0 | new_channels.append(TRY(channels[i].template as<T2>(bits_per_sample))); |
3227 | 0 | return new_channels; |
3228 | 0 | } Unexecuted instantiation: JPEGXLLoader.cpp:AK::ErrorOr<AK::Vector<Gfx::JPEGXL::Detail::Channel<float>, 0ul>, AK::Error> Gfx::JPEGXL::convert_channels<float, int>(AK::Span<Gfx::JPEGXL::Detail::Channel<int> > const&, unsigned char) Unexecuted instantiation: JPEGXLLoader.cpp:AK::ErrorOr<AK::Vector<Gfx::JPEGXL::Detail::Channel<int>, 0ul>, AK::Error> Gfx::JPEGXL::convert_channels<int, float>(AK::Span<Gfx::JPEGXL::Detail::Channel<float> > const&, unsigned char) |
3229 | | |
3230 | | static ErrorOr<SplitChannels> extract_color_channels(ImageMetadata const& metadata, Image& image) |
3231 | 0 | { |
3232 | 0 | auto all_channels = move(image.channels()); |
3233 | 0 | auto f32_color_channels = TRY(convert_channels<f32>(all_channels.span().trim(metadata.number_of_color_channels()), metadata.bit_depth.bits_per_sample)); |
3234 | 0 | all_channels.remove(0, metadata.number_of_color_channels()); |
3235 | 0 | return SplitChannels { move(f32_color_channels), move(all_channels) }; |
3236 | 0 | } |
3237 | | |
3238 | | static ErrorOr<void> ensure_enough_color_channels(Vector<FloatChannel>& channels) |
3239 | 0 | { |
3240 | 0 | if (channels.size() == 3) |
3241 | 0 | return {}; |
3242 | 0 | VERIFY(channels.size() == 1); |
3243 | 0 | TRY(channels.try_append(TRY(channels[0].copy()))); |
3244 | 0 | TRY(channels.try_append(TRY(channels[0].copy()))); |
3245 | 0 | return {}; |
3246 | 0 | } |
3247 | | |
3248 | | // J.1 - General |
3249 | | static ErrorOr<void> apply_restoration_filters(Frame& frame, ImageMetadata const& metadata) |
3250 | 0 | { |
3251 | 0 | auto const& frame_header = frame.frame_header; |
3252 | |
|
3253 | 0 | if (frame_header.restoration_filter.gab || frame_header.restoration_filter.epf_iters != 0) { |
3254 | | if constexpr (JPEGXL_DEBUG) { |
3255 | | dbgln("Restoration filters:"); |
3256 | | dbgln(" * Gab: {}", frame_header.restoration_filter.gab); |
3257 | | dbgln(" * EPF: {}", frame_header.restoration_filter.epf_iters); |
3258 | | } |
3259 | | |
3260 | | // FIXME: Clarify where we should actually do the i32 -> f32 convertion. |
3261 | 0 | auto channels = TRY(extract_color_channels(metadata, *frame.image)); |
3262 | 0 | TRY(ensure_enough_color_channels(channels.color_channels)); |
3263 | |
|
3264 | 0 | if (frame_header.restoration_filter.gab) |
3265 | 0 | TRY(apply_gabor_like_filter(frame.frame_header.restoration_filter, channels.color_channels)); |
3266 | 0 | if (frame_header.restoration_filter.epf_iters != 0) |
3267 | 0 | TRY(apply_epf_filter(frame_header, channels.color_channels)); |
3268 | | |
3269 | | // Remove unwanted color channels if the image is greyscale. |
3270 | 0 | if (metadata.number_of_color_channels() == 1) |
3271 | 0 | channels.color_channels.remove(1, 2); |
3272 | 0 | auto i32_channels = TRY(convert_channels<i32>(channels.color_channels.span(), metadata.bit_depth.bits_per_sample)); |
3273 | 0 | TRY(i32_channels.try_extend(move(channels.extra_channels))); |
3274 | 0 | frame.image = TRY(Image::adopt_channels(move(i32_channels))); |
3275 | 0 | } |
3276 | |
|
3277 | 0 | return {}; |
3278 | 0 | } |
3279 | | /// |
3280 | | |
3281 | | /// K - Image features |
3282 | | static ErrorOr<void> apply_upsampling(Frame& frame, ImageMetadata const& metadata) |
3283 | 0 | { |
3284 | 0 | Optional<u32> ec_max; |
3285 | 0 | for (auto upsampling : frame.frame_header.ec_upsampling) { |
3286 | 0 | if (!ec_max.has_value() || upsampling > *ec_max) |
3287 | 0 | ec_max = upsampling; |
3288 | 0 | } |
3289 | |
|
3290 | 0 | if (frame.frame_header.upsampling > 1 || ec_max.value_or(0) > 1) { |
3291 | 0 | if (ec_max.value_or(0) > 2) |
3292 | 0 | TODO(); |
3293 | | |
3294 | 0 | auto const k = frame.frame_header.upsampling; |
3295 | |
|
3296 | 0 | auto weight = [k, &metadata](u8 index) -> double { |
3297 | 0 | if (k == 2) |
3298 | 0 | return metadata.up2_weight[index]; |
3299 | 0 | if (k == 4) |
3300 | 0 | return metadata.up4_weight[index]; |
3301 | 0 | return metadata.up8_weight[index]; |
3302 | 0 | }; |
3303 | | |
3304 | | // FIXME: Use ec_upsampling for extra-channels |
3305 | 0 | for (auto& channel : frame.image->channels()) { |
3306 | 0 | auto upsampled = TRY(Channel::create({ .width = k * channel.width(), .height = k * channel.height() })); |
3307 | | |
3308 | | // Loop over the original image |
3309 | 0 | for (u32 y {}; y < channel.height(); y++) { |
3310 | 0 | for (u32 x {}; x < channel.width(); x++) { |
3311 | | |
3312 | | // Loop over the upsampling factor |
3313 | 0 | for (u8 kx {}; kx < k; ++kx) { |
3314 | 0 | for (u8 ky {}; ky < k; ++ky) { |
3315 | 0 | double sum {}; |
3316 | | // Loop over the W window |
3317 | 0 | double W_min = NumericLimits<double>::max(); |
3318 | 0 | double W_max = -NumericLimits<double>::max(); |
3319 | 0 | for (u8 ix {}; ix < 5; ++ix) { |
3320 | 0 | for (u8 iy {}; iy < 5; ++iy) { |
3321 | 0 | auto const j = (ky < k / 2) ? (iy + 5 * ky) : ((4 - iy) + 5 * (k - 1 - ky)); |
3322 | 0 | auto const i = (kx < k / 2) ? (ix + 5 * kx) : ((4 - ix) + 5 * (k - 1 - kx)); |
3323 | 0 | auto const minimum = min(i, j); |
3324 | 0 | auto const maximum = max(i, j); |
3325 | 0 | auto const index = 5 * k * minimum / 2 - minimum * (minimum - 1) / 2 + maximum - minimum; |
3326 | |
|
3327 | 0 | auto const origin_sample = channel.get_mirrored(x + ix - 2, y + iy - 2); |
3328 | |
|
3329 | 0 | W_min = min(W_min, origin_sample); |
3330 | 0 | W_max = max(W_max, origin_sample); |
3331 | |
|
3332 | 0 | sum += origin_sample * weight(index); |
3333 | 0 | } |
3334 | 0 | } |
3335 | | |
3336 | | // The resulting sample is clamped to the range [a, b] where a and b are |
3337 | | // the minimum and maximum of the samples in W. |
3338 | 0 | sum = clamp(sum, W_min, W_max); |
3339 | |
|
3340 | 0 | upsampled.set(x * k + kx, y * k + ky, sum); |
3341 | 0 | } |
3342 | 0 | } |
3343 | 0 | } |
3344 | 0 | } |
3345 | 0 | channel = move(upsampled); |
3346 | 0 | } |
3347 | 0 | } |
3348 | | |
3349 | 0 | return {}; |
3350 | 0 | } |
3351 | | |
3352 | | /// K.3.2 Patches rendering |
3353 | | static ErrorOr<void> apply_patches(Span<Frame> previous_frames, Frame& frame) |
3354 | 0 | { |
3355 | 0 | auto& destination_image = frame.image; |
3356 | 0 | for (auto const& [i, patch] : enumerate(frame.lf_global.patches)) { |
3357 | 0 | if (patch.ref > previous_frames.size()) |
3358 | 0 | return Error::from_string_literal("JPEGXLLoader: Unable to find the requested reference frame"); |
3359 | | |
3360 | 0 | auto& source_image = previous_frames[patch.ref].image; |
3361 | 0 | auto source_rect = IntRect({ patch.x0, patch.y0 }, { patch.width, patch.height }); |
3362 | 0 | auto source_patch = TRY(source_image->get_subimage(source_rect)); |
3363 | |
|
3364 | 0 | for (u32 j = 0; j < patch.count; ++j) { |
3365 | 0 | auto destination = IntRect(patch.positions[j], { patch.width, patch.height }); |
3366 | 0 | auto destination_patch = TRY(destination_image->get_subimage(destination)); |
3367 | | // FIXME: "iterates over the three colour channels if c == 0 and refers to the extra channel with index c−1 otherwise" |
3368 | 0 | TRY(source_patch.blend_into(destination_patch, patch.blending[j][0].mode)); |
3369 | 0 | } |
3370 | 0 | } |
3371 | | |
3372 | 0 | return {}; |
3373 | 0 | } |
3374 | | |
3375 | | static ErrorOr<void> apply_image_features(Span<Frame> previous_frames, Frame& frame, ImageMetadata const& metadata) |
3376 | 0 | { |
3377 | 0 | TRY(apply_upsampling(frame, metadata)); |
3378 | |
|
3379 | 0 | auto flags = frame.frame_header.flags; |
3380 | 0 | if (flags & FrameHeader::Flags::kPatches) { |
3381 | 0 | TRY(apply_patches(previous_frames, frame)); |
3382 | 0 | } else if (flags != FrameHeader::Flags::None) { |
3383 | 0 | dbgln("JPEGXLLoader: Unsupported image features"); |
3384 | 0 | } |
3385 | 0 | return {}; |
3386 | 0 | } |
3387 | | /// |
3388 | | |
3389 | | /// L.2 - XYB + L.3 - YCbCr |
3390 | | template<typename F> |
3391 | | static void for_each_pixel_of_color_channels(Image& image, F color_conversion) |
3392 | 0 | { |
3393 | 0 | auto& channels = image.channels(); |
3394 | 0 | VERIFY(channels.size() >= 3); |
3395 | | |
3396 | 0 | VERIFY(channels[0].width() == channels[1].width() && channels[1].width() == channels[2].width()); |
3397 | 0 | VERIFY(channels[0].height() == channels[1].height() && channels[1].height() == channels[2].height()); |
3398 | | |
3399 | 0 | for (u32 y = 0; y < channels[0].height(); ++y) { |
3400 | 0 | for (u32 x = 0; x < channels[0].width(); ++x) { |
3401 | 0 | color_conversion(channels[0].get(x, y), channels[1].get(x, y), channels[2].get(x, y)); |
3402 | 0 | } |
3403 | 0 | } |
3404 | 0 | } Unexecuted instantiation: JPEGXLLoader.cpp:void Gfx::JPEGXL::for_each_pixel_of_color_channels<Gfx::JPEGXL::ycbcr_to_rgb(Gfx::JPEGXL::Image&, unsigned char)::$_0>(Gfx::JPEGXL::Image&, Gfx::JPEGXL::ycbcr_to_rgb(Gfx::JPEGXL::Image&, unsigned char)::$_0) Unexecuted instantiation: JPEGXLLoader.cpp:void Gfx::JPEGXL::for_each_pixel_of_color_channels<Gfx::JPEGXL::xyb_to_rgb(Gfx::JPEGXL::Frame&, Gfx::JPEGXL::ImageMetadata const&)::$_0>(Gfx::JPEGXL::Image&, Gfx::JPEGXL::xyb_to_rgb(Gfx::JPEGXL::Frame&, Gfx::JPEGXL::ImageMetadata const&)::$_0) |
3405 | | |
3406 | | static void ycbcr_to_rgb(Image& image, u8 bits_per_sample) |
3407 | 0 | { |
3408 | 0 | auto const half_range_offset = (1 << bits_per_sample) / 2; |
3409 | 0 | auto color_conversion = [half_range_offset](i32& c1, i32& c2, i32& c3) { |
3410 | 0 | auto const cb = c1; |
3411 | 0 | auto const luma = c2; |
3412 | 0 | auto const cr = c3; |
3413 | |
|
3414 | 0 | c1 = luma + half_range_offset + 1.402 * cr; |
3415 | 0 | c2 = luma + half_range_offset - 0.344136 * cb - 0.714136 * cr; |
3416 | 0 | c3 = luma + half_range_offset + 1.772 * cb; |
3417 | 0 | }; |
3418 | |
|
3419 | 0 | for_each_pixel_of_color_channels(image, move(color_conversion)); |
3420 | 0 | } |
3421 | | |
3422 | | // L.2.2 Inverse XYB transform |
3423 | | static void xyb_to_rgb(Frame& frame, ImageMetadata const& metadata) |
3424 | 0 | { |
3425 | | // "X, Y, B samples are converted to an RGB colour encoding as specified in this subclause, |
3426 | | // in which oim denotes metadata.opsin_inverse_matrix." |
3427 | 0 | auto const& oim = metadata.opsin_inverse_matrix; |
3428 | 0 | f32 to_int = (1 << metadata.bit_depth.bits_per_sample) - 1; |
3429 | 0 | auto linear_to_srgb = [](f32 c) { |
3430 | 0 | return c >= 0.0031308f ? 1.055f * pow(c, 0.4166666f) - 0.055f : 12.92f * c; |
3431 | 0 | }; |
3432 | 0 | auto color_conversion = [&](i32& c1, i32& c2, i32& c3) { |
3433 | 0 | f32 const y_ = c1; |
3434 | 0 | f32 const x_ = c2; |
3435 | 0 | f32 const b_ = c3; |
3436 | |
|
3437 | 0 | f32 y {}, x {}, b {}; |
3438 | 0 | if (frame.frame_header.encoding == Encoding::kModular) { |
3439 | 0 | y = y_ * frame.lf_global.lf_dequant.m_y_lf_unscaled; |
3440 | 0 | x = x_ * frame.lf_global.lf_dequant.m_x_lf_unscaled; |
3441 | 0 | b = (b_ + y_) * frame.lf_global.lf_dequant.m_b_lf_unscaled; |
3442 | 0 | } else { |
3443 | 0 | y = y_; |
3444 | 0 | x = x_; |
3445 | 0 | b = b_; |
3446 | 0 | } |
3447 | |
|
3448 | 0 | f32 Lgamma = y + x; |
3449 | 0 | f32 Mgamma = y - x; |
3450 | 0 | f32 Sgamma = b; |
3451 | 0 | f32 itscale = 255 / metadata.tone_mapping.intensity_target; |
3452 | 0 | f32 Lmix = (powf(Lgamma - cbrt(oim.opsin_bias0), 3) + oim.opsin_bias0) * itscale; |
3453 | 0 | f32 Mmix = (powf(Mgamma - cbrt(oim.opsin_bias1), 3) + oim.opsin_bias1) * itscale; |
3454 | 0 | f32 Smix = (powf(Sgamma - cbrt(oim.opsin_bias2), 3) + oim.opsin_bias2) * itscale; |
3455 | 0 | f32 R = oim.inv_mat00 * Lmix + oim.inv_mat01 * Mmix + oim.inv_mat02 * Smix; |
3456 | 0 | f32 G = oim.inv_mat10 * Lmix + oim.inv_mat11 * Mmix + oim.inv_mat12 * Smix; |
3457 | 0 | f32 B = oim.inv_mat20 * Lmix + oim.inv_mat21 * Mmix + oim.inv_mat22 * Smix; |
3458 | | |
3459 | | // "The resulting RGB samples correspond to sRGB primaries and a D65 white point, and the transfer function is linear." |
3460 | | // We assume sRGB everywhere, so let's apply the transfer function here. |
3461 | 0 | R = linear_to_srgb(R); |
3462 | 0 | G = linear_to_srgb(G); |
3463 | 0 | B = linear_to_srgb(B); |
3464 | |
|
3465 | 0 | c1 = round_to<i32>(R * to_int); |
3466 | 0 | c2 = round_to<i32>(G * to_int); |
3467 | 0 | c3 = round_to<i32>(B * to_int); |
3468 | 0 | }; |
3469 | |
|
3470 | 0 | for_each_pixel_of_color_channels(*frame.image, move(color_conversion)); |
3471 | 0 | } |
3472 | | |
3473 | | static void apply_colour_transformation(Frame& frame, ImageMetadata const& metadata) |
3474 | 0 | { |
3475 | 0 | if (frame.frame_header.do_YCbCr) |
3476 | 0 | ycbcr_to_rgb(*frame.image, metadata.bit_depth.bits_per_sample); |
3477 | |
|
3478 | 0 | if (metadata.xyb_encoded) { |
3479 | 0 | xyb_to_rgb(frame, metadata); |
3480 | 0 | } else { |
3481 | | // FIXME: Do a proper color transformation with metadata.colour_encoding |
3482 | 0 | } |
3483 | 0 | } |
3484 | | /// |
3485 | | |
3486 | | /// L.4 - Extra channel rendering |
3487 | | static ErrorOr<void> render_extra_channels(Image&, ImageMetadata const& metadata) |
3488 | 0 | { |
3489 | 0 | for (u16 i = metadata.number_of_color_channels(); i < metadata.number_of_channels(); ++i) { |
3490 | 0 | auto const ec_index = i - metadata.number_of_color_channels(); |
3491 | 0 | if (metadata.ec_info[ec_index].dim_shift != 0) |
3492 | 0 | TODO(); |
3493 | 0 | } |
3494 | | |
3495 | 0 | return {}; |
3496 | 0 | } |
3497 | | /// |
3498 | | |
3499 | | class LoadingContext { |
3500 | | public: |
3501 | | LoadingContext(NonnullOwnPtr<Stream> stream) |
3502 | 0 | : m_stream(move(stream)) |
3503 | 0 | { |
3504 | 0 | } |
3505 | | |
3506 | | ErrorOr<void> decode_image_header() |
3507 | 0 | { |
3508 | 0 | constexpr auto JPEGXL_SIGNATURE = 0xFF0A; |
3509 | |
|
3510 | 0 | auto const signature = TRY(m_stream.read_value<BigEndian<u16>>()); |
3511 | 0 | if (signature != JPEGXL_SIGNATURE) |
3512 | 0 | return Error::from_string_literal("Unrecognized signature"); |
3513 | | |
3514 | 0 | m_header = TRY(read_size_header(m_stream)); |
3515 | 0 | m_metadata = TRY(read_metadata_header(m_stream)); |
3516 | |
|
3517 | 0 | dbgln_if(JPEGXL_DEBUG, "Decoding a JPEG XL image with size {}x{} and {} channels, bit-depth={}{}.", |
3518 | 0 | m_header.width, m_header.height, m_metadata.number_of_channels(), m_metadata.bit_depth.bits_per_sample, |
3519 | 0 | m_metadata.colour_encoding.want_icc ? ", icc_profile"sv : ""sv); |
3520 | |
|
3521 | 0 | m_state = State::HeaderDecoded; |
3522 | |
|
3523 | 0 | return {}; |
3524 | 0 | } |
3525 | | |
3526 | | ErrorOr<void> decode_icc() |
3527 | 0 | { |
3528 | 0 | if (m_metadata.colour_encoding.want_icc && m_icc_profile.size() == 0) |
3529 | 0 | m_icc_profile = TRY(read_icc(m_stream)); |
3530 | 0 | m_state = State::ICCProfileDecoded; |
3531 | 0 | return {}; |
3532 | 0 | } |
3533 | | |
3534 | | ErrorOr<void> decode_frame() |
3535 | 0 | { |
3536 | 0 | auto frame = TRY(read_frame(m_stream, m_header, m_metadata)); |
3537 | 0 | auto const& frame_header = frame.frame_header; |
3538 | |
|
3539 | 0 | TRY(apply_restoration_filters(frame, m_metadata)); |
3540 | |
|
3541 | 0 | TRY(apply_image_features(m_frames, frame, m_metadata)); |
3542 | | |
3543 | | // "If lf_level != 0, the samples of the frame (before any colour transform is applied) |
3544 | | // are recorded as LFFrame[lf_level−1] and may be referenced by subsequent frames." |
3545 | 0 | if (frame.frame_header.lf_level != 0) { |
3546 | 0 | m_lf_frames[frame.frame_header.lf_level - 1] = move(frame); |
3547 | 0 | return {}; |
3548 | 0 | } |
3549 | | |
3550 | 0 | if (!frame_header.save_before_ct) { |
3551 | 0 | apply_colour_transformation(frame, m_metadata); |
3552 | 0 | } |
3553 | |
|
3554 | 0 | TRY(render_extra_channels(*frame.image, m_metadata)); |
3555 | |
|
3556 | 0 | m_frames.append(move(frame)); |
3557 | |
|
3558 | 0 | return {}; |
3559 | 0 | } |
3560 | | |
3561 | | ErrorOr<void> decode() |
3562 | 0 | { |
3563 | 0 | auto result = [this]() -> ErrorOr<void> { |
3564 | | // A.1 - Codestream structure |
3565 | | |
3566 | | // The header is already decoded in JPEGXLImageDecoderPlugin::create() |
3567 | |
|
3568 | 0 | TRY(decode_icc()); |
3569 | |
|
3570 | 0 | if (m_metadata.preview.has_value()) |
3571 | 0 | TODO(); |
3572 | | |
3573 | 0 | while (m_frames.is_empty() || !m_frames.last().frame_header.is_last) |
3574 | 0 | TRY(decode_frame()); |
3575 | |
|
3576 | 0 | TRY(render_frame()); |
3577 | |
|
3578 | 0 | return {}; |
3579 | 0 | }(); |
3580 | |
|
3581 | 0 | m_state = result.is_error() ? State::Error : State::FrameDecoded; |
3582 | |
|
3583 | 0 | return result; |
3584 | 0 | } |
3585 | | |
3586 | | enum class State { |
3587 | | NotDecoded = 0, |
3588 | | Error, |
3589 | | HeaderDecoded, |
3590 | | ICCProfileDecoded, |
3591 | | FrameDecoded, |
3592 | | }; |
3593 | | |
3594 | | State state() const |
3595 | 0 | { |
3596 | 0 | return m_state; |
3597 | 0 | } |
3598 | | |
3599 | | IntSize size() const |
3600 | 0 | { |
3601 | 0 | return { m_header.width, m_header.height }; |
3602 | 0 | } |
3603 | | |
3604 | | RefPtr<Bitmap> bitmap() const |
3605 | 0 | { |
3606 | 0 | return m_bitmap; |
3607 | 0 | } |
3608 | | |
3609 | | RefPtr<CMYKBitmap> cmyk_bitmap() const |
3610 | 0 | { |
3611 | 0 | return m_cmyk_bitmap; |
3612 | 0 | } |
3613 | | |
3614 | | ByteBuffer const& icc_profile() const |
3615 | 0 | { |
3616 | 0 | return m_icc_profile; |
3617 | 0 | } |
3618 | | |
3619 | | bool is_cmyk() const |
3620 | 0 | { |
3621 | 0 | return any_of(m_metadata.ec_info, [](auto& info) { return info.type == ExtraChannelInfo::ExtraChannelType::kBlack; }); |
3622 | 0 | } |
3623 | | |
3624 | | private: |
3625 | | ErrorOr<void> render_frame() |
3626 | 0 | { |
3627 | 0 | auto final_image = TRY(Image::create({ m_header.width, m_header.height }, m_metadata)); |
3628 | |
|
3629 | 0 | for (auto& frame : m_frames) { |
3630 | 0 | if (frame.frame_header.frame_type != FrameHeader::FrameType::kRegularFrame) |
3631 | 0 | continue; |
3632 | | |
3633 | 0 | auto blending_mode = frame.frame_header.blending_info.mode; |
3634 | | |
3635 | | // "If x0 or y0 is negative, or the frame extends beyond the right or bottom |
3636 | | // edge of the image, only the intersection of the frame with the image is |
3637 | | // updated and contributes to the decoded image." |
3638 | 0 | IntRect frame_rect = frame.image->rect(); |
3639 | 0 | auto image_rect = IntRect::intersection(frame_rect.translated(IntPoint { frame.frame_header.x0, frame.frame_header.y0 }), final_image.rect()); |
3640 | 0 | frame_rect.set_x(-min(frame.frame_header.x0, 0)); |
3641 | 0 | frame_rect.set_y(-min(frame.frame_header.y0, 0)); |
3642 | 0 | frame_rect.set_size(image_rect.size()); |
3643 | |
|
3644 | 0 | auto frame_out = TRY(frame.image->get_subimage(frame_rect)); |
3645 | 0 | auto image_out = TRY(final_image.get_subimage(image_rect)); |
3646 | 0 | TRY(frame_out.blend_into(image_out, blending_mode)); |
3647 | 0 | } |
3648 | | |
3649 | 0 | if (is_cmyk()) |
3650 | 0 | m_cmyk_bitmap = TRY(final_image.to_cmyk_bitmap(m_metadata)); |
3651 | 0 | else |
3652 | 0 | m_bitmap = TRY(final_image.to_bitmap(m_metadata)); |
3653 | 0 | return {}; |
3654 | 0 | } |
3655 | | |
3656 | | State m_state { State::NotDecoded }; |
3657 | | |
3658 | | LittleEndianInputBitStream m_stream; |
3659 | | RefPtr<Gfx::Bitmap> m_bitmap; |
3660 | | RefPtr<Gfx::CMYKBitmap> m_cmyk_bitmap; |
3661 | | |
3662 | | Vector<Frame> m_frames; |
3663 | | Array<Optional<Frame>, 4> m_lf_frames; |
3664 | | |
3665 | | SizeHeader m_header; |
3666 | | ImageMetadata m_metadata; |
3667 | | |
3668 | | ByteBuffer m_icc_profile; |
3669 | | }; |
3670 | | |
3671 | | } |
3672 | | |
3673 | | namespace Gfx { |
3674 | | |
3675 | | JPEGXLImageDecoderPlugin::JPEGXLImageDecoderPlugin(Optional<Vector<u8>>&& jxlc_content, NonnullOwnPtr<FixedMemoryStream> stream) |
3676 | 0 | : m_context(make<JPEGXL::LoadingContext>(move(stream))) |
3677 | 0 | , m_jxlc_content(move(jxlc_content)) |
3678 | 0 | { |
3679 | 0 | } |
3680 | | |
3681 | 0 | JPEGXLImageDecoderPlugin::~JPEGXLImageDecoderPlugin() = default; |
3682 | | |
3683 | | IntSize JPEGXLImageDecoderPlugin::size() |
3684 | 0 | { |
3685 | 0 | return m_context->size(); |
3686 | 0 | } |
3687 | | |
3688 | | static bool is_raw_codestream(ReadonlyBytes data) |
3689 | 0 | { |
3690 | 0 | return data.starts_with(to_array<u8>({ 0xFF, 0x0A })); |
3691 | 0 | } |
3692 | | |
3693 | | bool JPEGXLImageDecoderPlugin::sniff(ReadonlyBytes data) |
3694 | 0 | { |
3695 | | // 18181-2: 9.1 JPEG XL Signature box (JXL␣) |
3696 | 0 | static constexpr Array signature = to_array<u8>({ |
3697 | | // clang-format off |
3698 | 0 | 0x00, 0x00, 0x00, 0x0C, |
3699 | 0 | 0x4A, 0x58, 0x4C, 0x20, |
3700 | 0 | 0x0D, 0x0A, 0x87, 0x0A, |
3701 | | // clang-format on |
3702 | 0 | }); |
3703 | 0 | bool is_container = data.starts_with(signature); |
3704 | 0 | return is_raw_codestream(data) || is_container; |
3705 | 0 | } |
3706 | | |
3707 | | static ErrorOr<Vector<u8>> extract_codestream_from_container(NonnullOwnPtr<FixedMemoryStream> input) |
3708 | 0 | { |
3709 | 0 | auto box_reader = TRY(ISOBMFF::Reader::create(move(input))); |
3710 | 0 | auto box_list = TRY(box_reader.read_entire_file()); |
3711 | |
|
3712 | 0 | size_t jxlc_box_count = 0; |
3713 | 0 | size_t jxlp_box_count = 0; |
3714 | 0 | for (auto& box : box_list) { |
3715 | 0 | if (box->box_type() == ISOBMFF::BoxType::JPEGXLCodestreamBox) |
3716 | 0 | jxlc_box_count++; |
3717 | 0 | else if (box->box_type() == ISOBMFF::BoxType::JPEGXLPartialCodestreamBox) |
3718 | 0 | jxlp_box_count++; |
3719 | 0 | } |
3720 | | |
3721 | | // "A JPEG XL file shall contain either exactly one JPEG XL codestream box, or one or more JPEG XL partial |
3722 | | // codestream boxes, but not both." |
3723 | 0 | if (jxlc_box_count == 0 && jxlp_box_count == 0) |
3724 | 0 | return Error::from_string_literal("JPEGXLLoader: No jxlc box and no jxlp boxes found"); |
3725 | 0 | if (jxlc_box_count > 1) |
3726 | 0 | return Error::from_string_literal("JPEGXLLoader: Multiple jxlc boxes found"); |
3727 | 0 | if (jxlp_box_count > 0 && jxlc_box_count > 0) |
3728 | 0 | return Error::from_string_literal("JPEGXLLoader: Both jxlc box and jxlp boxes found"); |
3729 | | |
3730 | 0 | if (jxlc_box_count > 0) { |
3731 | 0 | auto& box = *box_list.find_if([](auto& box) { return box->box_type() == ISOBMFF::BoxType::JPEGXLCodestreamBox; }); |
3732 | 0 | auto& codestream_box = static_cast<ISOBMFF::JPEGXLCodestreamBox&>(*box); |
3733 | 0 | return move(codestream_box.codestream); |
3734 | 0 | } |
3735 | | |
3736 | | // "The index modulo 2^31 shall be 0 for the first partial |
3737 | | // codestream box, and incremented by 1 for each next partial codestream box. The index shall be lower |
3738 | | // than 2^31, except for the last partial codestream box, which shall have an index of at least 2^31. The boxes |
3739 | | // shall appear in the file in order of increasing index. The full concatenation of all partial codestream |
3740 | | // boxes in this order shall form exactly one complete and valid JPEG XL codestream." |
3741 | | // FIXME: Try to prevent the extra copy, maybe with a non-contiguous steam class. |
3742 | 0 | VERIFY(jxlp_box_count > 0); |
3743 | 0 | size_t next_part_index = 0; |
3744 | 0 | Vector<u8> codestream; |
3745 | 0 | for (auto& box : box_list) { |
3746 | 0 | if (box->box_type() != ISOBMFF::BoxType::JPEGXLPartialCodestreamBox) |
3747 | 0 | continue; |
3748 | 0 | auto& partial_box = static_cast<ISOBMFF::JPEGXLPartialCodestreamBox&>(*box); |
3749 | |
|
3750 | 0 | if (partial_box.index() != next_part_index) |
3751 | 0 | return Error::from_string_literal("JPEGXLLoader: Partial box indices not sequential"); |
3752 | 0 | ++next_part_index; |
3753 | |
|
3754 | 0 | bool is_last_box = next_part_index == jxlp_box_count; |
3755 | 0 | if (partial_box.is_last() != is_last_box) |
3756 | 0 | return Error::from_string_literal("JPEGXLLoader: Invalid is_last bit on partial box"); |
3757 | | |
3758 | 0 | TRY(codestream.try_extend(partial_box.codestream)); |
3759 | 0 | } |
3760 | 0 | return codestream; |
3761 | 0 | } |
3762 | | |
3763 | | ErrorOr<NonnullOwnPtr<ImageDecoderPlugin>> JPEGXLImageDecoderPlugin::create(ReadonlyBytes data) |
3764 | 0 | { |
3765 | 0 | auto stream = TRY(try_make<FixedMemoryStream>(data)); |
3766 | 0 | Optional<Vector<u8>> jxlc_content; |
3767 | 0 | if (!is_raw_codestream(data)) { |
3768 | 0 | jxlc_content = TRY(extract_codestream_from_container(move(stream))); |
3769 | 0 | stream = TRY(try_make<FixedMemoryStream>(jxlc_content->span())); |
3770 | 0 | } |
3771 | 0 | auto plugin = TRY(adopt_nonnull_own_or_enomem(new (nothrow) JPEGXLImageDecoderPlugin(move(jxlc_content), move(stream)))); |
3772 | 0 | TRY(plugin->m_context->decode_image_header()); |
3773 | 0 | return plugin; |
3774 | 0 | } |
3775 | | |
3776 | | bool JPEGXLImageDecoderPlugin::is_animated() |
3777 | 0 | { |
3778 | 0 | return false; |
3779 | 0 | } |
3780 | | |
3781 | | size_t JPEGXLImageDecoderPlugin::loop_count() |
3782 | 0 | { |
3783 | 0 | return 0; |
3784 | 0 | } |
3785 | | |
3786 | | size_t JPEGXLImageDecoderPlugin::frame_count() |
3787 | 0 | { |
3788 | 0 | return 1; |
3789 | 0 | } |
3790 | | |
3791 | | size_t JPEGXLImageDecoderPlugin::first_animated_frame_index() |
3792 | 0 | { |
3793 | 0 | return 0; |
3794 | 0 | } |
3795 | | |
3796 | | ErrorOr<ImageFrameDescriptor> JPEGXLImageDecoderPlugin::frame(size_t index, Optional<IntSize>) |
3797 | 0 | { |
3798 | 0 | if (index > 0) |
3799 | 0 | return Error::from_string_literal("JPEGXLImageDecoderPlugin: Invalid frame index"); |
3800 | | |
3801 | 0 | if (m_context->state() == JPEGXL::LoadingContext::State::Error) |
3802 | 0 | return Error::from_string_literal("JPEGXLImageDecoderPlugin: Decoding failed"); |
3803 | | |
3804 | 0 | if (m_context->state() < JPEGXL::LoadingContext::State::FrameDecoded) |
3805 | 0 | TRY(m_context->decode()); |
3806 | |
|
3807 | 0 | if (m_context->cmyk_bitmap() && !m_context->bitmap()) |
3808 | 0 | return ImageFrameDescriptor { TRY(m_context->cmyk_bitmap()->to_low_quality_rgb()), 0 }; |
3809 | | |
3810 | 0 | return ImageFrameDescriptor { m_context->bitmap(), 0 }; |
3811 | 0 | } |
3812 | | |
3813 | | ErrorOr<NonnullRefPtr<CMYKBitmap>> JPEGXLImageDecoderPlugin::cmyk_frame() |
3814 | 0 | { |
3815 | 0 | if (m_context->state() == JPEGXL::LoadingContext::State::Error) |
3816 | 0 | return Error::from_string_literal("JPEGXLImageDecoderPlugin: Decoding failed"); |
3817 | | |
3818 | 0 | if (m_context->state() < JPEGXL::LoadingContext::State::FrameDecoded) |
3819 | 0 | TRY(m_context->decode()); |
3820 | |
|
3821 | 0 | VERIFY(m_context->cmyk_bitmap() && !m_context->bitmap()); |
3822 | 0 | return *m_context->cmyk_bitmap(); |
3823 | 0 | } |
3824 | | |
3825 | | NaturalFrameFormat JPEGXLImageDecoderPlugin::natural_frame_format() const |
3826 | 0 | { |
3827 | 0 | return m_context->is_cmyk() ? NaturalFrameFormat::CMYK : NaturalFrameFormat::RGB; |
3828 | 0 | } |
3829 | | |
3830 | | ErrorOr<Optional<ReadonlyBytes>> JPEGXLImageDecoderPlugin::icc_data() |
3831 | 0 | { |
3832 | 0 | if (m_context->state() < JPEGXL::LoadingContext::State::ICCProfileDecoded) |
3833 | 0 | TRY(m_context->decode_icc()); |
3834 | 0 | if (m_context->icc_profile().size() == 0) |
3835 | 0 | return OptionalNone {}; |
3836 | 0 | return m_context->icc_profile(); |
3837 | 0 | } |
3838 | | |
3839 | | } |
3840 | | |
3841 | | namespace AK { |
3842 | | |
3843 | | template<> |
3844 | | struct Formatter<Gfx::JPEGXL::Encoding> : Formatter<StringView> { |
3845 | | ErrorOr<void> format(FormatBuilder& builder, Gfx::JPEGXL::Encoding const& header) |
3846 | 0 | { |
3847 | 0 | auto string = "Unknown"sv; |
3848 | 0 | switch (header) { |
3849 | 0 | case Gfx::JPEGXL::Encoding::kVarDCT: |
3850 | 0 | string = "VarDCT"sv; |
3851 | 0 | break; |
3852 | 0 | case Gfx::JPEGXL::Encoding::kModular: |
3853 | 0 | string = "Modular"sv; |
3854 | 0 | break; |
3855 | 0 | default: |
3856 | 0 | break; |
3857 | 0 | } |
3858 | 0 |
|
3859 | 0 | return Formatter<StringView>::format(builder, string); |
3860 | 0 | } |
3861 | | }; |
3862 | | |
3863 | | template<> |
3864 | | struct Formatter<Gfx::JPEGXL::FrameHeader::FrameType> : Formatter<StringView> { |
3865 | | ErrorOr<void> format(FormatBuilder& builder, Gfx::JPEGXL::FrameHeader::FrameType const& header) |
3866 | 0 | { |
3867 | 0 | switch (header) { |
3868 | 0 | case Gfx::JPEGXL::FrameHeader::FrameType::kRegularFrame: |
3869 | 0 | return Formatter<StringView>::format(builder, "RegularFrame"sv); |
3870 | 0 | case Gfx::JPEGXL::FrameHeader::FrameType::kLFFrame: |
3871 | 0 | return Formatter<StringView>::format(builder, "LFFrame"sv); |
3872 | 0 | case Gfx::JPEGXL::FrameHeader::FrameType::kReferenceOnly: |
3873 | 0 | return Formatter<StringView>::format(builder, "ReferenceOnly"sv); |
3874 | 0 | case Gfx::JPEGXL::FrameHeader::FrameType::kSkipProgressive: |
3875 | 0 | return Formatter<StringView>::format(builder, "SkipProgressive"sv); |
3876 | 0 | } |
3877 | 0 | VERIFY_NOT_REACHED(); |
3878 | 0 | } |
3879 | | }; |
3880 | | |
3881 | | } |