/src/libjxl/lib/jxl/test_utils.cc
Line | Count | Source |
1 | | // Copyright (c) the JPEG XL Project Authors. All rights reserved. |
2 | | // |
3 | | // Use of this source code is governed by a BSD-style |
4 | | // license that can be found in the LICENSE file. |
5 | | |
6 | | #include "lib/jxl/test_utils.h" |
7 | | |
8 | | #include <jxl/cms.h> |
9 | | #include <jxl/cms_interface.h> |
10 | | #include <jxl/codestream_header.h> |
11 | | #include <jxl/encode.h> |
12 | | #include <jxl/memory_manager.h> |
13 | | #include <jxl/types.h> |
14 | | |
15 | | #include <algorithm> |
16 | | #include <cmath> |
17 | | #include <cstddef> |
18 | | #include <cstdint> |
19 | | #include <cstdio> |
20 | | #include <cstring> |
21 | | #include <fstream> |
22 | | #include <ios> |
23 | | #include <iterator> |
24 | | #include <memory> |
25 | | #include <sstream> |
26 | | #include <string> |
27 | | #include <utility> |
28 | | #include <vector> |
29 | | |
30 | | #include "lib/extras/codec_in_out.h" |
31 | | #include "lib/extras/dec/jxl.h" |
32 | | #include "lib/extras/enc/jxl.h" |
33 | | #include "lib/extras/metrics.h" |
34 | | #include "lib/extras/packed_image.h" |
35 | | #include "lib/extras/packed_image_convert.h" |
36 | | #include "lib/jxl/base/byte_order.h" |
37 | | #include "lib/jxl/base/common.h" |
38 | | #include "lib/jxl/base/compiler_specific.h" |
39 | | #include "lib/jxl/base/data_parallel.h" |
40 | | #include "lib/jxl/base/float.h" |
41 | | #include "lib/jxl/base/printf_macros.h" |
42 | | #include "lib/jxl/base/span.h" |
43 | | #include "lib/jxl/base/status.h" |
44 | | #include "lib/jxl/butteraugli/butteraugli.h" |
45 | | #include "lib/jxl/cms/color_encoding_cms.h" |
46 | | #include "lib/jxl/color_encoding_internal.h" |
47 | | #include "lib/jxl/dec_bit_reader.h" |
48 | | #include "lib/jxl/enc_aux_out.h" |
49 | | #include "lib/jxl/enc_bit_writer.h" |
50 | | #include "lib/jxl/enc_butteraugli_comparator.h" |
51 | | #include "lib/jxl/enc_cache.h" |
52 | | #include "lib/jxl/enc_comparator.h" |
53 | | #include "lib/jxl/enc_external_image.h" |
54 | | #include "lib/jxl/enc_fields.h" |
55 | | #include "lib/jxl/enc_frame.h" |
56 | | #include "lib/jxl/enc_icc_codec.h" |
57 | | #include "lib/jxl/enc_params.h" |
58 | | #include "lib/jxl/field_encodings.h" |
59 | | #include "lib/jxl/frame_header.h" |
60 | | #include "lib/jxl/icc_codec.h" |
61 | | #include "lib/jxl/image.h" |
62 | | #include "lib/jxl/image_bundle.h" |
63 | | #include "lib/jxl/image_metadata.h" |
64 | | #include "lib/jxl/jpeg/enc_jpeg_data.h" |
65 | | #include "lib/jxl/jpeg/jpeg_data.h" |
66 | | #include "lib/jxl/luminance.h" |
67 | | #include "lib/jxl/padded_bytes.h" |
68 | | #include "lib/jxl/test_memory_manager.h" |
69 | | |
70 | | #if !defined(TEST_DATA_PATH) |
71 | | #include "tools/cpp/runfiles/runfiles.h" |
72 | | #endif |
73 | | |
74 | | namespace jxl { |
75 | | namespace test { |
76 | | |
77 | 0 | void CheckImpl(bool ok, const char* conndition, const char* file, int line) { |
78 | 0 | if (!ok) { |
79 | 0 | fprintf(stderr, "Check(%s) failed at %s:%d\n", conndition, file, line); |
80 | 0 | JXL_CRASH(); |
81 | 0 | } |
82 | 0 | } |
83 | | |
84 | | #if defined(TEST_DATA_PATH) |
85 | 0 | std::string GetTestDataPath(const std::string& filename) { |
86 | 0 | return std::string(TEST_DATA_PATH "/") + filename; |
87 | 0 | } |
88 | | #else |
89 | | using ::bazel::tools::cpp::runfiles::Runfiles; |
90 | | const std::unique_ptr<Runfiles> kRunfiles(Runfiles::Create("")); |
91 | | std::string GetTestDataPath(const std::string& filename) { |
92 | | std::string root(JPEGXL_ROOT_PACKAGE "/testdata/"); |
93 | | return kRunfiles->Rlocation(root + filename); |
94 | | } |
95 | | #endif |
96 | | |
97 | 0 | jxl::IccBytes GetIccTestProfile() { |
98 | 0 | return ReadTestData("external/Compact-ICC-Profiles/profiles/scRGB-v2.icc"); |
99 | 0 | } |
100 | | |
101 | 0 | std::vector<uint8_t> GetCompressedIccTestProfile() { |
102 | 0 | BitWriter writer(MemoryManager()); |
103 | 0 | const IccBytes icc = GetIccTestProfile(); |
104 | 0 | Check( |
105 | 0 | WriteICC(Span<const uint8_t>(icc), &writer, LayerType::Header, nullptr)); |
106 | 0 | writer.ZeroPadToByte(); |
107 | 0 | jxl::Bytes bytes = writer.GetSpan(); |
108 | 0 | return std::vector<uint8_t>(bytes.begin(), bytes.end()); |
109 | 0 | } |
110 | | |
111 | 0 | std::vector<uint8_t> ReadTestData(const std::string& filename) { |
112 | 0 | std::string full_path = GetTestDataPath(filename); |
113 | 0 | fprintf(stderr, "ReadTestData %s\n", full_path.c_str()); |
114 | 0 | std::ifstream file(full_path, std::ios::binary); |
115 | 0 | std::vector<char> str((std::istreambuf_iterator<char>(file)), |
116 | 0 | std::istreambuf_iterator<char>()); |
117 | 0 | Check(file.good()); |
118 | 0 | const uint8_t* raw = reinterpret_cast<const uint8_t*>(str.data()); |
119 | 0 | std::vector<uint8_t> data(raw, raw + str.size()); |
120 | 0 | printf("Test data %s is %d bytes long.\n", filename.c_str(), |
121 | 0 | static_cast<int>(data.size())); |
122 | 0 | return data; |
123 | 0 | } |
124 | | |
125 | 0 | void DefaultAcceptedFormats(extras::JXLDecompressParams& dparams) { |
126 | 0 | if (dparams.accepted_formats.empty()) { |
127 | 0 | for (const uint32_t num_channels : {1, 2, 3, 4}) { |
128 | 0 | dparams.accepted_formats.push_back( |
129 | 0 | {num_channels, JXL_TYPE_FLOAT, JXL_LITTLE_ENDIAN, /*align=*/0}); |
130 | 0 | } |
131 | 0 | } |
132 | 0 | } |
133 | | |
134 | | Status DecodeFile(const extras::JXLDecompressParams& dparams, |
135 | | const Span<const uint8_t> file, CodecInOut* JXL_RESTRICT io, |
136 | 0 | ThreadPool* pool) { |
137 | 0 | extras::JXLDecompressParams local_dparams(dparams); |
138 | 0 | DefaultAcceptedFormats(local_dparams); |
139 | 0 | SetThreadParallelRunner(dparams, pool); |
140 | 0 | extras::PackedPixelFile ppf; |
141 | 0 | JXL_RETURN_IF_ERROR(DecodeImageJXL(file.data(), file.size(), local_dparams, |
142 | 0 | /*decoded_bytes=*/nullptr, &ppf)); |
143 | 0 | JXL_RETURN_IF_ERROR(ConvertPackedPixelFileToCodecInOut(ppf, pool, io)); |
144 | 0 | return true; |
145 | 0 | } |
146 | | |
147 | | void JxlBasicInfoSetFromPixelFormat(JxlBasicInfo* basic_info, |
148 | 0 | const JxlPixelFormat* pixel_format) { |
149 | 0 | JxlEncoderInitBasicInfo(basic_info); |
150 | 0 | switch (pixel_format->data_type) { |
151 | 0 | case JXL_TYPE_FLOAT: |
152 | 0 | basic_info->bits_per_sample = 32; |
153 | 0 | basic_info->exponent_bits_per_sample = 8; |
154 | 0 | break; |
155 | 0 | case JXL_TYPE_FLOAT16: |
156 | 0 | basic_info->bits_per_sample = 16; |
157 | 0 | basic_info->exponent_bits_per_sample = 5; |
158 | 0 | break; |
159 | 0 | case JXL_TYPE_UINT8: |
160 | 0 | basic_info->bits_per_sample = 8; |
161 | 0 | basic_info->exponent_bits_per_sample = 0; |
162 | 0 | break; |
163 | 0 | case JXL_TYPE_UINT16: |
164 | 0 | basic_info->bits_per_sample = 16; |
165 | 0 | basic_info->exponent_bits_per_sample = 0; |
166 | 0 | break; |
167 | 0 | default: |
168 | 0 | Check(false); |
169 | 0 | } |
170 | 0 | if (pixel_format->num_channels < 3) { |
171 | 0 | basic_info->num_color_channels = 1; |
172 | 0 | } else { |
173 | 0 | basic_info->num_color_channels = 3; |
174 | 0 | } |
175 | 0 | if (pixel_format->num_channels == 2 || pixel_format->num_channels == 4) { |
176 | 0 | basic_info->alpha_exponent_bits = basic_info->exponent_bits_per_sample; |
177 | 0 | basic_info->alpha_bits = basic_info->bits_per_sample; |
178 | 0 | basic_info->num_extra_channels = 1; |
179 | 0 | } else { |
180 | 0 | basic_info->alpha_exponent_bits = 0; |
181 | 0 | basic_info->alpha_bits = 0; |
182 | 0 | } |
183 | 0 | } |
184 | | |
185 | 0 | ColorEncoding ColorEncodingFromDescriptor(const ColorEncodingDescriptor& desc) { |
186 | 0 | ColorEncoding c; |
187 | 0 | c.SetColorSpace(desc.color_space); |
188 | 0 | if (desc.color_space != ColorSpace::kXYB) { |
189 | 0 | Check(c.SetWhitePointType(desc.white_point)); |
190 | 0 | if (desc.color_space != ColorSpace::kGray) { |
191 | 0 | Check(c.SetPrimariesType(desc.primaries)); |
192 | 0 | } |
193 | 0 | c.Tf().SetTransferFunction(desc.tf); |
194 | 0 | } |
195 | 0 | c.SetRenderingIntent(desc.rendering_intent); |
196 | 0 | Check(c.CreateICC()); |
197 | 0 | return c; |
198 | 0 | } |
199 | | |
200 | | namespace { |
201 | | void CheckSameEncodings(const std::vector<ColorEncoding>& a, |
202 | | const std::vector<ColorEncoding>& b, |
203 | | const std::string& check_name, |
204 | 0 | std::stringstream& failures) { |
205 | 0 | Check(a.size() == b.size()); |
206 | 0 | for (size_t i = 0; i < a.size(); ++i) { |
207 | 0 | if ((a[i].ICC() == b[i].ICC()) || |
208 | 0 | ((a[i].GetPrimariesType() == b[i].GetPrimariesType()) && |
209 | 0 | a[i].Tf().IsSame(b[i].Tf()))) { |
210 | 0 | continue; |
211 | 0 | } |
212 | 0 | failures << "CheckSameEncodings " << check_name << ": " << i |
213 | 0 | << "-th encoding mismatch\n"; |
214 | 0 | } |
215 | 0 | } |
216 | | } // namespace |
217 | | |
218 | | bool Roundtrip(CodecInOut* io, const CompressParams& cparams, |
219 | | const extras::JXLDecompressParams& dparams, |
220 | | CodecInOut* JXL_RESTRICT io2, std::stringstream& failures, |
221 | 0 | size_t* compressed_size, ThreadPool* pool) { |
222 | 0 | extras::JXLDecompressParams local_dparams(dparams); |
223 | 0 | DefaultAcceptedFormats(local_dparams); |
224 | 0 | if (compressed_size) { |
225 | 0 | *compressed_size = static_cast<size_t>(-1); |
226 | 0 | } |
227 | 0 | std::vector<uint8_t> compressed; |
228 | |
|
229 | 0 | std::vector<ColorEncoding> original_metadata_encodings; |
230 | 0 | std::vector<ColorEncoding> original_current_encodings; |
231 | 0 | std::vector<ColorEncoding> metadata_encodings_1; |
232 | 0 | std::vector<ColorEncoding> metadata_encodings_2; |
233 | 0 | std::vector<ColorEncoding> current_encodings_2; |
234 | 0 | original_metadata_encodings.reserve(io->frames.size()); |
235 | 0 | original_current_encodings.reserve(io->frames.size()); |
236 | 0 | metadata_encodings_1.reserve(io->frames.size()); |
237 | 0 | metadata_encodings_2.reserve(io->frames.size()); |
238 | 0 | current_encodings_2.reserve(io->frames.size()); |
239 | |
|
240 | 0 | for (const ImageBundle& ib : io->frames) { |
241 | | // Remember original encoding, will be returned by decoder. |
242 | 0 | original_metadata_encodings.push_back(ib.metadata()->color_encoding); |
243 | | // c_current should not change during encoding. |
244 | 0 | original_current_encodings.push_back(ib.c_current()); |
245 | 0 | } |
246 | |
|
247 | 0 | Check(test::EncodeFile(cparams, io, &compressed, pool)); |
248 | |
|
249 | 0 | for (const ImageBundle& ib1 : io->frames) { |
250 | 0 | metadata_encodings_1.push_back(ib1.metadata()->color_encoding); |
251 | 0 | } |
252 | | |
253 | | // Should still be in the same color space after encoding. |
254 | 0 | CheckSameEncodings(metadata_encodings_1, original_metadata_encodings, |
255 | 0 | "original vs after encoding", failures); |
256 | |
|
257 | 0 | Check(DecodeFile(local_dparams, Bytes(compressed), io2, pool)); |
258 | 0 | Check(io2->frames.size() == io->frames.size()); |
259 | |
|
260 | 0 | for (const ImageBundle& ib2 : io2->frames) { |
261 | 0 | metadata_encodings_2.push_back(ib2.metadata()->color_encoding); |
262 | 0 | current_encodings_2.push_back(ib2.c_current()); |
263 | 0 | } |
264 | | |
265 | | // We always produce the original color encoding if a color transform hook is |
266 | | // set. |
267 | 0 | CheckSameEncodings(current_encodings_2, original_current_encodings, |
268 | 0 | "current: original vs decoded", failures); |
269 | | |
270 | | // Decoder returns the originals passed to the encoder. |
271 | 0 | CheckSameEncodings(metadata_encodings_2, original_metadata_encodings, |
272 | 0 | "metadata: original vs decoded", failures); |
273 | |
|
274 | 0 | if (compressed_size) { |
275 | 0 | *compressed_size = compressed.size(); |
276 | 0 | } |
277 | |
|
278 | 0 | return failures.str().empty(); |
279 | 0 | } |
280 | | |
281 | | size_t Roundtrip(const extras::PackedPixelFile& ppf_in, |
282 | | const extras::JXLCompressParams& cparams, |
283 | | const extras::JXLDecompressParams& dparams, ThreadPool* pool, |
284 | 0 | extras::PackedPixelFile* ppf_out, size_t* decoded_size) { |
285 | 0 | extras::JXLDecompressParams local_dparams(dparams); |
286 | 0 | DefaultAcceptedFormats(local_dparams); |
287 | 0 | SetThreadParallelRunner(cparams, pool); |
288 | 0 | SetThreadParallelRunner(local_dparams, pool); |
289 | 0 | std::vector<uint8_t> compressed; |
290 | 0 | Check(extras::EncodeImageJXL(cparams, ppf_in, /*jpeg_bytes=*/nullptr, |
291 | 0 | &compressed)); |
292 | 0 | size_t decoded_bytes = 0; |
293 | 0 | Check(extras::DecodeImageJXL(compressed.data(), compressed.size(), |
294 | 0 | local_dparams, &decoded_bytes, ppf_out)); |
295 | 0 | if (decoded_size) { |
296 | 0 | *decoded_size = decoded_bytes; |
297 | 0 | } else { |
298 | 0 | Check(decoded_bytes == compressed.size()); |
299 | 0 | } |
300 | 0 | return compressed.size(); |
301 | 0 | } |
302 | | |
303 | 0 | std::vector<ColorEncodingDescriptor> AllEncodings() { |
304 | 0 | std::vector<ColorEncodingDescriptor> all_encodings; |
305 | 0 | all_encodings.reserve(300); |
306 | |
|
307 | 0 | for (ColorSpace cs : Values<ColorSpace>()) { |
308 | 0 | if (cs == ColorSpace::kUnknown || cs == ColorSpace::kXYB || |
309 | 0 | cs == ColorSpace::kGray) { |
310 | 0 | continue; |
311 | 0 | } |
312 | | |
313 | 0 | for (WhitePoint wp : Values<WhitePoint>()) { |
314 | 0 | if (wp == WhitePoint::kCustom) continue; |
315 | 0 | for (Primaries primaries : Values<Primaries>()) { |
316 | 0 | if (primaries == Primaries::kCustom) continue; |
317 | 0 | for (TransferFunction tf : Values<TransferFunction>()) { |
318 | 0 | if (tf == TransferFunction::kUnknown) continue; |
319 | 0 | for (RenderingIntent ri : Values<RenderingIntent>()) { |
320 | 0 | ColorEncodingDescriptor cdesc; |
321 | 0 | cdesc.color_space = cs; |
322 | 0 | cdesc.white_point = wp; |
323 | 0 | cdesc.primaries = primaries; |
324 | 0 | cdesc.tf = tf; |
325 | 0 | cdesc.rendering_intent = ri; |
326 | 0 | all_encodings.push_back(cdesc); |
327 | 0 | } |
328 | 0 | } |
329 | 0 | } |
330 | 0 | } |
331 | 0 | } |
332 | |
|
333 | 0 | return all_encodings; |
334 | 0 | } |
335 | | |
336 | | std::unique_ptr<jxl::CodecInOut> SomeTestImageToCodecInOut( |
337 | | const std::vector<uint8_t>& buf, size_t num_channels, size_t xsize, |
338 | 0 | size_t ysize) { |
339 | 0 | JxlMemoryManager* memory_manager = jxl::test::MemoryManager(); |
340 | 0 | auto io = jxl::make_unique<jxl::CodecInOut>(memory_manager); |
341 | 0 | Check(io->SetSize(xsize, ysize)); |
342 | 0 | io->metadata.m.SetAlphaBits(16); |
343 | 0 | io->metadata.m.color_encoding = jxl::ColorEncoding::SRGB( |
344 | 0 | /*is_gray=*/num_channels == 1 || num_channels == 2); |
345 | 0 | JxlPixelFormat format = {static_cast<uint32_t>(num_channels), JXL_TYPE_UINT16, |
346 | 0 | JXL_BIG_ENDIAN, 0}; |
347 | 0 | Check(ConvertFromExternal( |
348 | 0 | jxl::Bytes(buf), xsize, ysize, |
349 | 0 | jxl::ColorEncoding::SRGB(/*is_gray=*/num_channels < 3), |
350 | 0 | /*bits_per_sample=*/16, format, |
351 | 0 | /*pool=*/nullptr, |
352 | 0 | /*ib=*/&io->Main())); |
353 | 0 | return io; |
354 | 0 | } |
355 | | |
356 | 0 | bool Near(double expected, double value, double max_dist) { |
357 | 0 | double dist = expected > value ? expected - value : value - expected; |
358 | 0 | return dist <= max_dist; |
359 | 0 | } |
360 | | |
361 | 0 | float LoadLEFloat16(const uint8_t* p) { |
362 | 0 | uint16_t bits16 = LoadLE16(p); |
363 | 0 | return detail::LoadFloat16(bits16); |
364 | 0 | } |
365 | | |
366 | 0 | float LoadBEFloat16(const uint8_t* p) { |
367 | 0 | uint16_t bits16 = LoadBE16(p); |
368 | 0 | return detail::LoadFloat16(bits16); |
369 | 0 | } |
370 | | |
371 | 0 | size_t GetPrecision(JxlDataType data_type) { |
372 | 0 | switch (data_type) { |
373 | 0 | case JXL_TYPE_UINT8: |
374 | 0 | return 8; |
375 | 0 | case JXL_TYPE_UINT16: |
376 | 0 | return 16; |
377 | 0 | case JXL_TYPE_FLOAT: |
378 | | // Floating point mantissa precision |
379 | 0 | return 24; |
380 | 0 | case JXL_TYPE_FLOAT16: |
381 | 0 | return 11; |
382 | 0 | default: |
383 | 0 | Check(false); |
384 | 0 | return 0; |
385 | 0 | } |
386 | 0 | } |
387 | | |
388 | 0 | size_t GetDataBits(JxlDataType data_type) { |
389 | 0 | switch (data_type) { |
390 | 0 | case JXL_TYPE_UINT8: |
391 | 0 | return 8; |
392 | 0 | case JXL_TYPE_UINT16: |
393 | 0 | return 16; |
394 | 0 | case JXL_TYPE_FLOAT: |
395 | 0 | return 32; |
396 | 0 | case JXL_TYPE_FLOAT16: |
397 | 0 | return 16; |
398 | 0 | default: |
399 | 0 | Check(false); |
400 | 0 | return 0; |
401 | 0 | } |
402 | 0 | } |
403 | | |
404 | | std::vector<double> ConvertToRGBA32(const uint8_t* pixels, size_t xsize, |
405 | | size_t ysize, const JxlPixelFormat& format, |
406 | 0 | double factor) { |
407 | 0 | std::vector<double> result(xsize * ysize * 4); |
408 | 0 | size_t num_channels = format.num_channels; |
409 | 0 | bool gray = num_channels == 1 || num_channels == 2; |
410 | 0 | bool alpha = num_channels == 2 || num_channels == 4; |
411 | 0 | JxlEndianness endianness = format.endianness; |
412 | | // Compute actual type: |
413 | 0 | if (endianness == JXL_NATIVE_ENDIAN) { |
414 | 0 | endianness = IsLittleEndian() ? JXL_LITTLE_ENDIAN : JXL_BIG_ENDIAN; |
415 | 0 | } |
416 | |
|
417 | 0 | size_t stride = |
418 | 0 | xsize * jxl::DivCeil(GetDataBits(format.data_type) * num_channels, |
419 | 0 | jxl::kBitsPerByte); |
420 | 0 | if (format.align > 1) stride = jxl::RoundUpTo(stride, format.align); |
421 | |
|
422 | 0 | if (format.data_type == JXL_TYPE_UINT8) { |
423 | | // Multiplier to bring to 0-1.0 range |
424 | 0 | double mul = factor > 0.0 ? factor : 1.0 / 255.0; |
425 | 0 | for (size_t y = 0; y < ysize; ++y) { |
426 | 0 | for (size_t x = 0; x < xsize; ++x) { |
427 | 0 | size_t j = (y * xsize + x) * 4; |
428 | 0 | size_t i = y * stride + x * num_channels; |
429 | 0 | double r = pixels[i]; |
430 | 0 | double g = gray ? r : pixels[i + 1]; |
431 | 0 | double b = gray ? r : pixels[i + 2]; |
432 | 0 | double a = alpha ? pixels[i + num_channels - 1] : 255; |
433 | 0 | result[j + 0] = r * mul; |
434 | 0 | result[j + 1] = g * mul; |
435 | 0 | result[j + 2] = b * mul; |
436 | 0 | result[j + 3] = a * mul; |
437 | 0 | } |
438 | 0 | } |
439 | 0 | } else if (format.data_type == JXL_TYPE_UINT16) { |
440 | 0 | Check(endianness != JXL_NATIVE_ENDIAN); |
441 | | // Multiplier to bring to 0-1.0 range |
442 | 0 | double mul = factor > 0.0 ? factor : 1.0 / 65535.0; |
443 | 0 | for (size_t y = 0; y < ysize; ++y) { |
444 | 0 | for (size_t x = 0; x < xsize; ++x) { |
445 | 0 | size_t j = (y * xsize + x) * 4; |
446 | 0 | size_t i = y * stride + x * num_channels * 2; |
447 | 0 | double r; |
448 | 0 | double g; |
449 | 0 | double b; |
450 | 0 | double a; |
451 | 0 | if (endianness == JXL_BIG_ENDIAN) { |
452 | 0 | r = (pixels[i + 0] << 8) + pixels[i + 1]; |
453 | 0 | g = gray ? r : (pixels[i + 2] << 8) + pixels[i + 3]; |
454 | 0 | b = gray ? r : (pixels[i + 4] << 8) + pixels[i + 5]; |
455 | 0 | a = alpha ? (pixels[i + num_channels * 2 - 2] << 8) + |
456 | 0 | pixels[i + num_channels * 2 - 1] |
457 | 0 | : 65535; |
458 | 0 | } else { |
459 | 0 | r = (pixels[i + 1] << 8) + pixels[i + 0]; |
460 | 0 | g = gray ? r : (pixels[i + 3] << 8) + pixels[i + 2]; |
461 | 0 | b = gray ? r : (pixels[i + 5] << 8) + pixels[i + 4]; |
462 | 0 | a = alpha ? (pixels[i + num_channels * 2 - 1] << 8) + |
463 | 0 | pixels[i + num_channels * 2 - 2] |
464 | 0 | : 65535; |
465 | 0 | } |
466 | 0 | result[j + 0] = r * mul; |
467 | 0 | result[j + 1] = g * mul; |
468 | 0 | result[j + 2] = b * mul; |
469 | 0 | result[j + 3] = a * mul; |
470 | 0 | } |
471 | 0 | } |
472 | 0 | } else if (format.data_type == JXL_TYPE_FLOAT) { |
473 | 0 | Check(endianness != JXL_NATIVE_ENDIAN); |
474 | 0 | for (size_t y = 0; y < ysize; ++y) { |
475 | 0 | for (size_t x = 0; x < xsize; ++x) { |
476 | 0 | size_t j = (y * xsize + x) * 4; |
477 | 0 | size_t i = y * stride + x * num_channels * 4; |
478 | 0 | double r; |
479 | 0 | double g; |
480 | 0 | double b; |
481 | 0 | double a; |
482 | 0 | if (endianness == JXL_BIG_ENDIAN) { |
483 | 0 | r = LoadBEFloat(pixels + i); |
484 | 0 | g = gray ? r : LoadBEFloat(pixels + i + 4); |
485 | 0 | b = gray ? r : LoadBEFloat(pixels + i + 8); |
486 | 0 | a = alpha ? LoadBEFloat(pixels + i + num_channels * 4 - 4) : 1.0; |
487 | 0 | } else { |
488 | 0 | r = LoadLEFloat(pixels + i); |
489 | 0 | g = gray ? r : LoadLEFloat(pixels + i + 4); |
490 | 0 | b = gray ? r : LoadLEFloat(pixels + i + 8); |
491 | 0 | a = alpha ? LoadLEFloat(pixels + i + num_channels * 4 - 4) : 1.0; |
492 | 0 | } |
493 | 0 | result[j + 0] = r; |
494 | 0 | result[j + 1] = g; |
495 | 0 | result[j + 2] = b; |
496 | 0 | result[j + 3] = a; |
497 | 0 | } |
498 | 0 | } |
499 | 0 | } else if (format.data_type == JXL_TYPE_FLOAT16) { |
500 | 0 | Check(endianness != JXL_NATIVE_ENDIAN); |
501 | 0 | for (size_t y = 0; y < ysize; ++y) { |
502 | 0 | for (size_t x = 0; x < xsize; ++x) { |
503 | 0 | size_t j = (y * xsize + x) * 4; |
504 | 0 | size_t i = y * stride + x * num_channels * 2; |
505 | 0 | double r; |
506 | 0 | double g; |
507 | 0 | double b; |
508 | 0 | double a; |
509 | 0 | if (endianness == JXL_BIG_ENDIAN) { |
510 | 0 | r = LoadBEFloat16(pixels + i); |
511 | 0 | g = gray ? r : LoadBEFloat16(pixels + i + 2); |
512 | 0 | b = gray ? r : LoadBEFloat16(pixels + i + 4); |
513 | 0 | a = alpha ? LoadBEFloat16(pixels + i + num_channels * 2 - 2) : 1.0; |
514 | 0 | } else { |
515 | 0 | r = LoadLEFloat16(pixels + i); |
516 | 0 | g = gray ? r : LoadLEFloat16(pixels + i + 2); |
517 | 0 | b = gray ? r : LoadLEFloat16(pixels + i + 4); |
518 | 0 | a = alpha ? LoadLEFloat16(pixels + i + num_channels * 2 - 2) : 1.0; |
519 | 0 | } |
520 | 0 | result[j + 0] = r; |
521 | 0 | result[j + 1] = g; |
522 | 0 | result[j + 2] = b; |
523 | 0 | result[j + 3] = a; |
524 | 0 | } |
525 | 0 | } |
526 | 0 | } else { |
527 | 0 | Check(false); // Unsupported type |
528 | 0 | } |
529 | 0 | return result; |
530 | 0 | } |
531 | | |
532 | | size_t ComparePixels(const uint8_t* a, const uint8_t* b, size_t xsize, |
533 | | size_t ysize, const JxlPixelFormat& format_a, |
534 | | const JxlPixelFormat& format_b, |
535 | 0 | double threshold_multiplier) { |
536 | | // Convert both images to equal full precision for comparison. |
537 | 0 | std::vector<double> a_full = ConvertToRGBA32(a, xsize, ysize, format_a); |
538 | 0 | std::vector<double> b_full = ConvertToRGBA32(b, xsize, ysize, format_b); |
539 | 0 | bool gray_a = format_a.num_channels < 3; |
540 | 0 | bool gray_b = format_b.num_channels < 3; |
541 | 0 | bool alpha_a = ((format_a.num_channels & 1) == 0); |
542 | 0 | bool alpha_b = ((format_b.num_channels & 1) == 0); |
543 | 0 | size_t bits_a = GetPrecision(format_a.data_type); |
544 | 0 | size_t bits_b = GetPrecision(format_b.data_type); |
545 | 0 | size_t bits = std::min(bits_a, bits_b); |
546 | | // How much distance is allowed in case of pixels with lower bit depths, given |
547 | | // that the double precision float images use range 0-1.0. |
548 | | // E.g. in case of 1-bit this is 0.5 since 0.499 must map to 0 and 0.501 must |
549 | | // map to 1. |
550 | 0 | double precision = 0.5 * threshold_multiplier / ((1ull << bits) - 1ull); |
551 | 0 | if (format_a.data_type == JXL_TYPE_FLOAT16 || |
552 | 0 | format_b.data_type == JXL_TYPE_FLOAT16) { |
553 | | // Lower the precision for float16, because it currently looks like the |
554 | | // scalar and WASM implementations of hwy have 1 less bit of precision |
555 | | // than the x86 implementations. |
556 | | // TODO(lode): Set the required precision back to 11 bits when possible. |
557 | 0 | precision = 0.5 * threshold_multiplier / ((1ull << (bits - 1)) - 1ull); |
558 | 0 | } |
559 | 0 | if (format_b.data_type == JXL_TYPE_UINT8) { |
560 | | // Increase the threshold by the maximum difference introduced by dithering. |
561 | 0 | precision += 63.0 / 128.0; |
562 | 0 | } |
563 | 0 | size_t numdiff = 0; |
564 | 0 | for (size_t y = 0; y < ysize; y++) { |
565 | 0 | for (size_t x = 0; x < xsize; x++) { |
566 | 0 | size_t i = (y * xsize + x) * 4; |
567 | 0 | bool ok = true; |
568 | 0 | if (gray_a || gray_b) { |
569 | 0 | if (!Near(a_full[i + 0], b_full[i + 0], precision)) ok = false; |
570 | | // If the input was grayscale and the output not, then the output must |
571 | | // have all channels equal. |
572 | 0 | if (gray_a && b_full[i + 0] != b_full[i + 1] && |
573 | 0 | b_full[i + 2] != b_full[i + 2]) { |
574 | 0 | ok = false; |
575 | 0 | } |
576 | 0 | } else { |
577 | 0 | if (!Near(a_full[i + 0], b_full[i + 0], precision) || |
578 | 0 | !Near(a_full[i + 1], b_full[i + 1], precision) || |
579 | 0 | !Near(a_full[i + 2], b_full[i + 2], precision)) { |
580 | 0 | ok = false; |
581 | 0 | } |
582 | 0 | } |
583 | 0 | if (alpha_a && alpha_b) { |
584 | 0 | if (!Near(a_full[i + 3], b_full[i + 3], precision)) ok = false; |
585 | 0 | } else { |
586 | | // If the input had no alpha channel, the output should be opaque |
587 | | // after roundtrip. |
588 | 0 | if (alpha_b && !Near(1.0, b_full[i + 3], precision)) ok = false; |
589 | 0 | } |
590 | 0 | if (!ok) numdiff++; |
591 | 0 | } |
592 | 0 | } |
593 | 0 | return numdiff; |
594 | 0 | } |
595 | | |
596 | | double DistanceRMS(const uint8_t* a, const uint8_t* b, size_t xsize, |
597 | 0 | size_t ysize, const JxlPixelFormat& format) { |
598 | | // Convert both images to equal full precision for comparison. |
599 | 0 | std::vector<double> a_full = ConvertToRGBA32(a, xsize, ysize, format); |
600 | 0 | std::vector<double> b_full = ConvertToRGBA32(b, xsize, ysize, format); |
601 | 0 | double sum = 0.0; |
602 | 0 | for (size_t y = 0; y < ysize; y++) { |
603 | 0 | double row_sum = 0.0; |
604 | 0 | for (size_t x = 0; x < xsize; x++) { |
605 | 0 | size_t i = (y * xsize + x) * 4; |
606 | 0 | for (size_t c = 0; c < format.num_channels; ++c) { |
607 | 0 | double diff = a_full[i + c] - b_full[i + c]; |
608 | 0 | row_sum += diff * diff; |
609 | 0 | } |
610 | 0 | } |
611 | 0 | sum += row_sum; |
612 | 0 | } |
613 | 0 | sum /= (xsize * ysize); |
614 | 0 | return sqrt(sum); |
615 | 0 | } |
616 | | |
617 | | float ButteraugliDistance(const extras::PackedPixelFile& a, |
618 | 0 | const extras::PackedPixelFile& b, ThreadPool* pool) { |
619 | 0 | JxlMemoryManager* memory_manager = jxl::test::MemoryManager(); |
620 | 0 | auto io0 = jxl::make_unique<CodecInOut>(memory_manager); |
621 | 0 | Check(ConvertPackedPixelFileToCodecInOut(a, pool, io0.get())); |
622 | 0 | auto io1 = jxl::make_unique<CodecInOut>(memory_manager); |
623 | 0 | Check(ConvertPackedPixelFileToCodecInOut(b, pool, io1.get())); |
624 | | // TODO(eustas): simplify? |
625 | 0 | return ButteraugliDistance(io0->frames, io1->frames, ButteraugliParams(), |
626 | 0 | *JxlGetDefaultCms(), |
627 | 0 | /*distmap=*/nullptr, pool); |
628 | 0 | } |
629 | | |
630 | | float ButteraugliDistance(const ImageBundle& rgb0, const ImageBundle& rgb1, |
631 | | const ButteraugliParams& params, |
632 | | const JxlCmsInterface& cms, ImageF* distmap, |
633 | 0 | ThreadPool* pool, bool ignore_alpha) { |
634 | 0 | JxlButteraugliComparator comparator(params, cms); |
635 | 0 | float distance; |
636 | 0 | Check(ComputeScore(rgb0, rgb1, &comparator, cms, &distance, distmap, pool, |
637 | 0 | ignore_alpha)); |
638 | 0 | return distance; |
639 | 0 | } |
640 | | |
641 | | float ButteraugliDistance(const std::vector<ImageBundle>& frames0, |
642 | | const std::vector<ImageBundle>& frames1, |
643 | | const ButteraugliParams& params, |
644 | | const JxlCmsInterface& cms, ImageF* distmap, |
645 | 0 | ThreadPool* pool) { |
646 | 0 | JxlButteraugliComparator comparator(params, cms); |
647 | 0 | Check(frames0.size() == frames1.size()); |
648 | 0 | float max_dist = 0.0f; |
649 | 0 | for (size_t i = 0; i < frames0.size(); ++i) { |
650 | 0 | float frame_score; |
651 | 0 | Check(ComputeScore(frames0[i], frames1[i], &comparator, cms, &frame_score, |
652 | 0 | distmap, pool)); |
653 | 0 | max_dist = std::max(max_dist, frame_score); |
654 | 0 | } |
655 | 0 | return max_dist; |
656 | 0 | } |
657 | | |
658 | | float Butteraugli3Norm(const extras::PackedPixelFile& a, |
659 | 0 | const extras::PackedPixelFile& b, ThreadPool* pool) { |
660 | 0 | JxlMemoryManager* memory_manager = jxl::test::MemoryManager(); |
661 | 0 | auto io0 = jxl::make_unique<CodecInOut>(memory_manager); |
662 | 0 | Check(ConvertPackedPixelFileToCodecInOut(a, pool, io0.get())); |
663 | 0 | auto io1 = jxl::make_unique<CodecInOut>(memory_manager); |
664 | 0 | Check(ConvertPackedPixelFileToCodecInOut(b, pool, io1.get())); |
665 | 0 | ButteraugliParams butteraugli_params; |
666 | 0 | ImageF distmap; |
667 | 0 | ButteraugliDistance(io0->frames, io1->frames, butteraugli_params, |
668 | 0 | *JxlGetDefaultCms(), &distmap, pool); |
669 | 0 | JXL_TEST_ASSIGN_OR_DIE(double pnorm, |
670 | 0 | ComputeDistanceP(distmap, butteraugli_params, 3)); |
671 | 0 | return pnorm; |
672 | 0 | } |
673 | | |
674 | | float ComputeDistance2(const extras::PackedPixelFile& a, |
675 | 0 | const extras::PackedPixelFile& b) { |
676 | 0 | JxlMemoryManager* memory_manager = jxl::test::MemoryManager(); |
677 | 0 | auto io0 = jxl::make_unique<CodecInOut>(memory_manager); |
678 | 0 | Check(ConvertPackedPixelFileToCodecInOut(a, nullptr, io0.get())); |
679 | 0 | auto io1 = jxl::make_unique<CodecInOut>(memory_manager); |
680 | 0 | Check(ConvertPackedPixelFileToCodecInOut(b, nullptr, io1.get())); |
681 | 0 | return ComputeDistance2(io0->Main(), io1->Main(), *JxlGetDefaultCms()); |
682 | 0 | } |
683 | | |
684 | | float ComputePSNR(const extras::PackedPixelFile& a, |
685 | 0 | const extras::PackedPixelFile& b) { |
686 | 0 | JxlMemoryManager* memory_manager = jxl::test::MemoryManager(); |
687 | 0 | auto io0 = jxl::make_unique<CodecInOut>(memory_manager); |
688 | 0 | Check(ConvertPackedPixelFileToCodecInOut(a, nullptr, io0.get())); |
689 | 0 | auto io1 = jxl::make_unique<CodecInOut>(memory_manager); |
690 | 0 | Check(ConvertPackedPixelFileToCodecInOut(b, nullptr, io1.get())); |
691 | 0 | return ComputePSNR(io0->Main(), io1->Main(), *JxlGetDefaultCms()); |
692 | 0 | } |
693 | | |
694 | | bool SameAlpha(const extras::PackedPixelFile& a, |
695 | 0 | const extras::PackedPixelFile& b) { |
696 | 0 | Check(a.info.xsize == b.info.xsize); |
697 | 0 | Check(a.info.ysize == b.info.ysize); |
698 | 0 | Check(a.info.alpha_bits == b.info.alpha_bits); |
699 | 0 | Check(a.info.alpha_exponent_bits == b.info.alpha_exponent_bits); |
700 | 0 | Check(a.info.alpha_bits > 0); |
701 | 0 | Check(a.frames.size() == b.frames.size()); |
702 | 0 | for (size_t i = 0; i < a.frames.size(); ++i) { |
703 | 0 | const extras::PackedImage& color_a = a.frames[i].color; |
704 | 0 | const extras::PackedImage& color_b = b.frames[i].color; |
705 | 0 | Check(color_a.format.num_channels == color_b.format.num_channels); |
706 | 0 | Check(color_a.format.data_type == color_b.format.data_type); |
707 | 0 | Check(color_a.format.endianness == color_b.format.endianness); |
708 | 0 | Check(color_a.pixels_size == color_b.pixels_size); |
709 | 0 | size_t pwidth = |
710 | 0 | extras::PackedImage::BitsPerChannel(color_a.format.data_type) / 8; |
711 | 0 | size_t num_color = color_a.format.num_channels < 3 ? 1 : 3; |
712 | 0 | const uint8_t* p_a = reinterpret_cast<const uint8_t*>(color_a.pixels()); |
713 | 0 | const uint8_t* p_b = reinterpret_cast<const uint8_t*>(color_b.pixels()); |
714 | 0 | for (size_t y = 0; y < a.info.ysize; ++y) { |
715 | 0 | for (size_t x = 0; x < a.info.xsize; ++x) { |
716 | 0 | size_t idx = |
717 | 0 | ((y * a.info.xsize + x) * color_a.format.num_channels + num_color) * |
718 | 0 | pwidth; |
719 | 0 | if (memcmp(&p_a[idx], &p_b[idx], pwidth) != 0) { |
720 | 0 | return false; |
721 | 0 | } |
722 | 0 | } |
723 | 0 | } |
724 | 0 | } |
725 | 0 | return true; |
726 | 0 | } |
727 | | |
728 | 0 | bool SamePixels(const extras::PackedImage& a, const extras::PackedImage& b) { |
729 | 0 | Check(a.xsize == b.xsize); |
730 | 0 | Check(a.ysize == b.ysize); |
731 | 0 | Check(a.format.num_channels == b.format.num_channels); |
732 | 0 | Check(a.format.data_type == b.format.data_type); |
733 | 0 | Check(a.format.endianness == b.format.endianness); |
734 | 0 | Check(a.pixels_size == b.pixels_size); |
735 | 0 | const uint8_t* p_a = reinterpret_cast<const uint8_t*>(a.pixels()); |
736 | 0 | const uint8_t* p_b = reinterpret_cast<const uint8_t*>(b.pixels()); |
737 | 0 | for (size_t y = 0; y < a.ysize; ++y) { |
738 | 0 | for (size_t x = 0; x < a.xsize; ++x) { |
739 | 0 | size_t idx = (y * a.xsize + x) * a.pixel_stride(); |
740 | 0 | if (memcmp(&p_a[idx], &p_b[idx], a.pixel_stride()) != 0) { |
741 | 0 | printf("Mismatch at row %" PRIuS " col %" PRIuS "\n", y, x); |
742 | 0 | printf(" a: "); |
743 | 0 | for (size_t j = 0; j < a.pixel_stride(); ++j) { |
744 | 0 | printf(" %3u", p_a[idx + j]); |
745 | 0 | } |
746 | 0 | printf("\n b: "); |
747 | 0 | for (size_t j = 0; j < a.pixel_stride(); ++j) { |
748 | 0 | printf(" %3u", p_b[idx + j]); |
749 | 0 | } |
750 | 0 | printf("\n"); |
751 | 0 | return false; |
752 | 0 | } |
753 | 0 | } |
754 | 0 | } |
755 | 0 | return true; |
756 | 0 | } |
757 | | |
758 | | bool SamePixels(const extras::PackedPixelFile& a, |
759 | 0 | const extras::PackedPixelFile& b) { |
760 | 0 | Check(a.info.xsize == b.info.xsize); |
761 | 0 | Check(a.info.ysize == b.info.ysize); |
762 | 0 | Check(a.info.bits_per_sample == b.info.bits_per_sample); |
763 | 0 | Check(a.info.exponent_bits_per_sample == b.info.exponent_bits_per_sample); |
764 | 0 | Check(a.frames.size() == b.frames.size()); |
765 | 0 | for (size_t i = 0; i < a.frames.size(); ++i) { |
766 | 0 | const auto& frame_a = a.frames[i]; |
767 | 0 | const auto& frame_b = b.frames[i]; |
768 | 0 | if (!SamePixels(frame_a.color, frame_b.color)) { |
769 | 0 | return false; |
770 | 0 | } |
771 | 0 | Check(frame_a.extra_channels.size() == frame_b.extra_channels.size()); |
772 | 0 | for (size_t j = 0; j < frame_a.extra_channels.size(); ++j) { |
773 | 0 | if (!SamePixels(frame_a.extra_channels[i], frame_b.extra_channels[i])) { |
774 | 0 | return false; |
775 | 0 | } |
776 | 0 | } |
777 | 0 | } |
778 | 0 | return true; |
779 | 0 | } |
780 | | |
781 | | Status ReadICC(BitReader* JXL_RESTRICT reader, |
782 | 0 | std::vector<uint8_t>* JXL_RESTRICT icc) { |
783 | 0 | JxlMemoryManager* memory_manager = jxl::test::MemoryManager(); |
784 | 0 | icc->clear(); |
785 | 0 | ICCReader icc_reader{memory_manager}; |
786 | 0 | PaddedBytes icc_buffer{memory_manager}; |
787 | 0 | JXL_RETURN_IF_ERROR(icc_reader.Init(reader)); |
788 | 0 | JXL_RETURN_IF_ERROR(icc_reader.Process(reader, &icc_buffer)); |
789 | 0 | Bytes(icc_buffer).AppendTo(*icc); |
790 | 0 | return true; |
791 | 0 | } |
792 | | |
793 | | namespace { // For EncodeFile |
794 | | Status PrepareCodecMetadataFromIO(const CompressParams& cparams, |
795 | | const CodecInOut* io, |
796 | 0 | CodecMetadata* metadata) { |
797 | 0 | *metadata = io->metadata; |
798 | 0 | size_t ups = 1; |
799 | 0 | if (cparams.already_downsampled) ups = cparams.resampling; |
800 | |
|
801 | 0 | JXL_RETURN_IF_ERROR(metadata->size.Set(io->xsize() * ups, io->ysize() * ups)); |
802 | | |
803 | | // Keep ICC profile in lossless modes because a reconstructed profile may be |
804 | | // slightly different (quantization). |
805 | | // Also keep ICC in JPEG reconstruction mode as we need byte-exact profiles. |
806 | 0 | if (!cparams.IsLossless() && !io->Main().IsJPEG() && cparams.cms_set) { |
807 | 0 | metadata->m.color_encoding.DecideIfWantICC(cparams.cms); |
808 | 0 | } |
809 | |
|
810 | 0 | metadata->m.xyb_encoded = |
811 | 0 | cparams.color_transform == ColorTransform::kXYB ? true : false; |
812 | | |
813 | | // TODO(firsching): move this EncodeFile to test_utils / re-implement this |
814 | | // using API functions |
815 | 0 | return true; |
816 | 0 | } |
817 | | |
818 | | Status EncodePreview(const CompressParams& cparams, ImageBundle& ib, |
819 | | const CodecMetadata* metadata, const JxlCmsInterface& cms, |
820 | 0 | ThreadPool* pool, BitWriter* JXL_RESTRICT writer) { |
821 | 0 | JxlMemoryManager* memory_manager = jxl::test::MemoryManager(); |
822 | 0 | BitWriter preview_writer{memory_manager}; |
823 | | // TODO(janwas): also support generating preview by downsampling |
824 | 0 | if (ib.HasColor()) { |
825 | 0 | AuxOut aux_out; |
826 | | // TODO(lode): check if we want all extra channels and matching xyb_encoded |
827 | | // for the preview, such that using the main ImageMetadata object for |
828 | | // encoding this frame is warrented. |
829 | 0 | FrameInfo frame_info; |
830 | 0 | frame_info.is_preview = true; |
831 | 0 | JXL_RETURN_IF_ERROR(EncodeFrame(memory_manager, cparams, frame_info, |
832 | 0 | metadata, ib, cms, pool, &preview_writer, |
833 | 0 | &aux_out)); |
834 | 0 | preview_writer.ZeroPadToByte(); |
835 | 0 | } |
836 | | |
837 | 0 | if (preview_writer.BitsWritten() != 0) { |
838 | 0 | writer->ZeroPadToByte(); |
839 | 0 | JXL_RETURN_IF_ERROR(writer->AppendByteAligned(preview_writer.GetSpan())); |
840 | 0 | } |
841 | | |
842 | 0 | return true; |
843 | 0 | } |
844 | | |
845 | | } // namespace |
846 | | |
847 | | Status EncodeFile(const CompressParams& params, CodecInOut* io, |
848 | 0 | std::vector<uint8_t>* compressed, ThreadPool* pool) { |
849 | 0 | JxlMemoryManager* memory_manager = jxl::test::MemoryManager(); |
850 | 0 | compressed->clear(); |
851 | 0 | const JxlCmsInterface& cms = *JxlGetDefaultCms(); |
852 | 0 | JXL_RETURN_IF_ERROR(io->CheckMetadata()); |
853 | 0 | BitWriter writer{memory_manager}; |
854 | |
|
855 | 0 | CompressParams cparams = params; |
856 | 0 | if (io->Main().color_transform != ColorTransform::kNone) { |
857 | | // Set the color transform to YCbCr or XYB if the original image is such. |
858 | 0 | cparams.color_transform = io->Main().color_transform; |
859 | 0 | } |
860 | |
|
861 | 0 | JXL_RETURN_IF_ERROR(ParamsPostInit(&cparams)); |
862 | | |
863 | 0 | std::unique_ptr<CodecMetadata> metadata = jxl::make_unique<CodecMetadata>(); |
864 | 0 | JXL_RETURN_IF_ERROR(PrepareCodecMetadataFromIO(cparams, io, metadata.get())); |
865 | 0 | JXL_RETURN_IF_ERROR( |
866 | 0 | WriteCodestreamHeaders(metadata.get(), &writer, /*aux_out*/ nullptr)); |
867 | | |
868 | | // Only send ICC (at least several hundred bytes) if fields aren't enough. |
869 | 0 | if (metadata->m.color_encoding.WantICC()) { |
870 | 0 | JXL_RETURN_IF_ERROR( |
871 | 0 | WriteICC(Span<const uint8_t>(metadata->m.color_encoding.ICC()), &writer, |
872 | 0 | LayerType::Header, /* aux_out */ nullptr)); |
873 | 0 | } |
874 | | |
875 | 0 | if (metadata->m.have_preview) { |
876 | 0 | JXL_RETURN_IF_ERROR(EncodePreview(cparams, io->preview_frame, |
877 | 0 | metadata.get(), cms, pool, &writer)); |
878 | 0 | } |
879 | | |
880 | | // Each frame should start on byte boundaries. |
881 | 0 | JXL_RETURN_IF_ERROR( |
882 | 0 | writer.WithMaxBits(8, LayerType::Header, /*aux_out=*/nullptr, [&] { |
883 | 0 | writer.ZeroPadToByte(); |
884 | 0 | return true; |
885 | 0 | })); |
886 | | |
887 | 0 | for (size_t i = 0; i < io->frames.size(); i++) { |
888 | 0 | FrameInfo info; |
889 | 0 | info.is_last = i == io->frames.size() - 1; |
890 | 0 | if (io->frames[i].use_for_next_frame) { |
891 | 0 | info.save_as_reference = 1; |
892 | 0 | } |
893 | 0 | JXL_RETURN_IF_ERROR(EncodeFrame(memory_manager, cparams, info, |
894 | 0 | metadata.get(), io->frames[i], cms, pool, |
895 | 0 | &writer, |
896 | 0 | /* aux_out */ nullptr)); |
897 | 0 | } |
898 | | |
899 | 0 | PaddedBytes output = std::move(writer).TakeBytes(); |
900 | 0 | Bytes(output).AppendTo(*compressed); |
901 | 0 | return true; |
902 | 0 | } |
903 | | |
904 | 0 | extras::JXLCompressParams CompressParamsForLossless() { |
905 | 0 | extras::JXLCompressParams cparams; |
906 | 0 | cparams.AddOption(JXL_ENC_FRAME_SETTING_MODULAR, 1); |
907 | 0 | cparams.AddOption(JXL_ENC_FRAME_SETTING_COLOR_TRANSFORM, 1); |
908 | 0 | cparams.AddOption(JXL_ENC_FRAME_SETTING_MODULAR_PREDICTOR, 6); // Weighted |
909 | 0 | cparams.distance = 0.0; |
910 | 0 | return cparams; |
911 | 0 | } |
912 | | |
913 | 0 | StatusOr<ImageF> GetImage(const extras::PackedPixelFile& ppf) { |
914 | 0 | JxlMemoryManager* memory_manager = MemoryManager(); |
915 | 0 | ImageF gray; |
916 | 0 | JXL_ENSURE(!ppf.frames.empty()); |
917 | 0 | const extras::PackedImage& image = ppf.frames[0].color; |
918 | 0 | const JxlPixelFormat& format = image.format; |
919 | 0 | JXL_RETURN_IF_ERROR(format.num_channels == 1); |
920 | 0 | const uint8_t* pixels = reinterpret_cast<const uint8_t*>(image.pixels()); |
921 | 0 | JXL_TEST_ASSIGN_OR_DIE( |
922 | 0 | gray, ImageF::Create(memory_manager, image.xsize, image.ysize)); |
923 | 0 | JXL_RETURN_IF_ERROR( |
924 | 0 | ConvertFromExternal(pixels, image.pixels_size, image.xsize, image.ysize, |
925 | 0 | ppf.info.bits_per_sample, format, 0, nullptr, &gray)); |
926 | 0 | return gray; |
927 | 0 | } |
928 | | |
929 | 0 | StatusOr<Image3F> GetColorImage(const extras::PackedPixelFile& ppf) { |
930 | 0 | JxlMemoryManager* memory_manager = MemoryManager(); |
931 | 0 | Image3F color; |
932 | 0 | JXL_ENSURE(!ppf.frames.empty()); |
933 | 0 | const extras::PackedImage& image = ppf.frames[0].color; |
934 | 0 | const JxlPixelFormat& format = image.format; |
935 | 0 | JXL_RETURN_IF_ERROR(format.num_channels == 3); |
936 | 0 | const uint8_t* pixels = reinterpret_cast<const uint8_t*>(image.pixels()); |
937 | 0 | JXL_TEST_ASSIGN_OR_DIE( |
938 | 0 | color, Image3F::Create(memory_manager, image.xsize, image.ysize)); |
939 | 0 | for (size_t c = 0; c < format.num_channels; ++c) { |
940 | 0 | JXL_RETURN_IF_ERROR(ConvertFromExternal( |
941 | 0 | pixels, image.pixels_size, image.xsize, image.ysize, |
942 | 0 | ppf.info.bits_per_sample, format, c, nullptr, &color.Plane(c))); |
943 | 0 | } |
944 | 0 | return color; |
945 | 0 | } |
946 | | |
947 | | Status JpegDataToCodecInOut(std::unique_ptr<jxl::jpeg::JPEGData>&& data, |
948 | 0 | CodecInOut* io) { |
949 | 0 | JxlMemoryManager* memory_manager = io->memory_manager; |
950 | 0 | io->frames.clear(); |
951 | 0 | io->frames.reserve(1); |
952 | 0 | io->frames.emplace_back(memory_manager, &io->metadata.m); |
953 | 0 | io->Main().jpeg_data = std::move(data); |
954 | 0 | jpeg::JPEGData* jpeg_data = io->Main().jpeg_data.get(); |
955 | 0 | JXL_RETURN_IF_ERROR(jxl::jpeg::SetColorEncodingFromJpegData( |
956 | 0 | *jpeg_data, &io->metadata.m.color_encoding)); |
957 | 0 | JXL_RETURN_IF_ERROR(jxl::jpeg::SetChromaSubsamplingFromJpegData( |
958 | 0 | *jpeg_data, &io->Main().chroma_subsampling)); |
959 | 0 | JXL_RETURN_IF_ERROR(jxl::jpeg::SetColorTransformFromJpegData( |
960 | 0 | *jpeg_data, &io->Main().color_transform)); |
961 | | |
962 | 0 | io->metadata.m.SetIntensityTarget(kDefaultIntensityTarget); |
963 | 0 | io->metadata.m.SetUintSamples(8); |
964 | 0 | JXL_ASSIGN_OR_RETURN( |
965 | 0 | Image3F tmp, |
966 | 0 | Image3F::Create(memory_manager, jpeg_data->width, jpeg_data->height)); |
967 | 0 | JXL_RETURN_IF_ERROR( |
968 | 0 | io->SetFromImage(std::move(tmp), io->metadata.m.color_encoding)); |
969 | 0 | SetIntensityTarget(&io->metadata.m); |
970 | 0 | return true; |
971 | 0 | } |
972 | | |
973 | | } // namespace test |
974 | | |
975 | 0 | bool operator==(const jxl::Bytes& a, const jxl::Bytes& b) { |
976 | 0 | if (a.size() != b.size()) return false; |
977 | 0 | if (memcmp(a.data(), b.data(), a.size()) != 0) return false; |
978 | 0 | return true; |
979 | 0 | } |
980 | | |
981 | | // Allow using EXPECT_EQ on jxl::Bytes |
982 | 0 | bool operator!=(const jxl::Bytes& a, const jxl::Bytes& b) { return !(a == b); } |
983 | | |
984 | | } // namespace jxl |