exr_to_pq.cc (6362B)
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 <stdio.h> 7 #include <stdlib.h> 8 9 #include "lib/extras/codec.h" 10 #include "lib/extras/dec/decode.h" 11 #include "lib/extras/packed_image_convert.h" 12 #include "lib/extras/tone_mapping.h" 13 #include "lib/jxl/cms/jxl_cms_internal.h" 14 #include "lib/jxl/image_bundle.h" 15 #include "tools/cmdline.h" 16 #include "tools/file_io.h" 17 #include "tools/hdr/image_utils.h" 18 #include "tools/thread_pool_internal.h" 19 20 namespace { 21 22 struct LuminanceInfo { 23 enum class Kind { kWhite, kMaximum }; 24 Kind kind = Kind::kWhite; 25 float luminance = 100.f; 26 }; 27 28 bool ParseLuminanceInfo(const char* argument, LuminanceInfo* luminance_info) { 29 if (strncmp(argument, "white=", 6) == 0) { 30 luminance_info->kind = LuminanceInfo::Kind::kWhite; 31 argument += 6; 32 } else if (strncmp(argument, "max=", 4) == 0) { 33 luminance_info->kind = LuminanceInfo::Kind::kMaximum; 34 argument += 4; 35 } else { 36 fprintf(stderr, 37 "Invalid prefix for luminance info, expected white= or max=\n"); 38 return false; 39 } 40 return jpegxl::tools::ParseFloat(argument, &luminance_info->luminance); 41 } 42 43 } // namespace 44 45 int main(int argc, const char** argv) { 46 jpegxl::tools::ThreadPoolInternal pool; 47 48 jpegxl::tools::CommandLineParser parser; 49 LuminanceInfo luminance_info; 50 auto luminance_option = 51 parser.AddOptionValue('l', "luminance", "<max|white=N>", 52 "luminance information (defaults to whiteLuminance " 53 "header if present, otherwise to white=100)", 54 &luminance_info, &ParseLuminanceInfo, 0); 55 const char* input_filename = nullptr; 56 auto input_filename_option = parser.AddPositionalOption( 57 "input", true, "input image", &input_filename, 0); 58 const char* output_filename = nullptr; 59 auto output_filename_option = parser.AddPositionalOption( 60 "output", true, "output image", &output_filename, 0); 61 62 if (!parser.Parse(argc, argv)) { 63 fprintf(stderr, "See -h for help.\n"); 64 return EXIT_FAILURE; 65 } 66 67 if (parser.HelpFlagPassed()) { 68 parser.PrintHelp(); 69 return EXIT_SUCCESS; 70 } 71 72 if (!parser.GetOption(input_filename_option)->matched()) { 73 fprintf(stderr, "Missing input filename.\nSee -h for help.\n"); 74 return EXIT_FAILURE; 75 } 76 if (!parser.GetOption(output_filename_option)->matched()) { 77 fprintf(stderr, "Missing output filename.\nSee -h for help.\n"); 78 return EXIT_FAILURE; 79 } 80 81 jxl::extras::PackedPixelFile ppf; 82 std::vector<uint8_t> input_bytes; 83 JXL_CHECK(jpegxl::tools::ReadFile(input_filename, &input_bytes)); 84 JXL_CHECK(jxl::extras::DecodeBytes(jxl::Bytes(input_bytes), 85 jxl::extras::ColorHints(), &ppf)); 86 87 jxl::CodecInOut image; 88 JXL_CHECK( 89 jxl::extras::ConvertPackedPixelFileToCodecInOut(ppf, &pool, &image)); 90 image.metadata.m.bit_depth.exponent_bits_per_sample = 0; 91 jxl::ColorEncoding linear_rec_2020 = image.Main().c_current(); 92 JXL_CHECK(linear_rec_2020.SetPrimariesType(jxl::Primaries::k2100)); 93 linear_rec_2020.Tf().SetTransferFunction(jxl::TransferFunction::kLinear); 94 JXL_CHECK(linear_rec_2020.CreateICC()); 95 JXL_CHECK( 96 jpegxl::tools::TransformCodecInOutTo(image, linear_rec_2020, &pool)); 97 98 float primaries_xyz[9]; 99 const jxl::PrimariesCIExy p = image.Main().c_current().GetPrimaries(); 100 const jxl::CIExy wp = image.Main().c_current().GetWhitePoint(); 101 JXL_CHECK(jxl::PrimariesToXYZ(p.r.x, p.r.y, p.g.x, p.g.y, p.b.x, p.b.y, wp.x, 102 wp.y, primaries_xyz)); 103 104 float max_value = 0.f; 105 float max_relative_luminance = 0.f; 106 float white_luminance = ppf.info.intensity_target != 0 && 107 !parser.GetOption(luminance_option)->matched() 108 ? ppf.info.intensity_target 109 : luminance_info.kind == LuminanceInfo::Kind::kWhite 110 ? luminance_info.luminance 111 : 0.f; 112 bool out_of_gamut = false; 113 for (size_t y = 0; y < image.ysize(); ++y) { 114 const float* const rows[3] = {image.Main().color()->ConstPlaneRow(0, y), 115 image.Main().color()->ConstPlaneRow(1, y), 116 image.Main().color()->ConstPlaneRow(2, y)}; 117 for (size_t x = 0; x < image.xsize(); ++x) { 118 if (!out_of_gamut && 119 (rows[0][x] < 0 || rows[1][x] < 0 || rows[2][x] < 0)) { 120 out_of_gamut = true; 121 fprintf(stderr, 122 "WARNING: found colors outside of the Rec. 2020 gamut.\n"); 123 } 124 max_value = std::max( 125 max_value, std::max(rows[0][x], std::max(rows[1][x], rows[2][x]))); 126 const float luminance = primaries_xyz[1] * rows[0][x] + 127 primaries_xyz[4] * rows[1][x] + 128 primaries_xyz[7] * rows[2][x]; 129 if (luminance_info.kind == LuminanceInfo::Kind::kMaximum && 130 luminance > max_relative_luminance) { 131 max_relative_luminance = luminance; 132 white_luminance = luminance_info.luminance / luminance; 133 } 134 } 135 } 136 137 bool needs_gamut_mapping = false; 138 139 white_luminance *= max_value; 140 if (white_luminance > 10000) { 141 fprintf(stderr, 142 "WARNING: the image is too bright for PQ (would need (1, 1, 1) to " 143 "be %g cd/m^2).\n", 144 white_luminance); 145 146 max_value *= 10000 / white_luminance; 147 white_luminance = 10000; 148 needs_gamut_mapping = true; 149 } else { 150 fprintf(stderr, 151 "The resulting image should be compressed with " 152 "--intensity_target=%g.\n", 153 white_luminance); 154 } 155 image.metadata.m.SetIntensityTarget(white_luminance); 156 157 jxl::ScaleImage(1.f / max_value, image.Main().color()); 158 159 if (needs_gamut_mapping) { 160 JXL_CHECK(jxl::GamutMap(&image, 0.f, &pool)); 161 } 162 163 jxl::ColorEncoding pq = image.Main().c_current(); 164 pq.Tf().SetTransferFunction(jxl::TransferFunction::kPQ); 165 JXL_CHECK(pq.CreateICC()); 166 JXL_CHECK(jpegxl::tools::TransformCodecInOutTo(image, pq, &pool)); 167 image.metadata.m.color_encoding = pq; 168 std::vector<uint8_t> encoded; 169 JXL_CHECK(jpegxl::tools::Encode(image, output_filename, &encoded, &pool)); 170 JXL_CHECK(jpegxl::tools::WriteFile(output_filename, encoded)); 171 }