load_jxl.cc (6150B)
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 "tools/viewer/load_jxl.h" 7 8 #include <jxl/decode.h> 9 #include <jxl/decode_cxx.h> 10 #include <jxl/thread_parallel_runner_cxx.h> 11 #include <jxl/types.h> 12 #include <stdint.h> 13 14 #include <QElapsedTimer> 15 #include <QFile> 16 17 #define CMS_NO_REGISTER_KEYWORD 1 18 #include "lcms2.h" 19 #undef CMS_NO_REGISTER_KEYWORD 20 21 namespace jpegxl { 22 namespace tools { 23 24 namespace { 25 26 struct CmsProfileCloser { 27 void operator()(const cmsHPROFILE profile) const { 28 if (profile != nullptr) { 29 cmsCloseProfile(profile); 30 } 31 } 32 }; 33 using CmsProfileUniquePtr = 34 std::unique_ptr<std::remove_pointer<cmsHPROFILE>::type, CmsProfileCloser>; 35 36 struct CmsTransformDeleter { 37 void operator()(const cmsHTRANSFORM transform) const { 38 if (transform != nullptr) { 39 cmsDeleteTransform(transform); 40 } 41 } 42 }; 43 using CmsTransformUniquePtr = 44 std::unique_ptr<std::remove_pointer<cmsHTRANSFORM>::type, 45 CmsTransformDeleter>; 46 47 } // namespace 48 49 QImage loadJxlImage(const QString& filename, const QByteArray& targetIccProfile, 50 qint64* elapsed_ns, bool* usedRequestedProfile) { 51 auto runner = JxlThreadParallelRunnerMake( 52 nullptr, JxlThreadParallelRunnerDefaultNumWorkerThreads()); 53 54 auto dec = JxlDecoderMake(nullptr); 55 56 #define EXPECT_TRUE(a) \ 57 do { \ 58 if (!(a)) { \ 59 fprintf(stderr, "Assertion failure (%d): %s\n", __LINE__, #a); \ 60 return QImage(); \ 61 } \ 62 } while (false) 63 #define EXPECT_EQ(a, b) \ 64 do { \ 65 int a_ = a; \ 66 int b_ = b; \ 67 if (a_ != b_) { \ 68 fprintf(stderr, "Assertion failure (%d): %s (%d) != %s (%d)\n", \ 69 __LINE__, #a, a_, #b, b_); \ 70 return QImage(); \ 71 } \ 72 } while (false) 73 74 EXPECT_EQ(JXL_DEC_SUCCESS, 75 JxlDecoderSubscribeEvents(dec.get(), JXL_DEC_BASIC_INFO | 76 JXL_DEC_COLOR_ENCODING | 77 JXL_DEC_FULL_IMAGE)); 78 QFile jpegXlFile(filename); 79 if (!jpegXlFile.open(QIODevice::ReadOnly)) { 80 return QImage(); 81 } 82 const QByteArray jpegXlData = jpegXlFile.readAll(); 83 if (jpegXlData.size() < 4) { 84 return QImage(); 85 } 86 87 QElapsedTimer timer; 88 timer.start(); 89 const uint8_t* jxl_data = reinterpret_cast<const uint8_t*>(jpegXlData.data()); 90 size_t jxl_size = jpegXlData.size(); 91 JxlDecoderSetInput(dec.get(), jxl_data, jxl_size); 92 EXPECT_EQ(JXL_DEC_BASIC_INFO, JxlDecoderProcessInput(dec.get())); 93 JxlBasicInfo info; 94 EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderGetBasicInfo(dec.get(), &info)); 95 size_t pixel_count = info.xsize * info.ysize; 96 97 EXPECT_EQ(JXL_DEC_COLOR_ENCODING, JxlDecoderProcessInput(dec.get())); 98 static const JxlPixelFormat format = {4, JXL_TYPE_FLOAT, JXL_NATIVE_ENDIAN, 99 0}; 100 size_t icc_size; 101 EXPECT_EQ(JXL_DEC_SUCCESS, 102 JxlDecoderGetICCProfileSize( 103 dec.get(), JXL_COLOR_PROFILE_TARGET_DATA, &icc_size)); 104 std::vector<uint8_t> icc_profile(icc_size); 105 EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderGetColorAsICCProfile( 106 dec.get(), JXL_COLOR_PROFILE_TARGET_DATA, 107 icc_profile.data(), icc_profile.size())); 108 109 std::vector<float> float_pixels(pixel_count * 4); 110 EXPECT_EQ(JXL_DEC_NEED_IMAGE_OUT_BUFFER, JxlDecoderProcessInput(dec.get())); 111 EXPECT_EQ(JXL_DEC_SUCCESS, 112 JxlDecoderSetImageOutBuffer(dec.get(), &format, float_pixels.data(), 113 pixel_count * 4 * sizeof(float))); 114 EXPECT_EQ(JXL_DEC_FULL_IMAGE, JxlDecoderProcessInput(dec.get())); 115 116 std::vector<uint16_t> uint16_pixels(pixel_count * 4); 117 const thread_local cmsContext context = cmsCreateContext(nullptr, nullptr); 118 EXPECT_TRUE(context != nullptr); 119 const CmsProfileUniquePtr jxl_profile(cmsOpenProfileFromMemTHR( 120 context, icc_profile.data(), icc_profile.size())); 121 EXPECT_TRUE(jxl_profile != nullptr); 122 CmsProfileUniquePtr target_profile(cmsOpenProfileFromMemTHR( 123 context, targetIccProfile.data(), targetIccProfile.size())); 124 if (usedRequestedProfile != nullptr) { 125 *usedRequestedProfile = (target_profile != nullptr); 126 } 127 if (target_profile == nullptr) { 128 target_profile.reset(cmsCreate_sRGBProfileTHR(context)); 129 } 130 EXPECT_TRUE(target_profile != nullptr); 131 CmsTransformUniquePtr transform(cmsCreateTransformTHR( 132 context, jxl_profile.get(), TYPE_RGBA_FLT, target_profile.get(), 133 TYPE_RGBA_16, INTENT_RELATIVE_COLORIMETRIC, cmsFLAGS_COPY_ALPHA)); 134 EXPECT_TRUE(transform != nullptr); 135 cmsDoTransform(transform.get(), float_pixels.data(), uint16_pixels.data(), 136 pixel_count); 137 if (elapsed_ns != nullptr) *elapsed_ns = timer.nsecsElapsed(); 138 139 QImage result(info.xsize, info.ysize, 140 info.alpha_premultiplied ? QImage::Format_RGBA64_Premultiplied 141 : QImage::Format_RGBA64); 142 143 for (int y = 0; y < result.height(); ++y) { 144 QRgba64* const row = reinterpret_cast<QRgba64*>(result.scanLine(y)); 145 const uint16_t* const data = uint16_pixels.data() + result.width() * y * 4; 146 for (int x = 0; x < result.width(); ++x) { 147 row[x] = qRgba64(data[4 * x + 0], data[4 * x + 1], data[4 * x + 2], 148 data[4 * x + 3]); 149 } 150 } 151 return result; 152 } 153 154 } // namespace tools 155 } // namespace jpegxl