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