jxl.cc (15315B)
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/extras/enc/jxl.h" 7 8 #include <jxl/codestream_header.h> 9 #include <jxl/encode.h> 10 #include <jxl/encode_cxx.h> 11 #include <jxl/types.h> 12 13 #include <algorithm> 14 #include <cstddef> 15 #include <cstdint> 16 #include <cstdio> 17 #include <vector> 18 19 #include "lib/extras/packed_image.h" 20 #include "lib/jxl/base/exif.h" 21 22 namespace jxl { 23 namespace extras { 24 25 JxlEncoderStatus SetOption(const JXLOption& opt, 26 JxlEncoderFrameSettings* settings) { 27 return opt.is_float 28 ? JxlEncoderFrameSettingsSetFloatOption(settings, opt.id, opt.fval) 29 : JxlEncoderFrameSettingsSetOption(settings, opt.id, opt.ival); 30 } 31 32 bool SetFrameOptions(const std::vector<JXLOption>& options, size_t frame_index, 33 size_t* option_idx, JxlEncoderFrameSettings* settings) { 34 while (*option_idx < options.size()) { 35 const auto& opt = options[*option_idx]; 36 if (opt.frame_index > frame_index) { 37 break; 38 } 39 if (JXL_ENC_SUCCESS != SetOption(opt, settings)) { 40 fprintf(stderr, "Setting option id %d failed.\n", opt.id); 41 return false; 42 } 43 (*option_idx)++; 44 } 45 return true; 46 } 47 48 bool SetupFrame(JxlEncoder* enc, JxlEncoderFrameSettings* settings, 49 const JxlFrameHeader& frame_header, 50 const JXLCompressParams& params, const PackedPixelFile& ppf, 51 size_t frame_index, size_t num_alpha_channels, 52 size_t num_interleaved_alpha, size_t& option_idx) { 53 if (JXL_ENC_SUCCESS != JxlEncoderSetFrameHeader(settings, &frame_header)) { 54 fprintf(stderr, "JxlEncoderSetFrameHeader() failed.\n"); 55 return false; 56 } 57 if (!SetFrameOptions(params.options, frame_index, &option_idx, settings)) { 58 return false; 59 } 60 if (num_alpha_channels > 0) { 61 JxlExtraChannelInfo extra_channel_info; 62 JxlEncoderInitExtraChannelInfo(JXL_CHANNEL_ALPHA, &extra_channel_info); 63 extra_channel_info.bits_per_sample = ppf.info.alpha_bits; 64 extra_channel_info.exponent_bits_per_sample = ppf.info.alpha_exponent_bits; 65 if (params.premultiply != -1) { 66 if (params.premultiply != 0 && params.premultiply != 1) { 67 fprintf(stderr, "premultiply must be one of: -1, 0, 1.\n"); 68 return false; 69 } 70 extra_channel_info.alpha_premultiplied = params.premultiply; 71 } 72 if (JXL_ENC_SUCCESS != 73 JxlEncoderSetExtraChannelInfo(enc, 0, &extra_channel_info)) { 74 fprintf(stderr, "JxlEncoderSetExtraChannelInfo() failed.\n"); 75 return false; 76 } 77 // We take the extra channel blend info frame_info, but don't do 78 // clamping. 79 JxlBlendInfo extra_channel_blend_info = frame_header.layer_info.blend_info; 80 extra_channel_blend_info.clamp = JXL_FALSE; 81 JxlEncoderSetExtraChannelBlendInfo(settings, 0, &extra_channel_blend_info); 82 } 83 // Add extra channel info for the rest of the extra channels. 84 for (size_t i = 0; i < ppf.info.num_extra_channels; ++i) { 85 if (i < ppf.extra_channels_info.size()) { 86 const auto& ec_info = ppf.extra_channels_info[i].ec_info; 87 if (JXL_ENC_SUCCESS != JxlEncoderSetExtraChannelInfo( 88 enc, num_interleaved_alpha + i, &ec_info)) { 89 fprintf(stderr, "JxlEncoderSetExtraChannelInfo() failed.\n"); 90 return false; 91 } 92 } 93 } 94 return true; 95 } 96 97 bool ReadCompressedOutput(JxlEncoder* enc, std::vector<uint8_t>* compressed) { 98 compressed->clear(); 99 compressed->resize(4096); 100 uint8_t* next_out = compressed->data(); 101 size_t avail_out = compressed->size() - (next_out - compressed->data()); 102 JxlEncoderStatus result = JXL_ENC_NEED_MORE_OUTPUT; 103 while (result == JXL_ENC_NEED_MORE_OUTPUT) { 104 result = JxlEncoderProcessOutput(enc, &next_out, &avail_out); 105 if (result == JXL_ENC_NEED_MORE_OUTPUT) { 106 size_t offset = next_out - compressed->data(); 107 compressed->resize(compressed->size() * 2); 108 next_out = compressed->data() + offset; 109 avail_out = compressed->size() - offset; 110 } 111 } 112 compressed->resize(next_out - compressed->data()); 113 if (result != JXL_ENC_SUCCESS) { 114 fprintf(stderr, "JxlEncoderProcessOutput failed.\n"); 115 return false; 116 } 117 return true; 118 } 119 120 bool EncodeImageJXL(const JXLCompressParams& params, const PackedPixelFile& ppf, 121 const std::vector<uint8_t>* jpeg_bytes, 122 std::vector<uint8_t>* compressed) { 123 auto encoder = JxlEncoderMake(params.memory_manager); 124 JxlEncoder* enc = encoder.get(); 125 126 if (params.allow_expert_options) { 127 JxlEncoderAllowExpertOptions(enc); 128 } 129 130 if (params.runner_opaque != nullptr && 131 JXL_ENC_SUCCESS != JxlEncoderSetParallelRunner(enc, params.runner, 132 params.runner_opaque)) { 133 fprintf(stderr, "JxlEncoderSetParallelRunner failed\n"); 134 return false; 135 } 136 137 if (params.HasOutputProcessor() && 138 JXL_ENC_SUCCESS != 139 JxlEncoderSetOutputProcessor(enc, params.output_processor)) { 140 fprintf(stderr, "JxlEncoderSetOutputProcessorfailed\n"); 141 return false; 142 } 143 144 auto* settings = JxlEncoderFrameSettingsCreate(enc, nullptr); 145 size_t option_idx = 0; 146 if (!SetFrameOptions(params.options, 0, &option_idx, settings)) { 147 return false; 148 } 149 if (JXL_ENC_SUCCESS != 150 JxlEncoderSetFrameDistance(settings, params.distance)) { 151 fprintf(stderr, "Setting frame distance failed.\n"); 152 return false; 153 } 154 if (params.debug_image) { 155 JxlEncoderSetDebugImageCallback(settings, params.debug_image, 156 params.debug_image_opaque); 157 } 158 if (params.stats) { 159 JxlEncoderCollectStats(settings, params.stats); 160 } 161 162 bool has_jpeg_bytes = (jpeg_bytes != nullptr); 163 bool use_boxes = !ppf.metadata.exif.empty() || !ppf.metadata.xmp.empty() || 164 !ppf.metadata.jhgm.empty() || !ppf.metadata.jumbf.empty() || 165 !ppf.metadata.iptc.empty(); 166 bool use_container = params.use_container || use_boxes || 167 (has_jpeg_bytes && params.jpeg_store_metadata); 168 169 if (JXL_ENC_SUCCESS != 170 JxlEncoderUseContainer(enc, static_cast<int>(use_container))) { 171 fprintf(stderr, "JxlEncoderUseContainer failed.\n"); 172 return false; 173 } 174 175 if (has_jpeg_bytes) { 176 if (params.jpeg_store_metadata && 177 JXL_ENC_SUCCESS != JxlEncoderStoreJPEGMetadata(enc, JXL_TRUE)) { 178 fprintf(stderr, "Storing JPEG metadata failed.\n"); 179 return false; 180 } 181 if (params.jpeg_store_metadata && params.jpeg_strip_exif) { 182 fprintf(stderr, 183 "Cannot store metadata and strip exif at the same time.\n"); 184 return false; 185 } 186 if (params.jpeg_store_metadata && params.jpeg_strip_xmp) { 187 fprintf(stderr, 188 "Cannot store metadata and strip xmp at the same time.\n"); 189 return false; 190 } 191 if (!params.jpeg_store_metadata && params.jpeg_strip_exif) { 192 JxlEncoderFrameSettingsSetOption(settings, 193 JXL_ENC_FRAME_SETTING_JPEG_KEEP_EXIF, 0); 194 } 195 if (!params.jpeg_store_metadata && params.jpeg_strip_xmp) { 196 JxlEncoderFrameSettingsSetOption(settings, 197 JXL_ENC_FRAME_SETTING_JPEG_KEEP_XMP, 0); 198 } 199 if (params.jpeg_strip_jumbf) { 200 JxlEncoderFrameSettingsSetOption( 201 settings, JXL_ENC_FRAME_SETTING_JPEG_KEEP_JUMBF, 0); 202 } 203 if (JXL_ENC_SUCCESS != JxlEncoderAddJPEGFrame(settings, jpeg_bytes->data(), 204 jpeg_bytes->size())) { 205 JxlEncoderError error = JxlEncoderGetError(enc); 206 if (error == JXL_ENC_ERR_BAD_INPUT) { 207 fprintf(stderr, 208 "Error while decoding the JPEG image. It may be corrupt (e.g. " 209 "truncated) or of an unsupported type (e.g. CMYK).\n"); 210 } else if (error == JXL_ENC_ERR_JBRD) { 211 fprintf(stderr, 212 "JPEG bitstream reconstruction data could not be created. " 213 "Possibly there is too much tail data.\n" 214 "Try using --allow_jpeg_reconstruction 0, to losslessly " 215 "recompress the JPEG image data without bitstream " 216 "reconstruction data.\n"); 217 } else { 218 fprintf(stderr, "JxlEncoderAddJPEGFrame() failed.\n"); 219 } 220 return false; 221 } 222 } else { 223 size_t num_alpha_channels = 0; // Adjusted below. 224 JxlBasicInfo basic_info = ppf.info; 225 basic_info.xsize *= params.already_downsampled; 226 basic_info.ysize *= params.already_downsampled; 227 if (basic_info.alpha_bits > 0) num_alpha_channels = 1; 228 if (params.intensity_target > 0) { 229 basic_info.intensity_target = params.intensity_target; 230 } 231 basic_info.num_extra_channels = 232 std::max<uint32_t>(num_alpha_channels, ppf.info.num_extra_channels); 233 basic_info.num_color_channels = ppf.info.num_color_channels; 234 const bool lossless = (params.distance == 0); 235 auto non_perceptual_option = std::find_if( 236 params.options.begin(), params.options.end(), [](JXLOption option) { 237 return option.id == 238 JXL_ENC_FRAME_SETTING_DISABLE_PERCEPTUAL_HEURISTICS; 239 }); 240 const bool non_perceptual = non_perceptual_option != params.options.end() && 241 non_perceptual_option->ival == 1; 242 basic_info.uses_original_profile = TO_JXL_BOOL(lossless || non_perceptual); 243 if (params.override_bitdepth != 0) { 244 basic_info.bits_per_sample = params.override_bitdepth; 245 basic_info.exponent_bits_per_sample = 246 params.override_bitdepth == 32 ? 8 : 0; 247 } 248 if (JXL_ENC_SUCCESS != 249 JxlEncoderSetCodestreamLevel(enc, params.codestream_level)) { 250 fprintf(stderr, "Setting --codestream_level failed.\n"); 251 return false; 252 } 253 if (JXL_ENC_SUCCESS != JxlEncoderSetBasicInfo(enc, &basic_info)) { 254 fprintf(stderr, "JxlEncoderSetBasicInfo() failed.\n"); 255 return false; 256 } 257 if (JXL_ENC_SUCCESS != 258 JxlEncoderSetUpsamplingMode(enc, params.already_downsampled, 259 params.upsampling_mode)) { 260 fprintf(stderr, "JxlEncoderSetUpsamplingMode() failed.\n"); 261 return false; 262 } 263 if (JXL_ENC_SUCCESS != 264 JxlEncoderSetFrameBitDepth(settings, &ppf.input_bitdepth)) { 265 fprintf(stderr, "JxlEncoderSetFrameBitDepth() failed.\n"); 266 return false; 267 } 268 if (num_alpha_channels != 0 && 269 JXL_ENC_SUCCESS != JxlEncoderSetExtraChannelDistance( 270 settings, 0, params.alpha_distance)) { 271 fprintf(stderr, "Setting alpha distance failed.\n"); 272 return false; 273 } 274 if (lossless && 275 JXL_ENC_SUCCESS != JxlEncoderSetFrameLossless(settings, JXL_TRUE)) { 276 fprintf(stderr, "JxlEncoderSetFrameLossless() failed.\n"); 277 return false; 278 } 279 if (ppf.primary_color_representation == PackedPixelFile::kIccIsPrimary) { 280 if (JXL_ENC_SUCCESS != 281 JxlEncoderSetICCProfile(enc, ppf.icc.data(), ppf.icc.size())) { 282 fprintf(stderr, "JxlEncoderSetICCProfile() failed.\n"); 283 return false; 284 } 285 } else { 286 if (JXL_ENC_SUCCESS != 287 JxlEncoderSetColorEncoding(enc, &ppf.color_encoding)) { 288 fprintf(stderr, "JxlEncoderSetColorEncoding() failed.\n"); 289 return false; 290 } 291 } 292 293 if (use_boxes) { 294 if (JXL_ENC_SUCCESS != JxlEncoderUseBoxes(enc)) { 295 fprintf(stderr, "JxlEncoderUseBoxes() failed.\n"); 296 return false; 297 } 298 // Prepend 4 zero bytes to exif for tiff header offset 299 std::vector<uint8_t> exif_with_offset; 300 bool bigendian; 301 if (IsExif(ppf.metadata.exif, &bigendian)) { 302 exif_with_offset.resize(ppf.metadata.exif.size() + 4); 303 memcpy(exif_with_offset.data() + 4, ppf.metadata.exif.data(), 304 ppf.metadata.exif.size()); 305 } 306 const struct BoxInfo { 307 const char* type; 308 const std::vector<uint8_t>& bytes; 309 } boxes[] = { 310 {"Exif", exif_with_offset}, {"xml ", ppf.metadata.xmp}, 311 {"jumb", ppf.metadata.jumbf}, {"xml ", ppf.metadata.iptc}, 312 {"jhgm", ppf.metadata.jhgm}, 313 }; 314 for (auto box : boxes) { 315 if (!box.bytes.empty()) { 316 if (JXL_ENC_SUCCESS != 317 JxlEncoderAddBox(enc, box.type, box.bytes.data(), 318 box.bytes.size(), 319 TO_JXL_BOOL(params.compress_boxes))) { 320 fprintf(stderr, "JxlEncoderAddBox() failed (%s).\n", box.type); 321 return false; 322 } 323 } 324 } 325 JxlEncoderCloseBoxes(enc); 326 } 327 328 for (size_t num_frame = 0; num_frame < ppf.frames.size(); ++num_frame) { 329 const jxl::extras::PackedFrame& pframe = ppf.frames[num_frame]; 330 const jxl::extras::PackedImage& pimage = pframe.color; 331 JxlPixelFormat ppixelformat = pimage.format; 332 size_t num_interleaved_alpha = 333 (ppixelformat.num_channels - ppf.info.num_color_channels); 334 if (!SetupFrame(enc, settings, pframe.frame_info, params, ppf, num_frame, 335 num_alpha_channels, num_interleaved_alpha, option_idx)) { 336 return false; 337 } 338 if (JXL_ENC_SUCCESS != JxlEncoderAddImageFrame(settings, &ppixelformat, 339 pimage.pixels(), 340 pimage.pixels_size)) { 341 fprintf(stderr, "JxlEncoderAddImageFrame() failed.\n"); 342 return false; 343 } 344 // Only set extra channel buffer if it is provided non-interleaved. 345 for (size_t i = 0; i < pframe.extra_channels.size(); ++i) { 346 if (JXL_ENC_SUCCESS != 347 JxlEncoderSetExtraChannelBuffer(settings, &ppixelformat, 348 pframe.extra_channels[i].pixels(), 349 pframe.extra_channels[i].stride * 350 pframe.extra_channels[i].ysize, 351 num_interleaved_alpha + i)) { 352 fprintf(stderr, "JxlEncoderSetExtraChannelBuffer() failed.\n"); 353 return false; 354 } 355 } 356 } 357 for (size_t fi = 0; fi < ppf.chunked_frames.size(); ++fi) { 358 ChunkedPackedFrame& chunked_frame = ppf.chunked_frames[fi]; 359 size_t num_interleaved_alpha = 360 (chunked_frame.format.num_channels - ppf.info.num_color_channels); 361 if (!SetupFrame(enc, settings, chunked_frame.frame_info, params, ppf, fi, 362 num_alpha_channels, num_interleaved_alpha, option_idx)) { 363 return false; 364 } 365 const bool last_frame = fi + 1 == ppf.chunked_frames.size(); 366 if (JXL_ENC_SUCCESS != 367 JxlEncoderAddChunkedFrame(settings, TO_JXL_BOOL(last_frame), 368 chunked_frame.GetInputSource())) { 369 fprintf(stderr, "JxlEncoderAddChunkedFrame() failed.\n"); 370 return false; 371 } 372 } 373 } 374 JxlEncoderCloseInput(enc); 375 if (params.HasOutputProcessor()) { 376 if (JXL_ENC_SUCCESS != JxlEncoderFlushInput(enc)) { 377 fprintf(stderr, "JxlEncoderAddChunkedFrame() failed.\n"); 378 return false; 379 } 380 } else if (!ReadCompressedOutput(enc, compressed)) { 381 return false; 382 } 383 return true; 384 } 385 386 } // namespace extras 387 } // namespace jxl