jpegli.cc (19582B)
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/jpegli.h" 7 8 #include <jxl/cms.h> 9 #include <jxl/codestream_header.h> 10 #include <jxl/types.h> 11 #include <setjmp.h> 12 #include <stdint.h> 13 14 #include <algorithm> 15 #include <cmath> 16 #include <cstddef> 17 #include <cstdlib> 18 #include <cstring> 19 #include <hwy/aligned_allocator.h> 20 #include <limits> 21 #include <string> 22 #include <utility> 23 #include <vector> 24 25 #include "lib/extras/enc/encode.h" 26 #include "lib/extras/packed_image.h" 27 #include "lib/jpegli/common.h" 28 #include "lib/jpegli/encode.h" 29 #include "lib/jpegli/types.h" 30 #include "lib/jxl/base/byte_order.h" 31 #include "lib/jxl/base/common.h" 32 #include "lib/jxl/base/data_parallel.h" 33 #include "lib/jxl/base/status.h" 34 #include "lib/jxl/color_encoding_internal.h" 35 #include "lib/jxl/enc_xyb.h" 36 #include "lib/jxl/image.h" 37 #include "lib/jxl/simd_util.h" 38 39 namespace jxl { 40 namespace extras { 41 42 namespace { 43 44 void MyErrorExit(j_common_ptr cinfo) { 45 jmp_buf* env = static_cast<jmp_buf*>(cinfo->client_data); 46 (*cinfo->err->output_message)(cinfo); 47 jpegli_destroy_compress(reinterpret_cast<j_compress_ptr>(cinfo)); 48 longjmp(*env, 1); 49 } 50 51 Status VerifyInput(const PackedPixelFile& ppf) { 52 const JxlBasicInfo& info = ppf.info; 53 JXL_RETURN_IF_ERROR(Encoder::VerifyBasicInfo(info)); 54 if (ppf.frames.size() != 1) { 55 return JXL_FAILURE("JPEG input must have exactly one frame."); 56 } 57 const PackedImage& image = ppf.frames[0].color; 58 JXL_RETURN_IF_ERROR(Encoder::VerifyImageSize(image, info)); 59 if (image.format.data_type == JXL_TYPE_FLOAT16) { 60 return JXL_FAILURE("FLOAT16 input is not supported."); 61 } 62 JXL_RETURN_IF_ERROR(Encoder::VerifyBitDepth(image.format.data_type, 63 info.bits_per_sample, 64 info.exponent_bits_per_sample)); 65 if ((image.format.data_type == JXL_TYPE_UINT8 && info.bits_per_sample != 8) || 66 (image.format.data_type == JXL_TYPE_UINT16 && 67 info.bits_per_sample != 16)) { 68 return JXL_FAILURE("Only full bit depth unsigned types are supported."); 69 } 70 return true; 71 } 72 73 Status GetColorEncoding(const PackedPixelFile& ppf, 74 ColorEncoding* color_encoding) { 75 if (ppf.primary_color_representation == PackedPixelFile::kIccIsPrimary) { 76 IccBytes icc = ppf.icc; 77 JXL_RETURN_IF_ERROR( 78 color_encoding->SetICC(std::move(icc), JxlGetDefaultCms())); 79 } else { 80 JXL_RETURN_IF_ERROR(color_encoding->FromExternal(ppf.color_encoding)); 81 } 82 if (color_encoding->ICC().empty()) { 83 return JXL_FAILURE("Invalid color encoding."); 84 } 85 return true; 86 } 87 88 bool HasICCProfile(const std::vector<uint8_t>& app_data) { 89 size_t pos = 0; 90 while (pos < app_data.size()) { 91 if (pos + 16 > app_data.size()) return false; 92 uint8_t marker = app_data[pos + 1]; 93 size_t marker_len = (app_data[pos + 2] << 8) + app_data[pos + 3] + 2; 94 if (marker == 0xe2 && memcmp(&app_data[pos + 4], "ICC_PROFILE", 12) == 0) { 95 return true; 96 } 97 pos += marker_len; 98 } 99 return false; 100 } 101 102 Status WriteAppData(j_compress_ptr cinfo, 103 const std::vector<uint8_t>& app_data) { 104 size_t pos = 0; 105 while (pos < app_data.size()) { 106 if (pos + 4 > app_data.size()) { 107 return JXL_FAILURE("Incomplete APP header."); 108 } 109 uint8_t marker = app_data[pos + 1]; 110 size_t marker_len = (app_data[pos + 2] << 8) + app_data[pos + 3] + 2; 111 if (app_data[pos] != 0xff || marker < 0xe0 || marker > 0xef) { 112 return JXL_FAILURE("Invalid APP marker %02x %02x", app_data[pos], marker); 113 } 114 if (marker_len <= 4) { 115 return JXL_FAILURE("Invalid APP marker length."); 116 } 117 if (pos + marker_len > app_data.size()) { 118 return JXL_FAILURE("Incomplete APP data"); 119 } 120 jpegli_write_marker(cinfo, marker, &app_data[pos + 4], marker_len - 4); 121 pos += marker_len; 122 } 123 return true; 124 } 125 126 constexpr int kICCMarker = 0xe2; 127 constexpr unsigned char kICCSignature[12] = { 128 0x49, 0x43, 0x43, 0x5F, 0x50, 0x52, 0x4F, 0x46, 0x49, 0x4C, 0x45, 0x00}; 129 constexpr uint8_t kUnknownTf = 2; 130 constexpr unsigned char kCICPTagSignature[4] = {0x63, 0x69, 0x63, 0x70}; 131 constexpr size_t kCICPTagSize = 12; 132 133 bool FindCICPTag(const uint8_t* icc_data, size_t len, bool is_first_chunk, 134 size_t* cicp_offset, size_t* cicp_length, uint8_t* cicp_tag, 135 size_t* cicp_pos) { 136 if (is_first_chunk) { 137 // Look up the offset of the CICP tag from the first chunk of ICC data. 138 if (len < 132) { 139 return false; 140 } 141 uint32_t tag_count = LoadBE32(&icc_data[128]); 142 if (len < 132 + 12 * tag_count) { 143 return false; 144 } 145 for (uint32_t i = 0; i < tag_count; ++i) { 146 if (memcmp(&icc_data[132 + 12 * i], kCICPTagSignature, 4) == 0) { 147 *cicp_offset = LoadBE32(&icc_data[136 + 12 * i]); 148 *cicp_length = LoadBE32(&icc_data[140 + 12 * i]); 149 } 150 } 151 if (*cicp_length < kCICPTagSize) { 152 return false; 153 } 154 } 155 if (*cicp_offset < len) { 156 size_t n_bytes = std::min(len - *cicp_offset, kCICPTagSize - *cicp_pos); 157 memcpy(&cicp_tag[*cicp_pos], &icc_data[*cicp_offset], n_bytes); 158 *cicp_pos += n_bytes; 159 *cicp_offset = 0; 160 } else { 161 *cicp_offset -= len; 162 } 163 return true; 164 } 165 166 uint8_t LookupCICPTransferFunctionFromAppData(const uint8_t* app_data, 167 size_t len) { 168 size_t last_index = 0; 169 size_t cicp_offset = 0; 170 size_t cicp_length = 0; 171 uint8_t cicp_tag[kCICPTagSize] = {}; 172 size_t cicp_pos = 0; 173 size_t pos = 0; 174 while (pos < len) { 175 const uint8_t* marker = &app_data[pos]; 176 if (pos + 4 > len) { 177 return kUnknownTf; 178 } 179 size_t marker_size = (marker[2] << 8) + marker[3] + 2; 180 if (pos + marker_size > len) { 181 return kUnknownTf; 182 } 183 if (marker_size < 18 || marker[0] != 0xff || marker[1] != kICCMarker || 184 memcmp(&marker[4], kICCSignature, 12) != 0) { 185 pos += marker_size; 186 continue; 187 } 188 uint8_t index = marker[16]; 189 uint8_t total = marker[17]; 190 const uint8_t* payload = marker + 18; 191 const size_t payload_size = marker_size - 18; 192 if (index != last_index + 1 || index > total) { 193 return kUnknownTf; 194 } 195 if (!FindCICPTag(payload, payload_size, last_index == 0, &cicp_offset, 196 &cicp_length, &cicp_tag[0], &cicp_pos)) { 197 return kUnknownTf; 198 } 199 if (cicp_pos == kCICPTagSize) { 200 break; 201 } 202 ++last_index; 203 } 204 if (cicp_pos >= kCICPTagSize && memcmp(cicp_tag, kCICPTagSignature, 4) == 0) { 205 return cicp_tag[9]; 206 } 207 return kUnknownTf; 208 } 209 210 uint8_t LookupCICPTransferFunctionFromICCProfile(const uint8_t* icc_data, 211 size_t len) { 212 size_t cicp_offset = 0; 213 size_t cicp_length = 0; 214 uint8_t cicp_tag[kCICPTagSize] = {}; 215 size_t cicp_pos = 0; 216 if (!FindCICPTag(icc_data, len, true, &cicp_offset, &cicp_length, 217 &cicp_tag[0], &cicp_pos)) { 218 return kUnknownTf; 219 } 220 if (cicp_pos >= kCICPTagSize && memcmp(cicp_tag, kCICPTagSignature, 4) == 0) { 221 return cicp_tag[9]; 222 } 223 return kUnknownTf; 224 } 225 226 JpegliDataType ConvertDataType(JxlDataType type) { 227 switch (type) { 228 case JXL_TYPE_UINT8: 229 return JPEGLI_TYPE_UINT8; 230 case JXL_TYPE_UINT16: 231 return JPEGLI_TYPE_UINT16; 232 case JXL_TYPE_FLOAT: 233 return JPEGLI_TYPE_FLOAT; 234 default: 235 return JPEGLI_TYPE_UINT8; 236 } 237 } 238 239 JpegliEndianness ConvertEndianness(JxlEndianness endianness) { 240 switch (endianness) { 241 case JXL_NATIVE_ENDIAN: 242 return JPEGLI_NATIVE_ENDIAN; 243 case JXL_LITTLE_ENDIAN: 244 return JPEGLI_LITTLE_ENDIAN; 245 case JXL_BIG_ENDIAN: 246 return JPEGLI_BIG_ENDIAN; 247 default: 248 return JPEGLI_NATIVE_ENDIAN; 249 } 250 } 251 252 void ToFloatRow(const uint8_t* row_in, JxlPixelFormat format, size_t len, 253 float* row_out) { 254 bool is_little_endian = 255 (format.endianness == JXL_LITTLE_ENDIAN || 256 (format.endianness == JXL_NATIVE_ENDIAN && IsLittleEndian())); 257 static constexpr double kMul8 = 1.0 / 255.0; 258 static constexpr double kMul16 = 1.0 / 65535.0; 259 if (format.data_type == JXL_TYPE_UINT8) { 260 for (size_t x = 0; x < len; ++x) { 261 row_out[x] = row_in[x] * kMul8; 262 } 263 } else if (format.data_type == JXL_TYPE_UINT16 && is_little_endian) { 264 for (size_t x = 0; x < len; ++x) { 265 row_out[x] = LoadLE16(&row_in[2 * x]) * kMul16; 266 } 267 } else if (format.data_type == JXL_TYPE_UINT16 && !is_little_endian) { 268 for (size_t x = 0; x < len; ++x) { 269 row_out[x] = LoadBE16(&row_in[2 * x]) * kMul16; 270 } 271 } else if (format.data_type == JXL_TYPE_FLOAT && is_little_endian) { 272 for (size_t x = 0; x < len; ++x) { 273 row_out[x] = LoadLEFloat(&row_in[4 * x]); 274 } 275 } else if (format.data_type == JXL_TYPE_FLOAT && !is_little_endian) { 276 for (size_t x = 0; x < len; ++x) { 277 row_out[x] = LoadBEFloat(&row_in[4 * x]); 278 } 279 } 280 } 281 282 Status EncodeJpegToTargetSize(const PackedPixelFile& ppf, 283 const JpegSettings& jpeg_settings, 284 size_t target_size, ThreadPool* pool, 285 std::vector<uint8_t>* output) { 286 output->clear(); 287 size_t best_error = std::numeric_limits<size_t>::max(); 288 float distance0 = -1.0f; 289 float distance1 = -1.0f; 290 float distance = 1.0f; 291 for (int step = 0; step < 15; ++step) { 292 JpegSettings settings = jpeg_settings; 293 settings.libjpeg_quality = 0; 294 settings.distance = distance; 295 settings.target_size = 0; 296 std::vector<uint8_t> compressed; 297 JXL_RETURN_IF_ERROR(EncodeJpeg(ppf, settings, pool, &compressed)); 298 size_t size = compressed.size(); 299 // prefer being under the target size to being over it 300 size_t error = size < target_size 301 ? target_size - size 302 : static_cast<size_t>(1.2f * (size - target_size)); 303 if (error < best_error) { 304 best_error = error; 305 std::swap(*output, compressed); 306 } 307 float rel_error = size * 1.0f / target_size; 308 if (std::abs(rel_error - 1.0f) < 0.002f) { 309 break; 310 } 311 if (size < target_size) { 312 distance1 = distance; 313 } else { 314 distance0 = distance; 315 } 316 if (distance1 == -1) { 317 distance *= std::pow(rel_error, 1.5) * 1.05; 318 } else if (distance0 == -1) { 319 distance *= std::pow(rel_error, 1.5) * 0.95; 320 } else { 321 distance = 0.5 * (distance0 + distance1); 322 } 323 } 324 return true; 325 } 326 327 } // namespace 328 329 Status EncodeJpeg(const PackedPixelFile& ppf, const JpegSettings& jpeg_settings, 330 ThreadPool* pool, std::vector<uint8_t>* compressed) { 331 if (jpeg_settings.libjpeg_quality > 0) { 332 auto encoder = Encoder::FromExtension(".jpg"); 333 encoder->SetOption("q", std::to_string(jpeg_settings.libjpeg_quality)); 334 if (!jpeg_settings.libjpeg_chroma_subsampling.empty()) { 335 encoder->SetOption("chroma_subsampling", 336 jpeg_settings.libjpeg_chroma_subsampling); 337 } 338 EncodedImage encoded; 339 JXL_RETURN_IF_ERROR(encoder->Encode(ppf, &encoded, pool)); 340 size_t target_size = encoded.bitstreams[0].size(); 341 return EncodeJpegToTargetSize(ppf, jpeg_settings, target_size, pool, 342 compressed); 343 } 344 if (jpeg_settings.target_size > 0) { 345 return EncodeJpegToTargetSize(ppf, jpeg_settings, jpeg_settings.target_size, 346 pool, compressed); 347 } 348 JXL_RETURN_IF_ERROR(VerifyInput(ppf)); 349 350 ColorEncoding color_encoding; 351 JXL_RETURN_IF_ERROR(GetColorEncoding(ppf, &color_encoding)); 352 353 ColorSpaceTransform c_transform(*JxlGetDefaultCms()); 354 ColorEncoding xyb_encoding; 355 if (jpeg_settings.xyb) { 356 if (ppf.info.num_color_channels != 3) { 357 return JXL_FAILURE("Only RGB input is supported in XYB mode."); 358 } 359 if (HasICCProfile(jpeg_settings.app_data)) { 360 return JXL_FAILURE("APP data ICC profile is not supported in XYB mode."); 361 } 362 const ColorEncoding& c_desired = ColorEncoding::LinearSRGB(false); 363 JXL_RETURN_IF_ERROR( 364 c_transform.Init(color_encoding, c_desired, 255.0f, ppf.info.xsize, 1)); 365 xyb_encoding.SetColorSpace(jxl::ColorSpace::kXYB); 366 xyb_encoding.SetRenderingIntent(jxl::RenderingIntent::kPerceptual); 367 JXL_RETURN_IF_ERROR(xyb_encoding.CreateICC()); 368 } 369 const ColorEncoding& output_encoding = 370 jpeg_settings.xyb ? xyb_encoding : color_encoding; 371 372 // We need to declare all the non-trivial destructor local variables 373 // before the call to setjmp(). 374 std::vector<uint8_t> pixels; 375 unsigned char* output_buffer = nullptr; 376 unsigned long output_size = 0; 377 std::vector<uint8_t> row_bytes; 378 size_t rowlen = RoundUpTo(ppf.info.xsize, MaxVectorSize()); 379 hwy::AlignedFreeUniquePtr<float[]> xyb_tmp = 380 hwy::AllocateAligned<float>(6 * rowlen); 381 hwy::AlignedFreeUniquePtr<float[]> premul_absorb = 382 hwy::AllocateAligned<float>(MaxVectorSize() * 12); 383 ComputePremulAbsorb(255.0f, premul_absorb.get()); 384 385 jpeg_compress_struct cinfo; 386 const auto try_catch_block = [&]() -> bool { 387 jpeg_error_mgr jerr; 388 jmp_buf env; 389 cinfo.err = jpegli_std_error(&jerr); 390 jerr.error_exit = &MyErrorExit; 391 if (setjmp(env)) { 392 return false; 393 } 394 cinfo.client_data = static_cast<void*>(&env); 395 jpegli_create_compress(&cinfo); 396 jpegli_mem_dest(&cinfo, &output_buffer, &output_size); 397 const JxlBasicInfo& info = ppf.info; 398 cinfo.image_width = info.xsize; 399 cinfo.image_height = info.ysize; 400 cinfo.input_components = info.num_color_channels; 401 cinfo.in_color_space = 402 cinfo.input_components == 1 ? JCS_GRAYSCALE : JCS_RGB; 403 if (jpeg_settings.xyb) { 404 jpegli_set_xyb_mode(&cinfo); 405 } else if (jpeg_settings.use_std_quant_tables) { 406 jpegli_use_standard_quant_tables(&cinfo); 407 } 408 uint8_t cicp_tf = kUnknownTf; 409 if (!jpeg_settings.app_data.empty()) { 410 cicp_tf = LookupCICPTransferFunctionFromAppData( 411 jpeg_settings.app_data.data(), jpeg_settings.app_data.size()); 412 } else if (!output_encoding.IsSRGB()) { 413 cicp_tf = LookupCICPTransferFunctionFromICCProfile( 414 output_encoding.ICC().data(), output_encoding.ICC().size()); 415 } 416 jpegli_set_cicp_transfer_function(&cinfo, cicp_tf); 417 jpegli_set_defaults(&cinfo); 418 if (!jpeg_settings.chroma_subsampling.empty()) { 419 if (jpeg_settings.chroma_subsampling == "444") { 420 cinfo.comp_info[0].h_samp_factor = 1; 421 cinfo.comp_info[0].v_samp_factor = 1; 422 } else if (jpeg_settings.chroma_subsampling == "440") { 423 cinfo.comp_info[0].h_samp_factor = 1; 424 cinfo.comp_info[0].v_samp_factor = 2; 425 } else if (jpeg_settings.chroma_subsampling == "422") { 426 cinfo.comp_info[0].h_samp_factor = 2; 427 cinfo.comp_info[0].v_samp_factor = 1; 428 } else if (jpeg_settings.chroma_subsampling == "420") { 429 cinfo.comp_info[0].h_samp_factor = 2; 430 cinfo.comp_info[0].v_samp_factor = 2; 431 } else { 432 return false; 433 } 434 for (int i = 1; i < cinfo.num_components; ++i) { 435 cinfo.comp_info[i].h_samp_factor = 1; 436 cinfo.comp_info[i].v_samp_factor = 1; 437 } 438 } 439 jpegli_enable_adaptive_quantization( 440 &cinfo, TO_JXL_BOOL(jpeg_settings.use_adaptive_quantization)); 441 if (jpeg_settings.psnr_target > 0.0) { 442 jpegli_set_psnr(&cinfo, jpeg_settings.psnr_target, 443 jpeg_settings.search_tolerance, 444 jpeg_settings.min_distance, jpeg_settings.max_distance); 445 } else if (jpeg_settings.quality > 0.0) { 446 float distance = jpegli_quality_to_distance(jpeg_settings.quality); 447 jpegli_set_distance(&cinfo, distance, TRUE); 448 } else { 449 jpegli_set_distance(&cinfo, jpeg_settings.distance, TRUE); 450 } 451 jpegli_set_progressive_level(&cinfo, jpeg_settings.progressive_level); 452 cinfo.optimize_coding = TO_JXL_BOOL(jpeg_settings.optimize_coding); 453 if (!jpeg_settings.app_data.empty()) { 454 // Make sure jpegli_start_compress() does not write any APP markers. 455 cinfo.write_JFIF_header = JXL_FALSE; 456 cinfo.write_Adobe_marker = JXL_FALSE; 457 } 458 const PackedImage& image = ppf.frames[0].color; 459 if (jpeg_settings.xyb) { 460 jpegli_set_input_format(&cinfo, JPEGLI_TYPE_FLOAT, JPEGLI_NATIVE_ENDIAN); 461 } else { 462 jpegli_set_input_format(&cinfo, ConvertDataType(image.format.data_type), 463 ConvertEndianness(image.format.endianness)); 464 } 465 jpegli_start_compress(&cinfo, TRUE); 466 if (!jpeg_settings.app_data.empty()) { 467 JXL_RETURN_IF_ERROR(WriteAppData(&cinfo, jpeg_settings.app_data)); 468 } 469 if ((jpeg_settings.app_data.empty() && !output_encoding.IsSRGB()) || 470 jpeg_settings.xyb) { 471 jpegli_write_icc_profile(&cinfo, output_encoding.ICC().data(), 472 output_encoding.ICC().size()); 473 } 474 const uint8_t* pixels = reinterpret_cast<const uint8_t*>(image.pixels()); 475 if (jpeg_settings.xyb) { 476 float* src_buf = c_transform.BufSrc(0); 477 float* dst_buf = c_transform.BufDst(0); 478 for (size_t y = 0; y < image.ysize; ++y) { 479 // convert to float 480 ToFloatRow(&pixels[y * image.stride], image.format, 3 * image.xsize, 481 src_buf); 482 // convert to linear srgb 483 if (!c_transform.Run(0, src_buf, dst_buf, image.xsize)) { 484 return false; 485 } 486 // deinterleave channels 487 float* row0 = &xyb_tmp[0]; 488 float* row1 = &xyb_tmp[rowlen]; 489 float* row2 = &xyb_tmp[2 * rowlen]; 490 for (size_t x = 0; x < image.xsize; ++x) { 491 row0[x] = dst_buf[3 * x + 0]; 492 row1[x] = dst_buf[3 * x + 1]; 493 row2[x] = dst_buf[3 * x + 2]; 494 } 495 // convert to xyb 496 LinearRGBRowToXYB(row0, row1, row2, premul_absorb.get(), image.xsize); 497 // scale xyb 498 ScaleXYBRow(row0, row1, row2, image.xsize); 499 // interleave channels 500 float* row_out = &xyb_tmp[3 * rowlen]; 501 for (size_t x = 0; x < image.xsize; ++x) { 502 row_out[3 * x + 0] = row0[x]; 503 row_out[3 * x + 1] = row1[x]; 504 row_out[3 * x + 2] = row2[x]; 505 } 506 // feed to jpegli as native endian floats 507 JSAMPROW row[] = {reinterpret_cast<uint8_t*>(row_out)}; 508 jpegli_write_scanlines(&cinfo, row, 1); 509 } 510 } else { 511 row_bytes.resize(image.stride); 512 if (cinfo.num_components == static_cast<int>(image.format.num_channels)) { 513 for (size_t y = 0; y < info.ysize; ++y) { 514 memcpy(row_bytes.data(), pixels + y * image.stride, image.stride); 515 JSAMPROW row[] = {row_bytes.data()}; 516 jpegli_write_scanlines(&cinfo, row, 1); 517 } 518 } else { 519 for (size_t y = 0; y < info.ysize; ++y) { 520 int bytes_per_channel = 521 PackedImage::BitsPerChannel(image.format.data_type) / 8; 522 int bytes_per_pixel = cinfo.num_components * bytes_per_channel; 523 for (size_t x = 0; x < info.xsize; ++x) { 524 memcpy(&row_bytes[x * bytes_per_pixel], 525 &pixels[y * image.stride + x * image.pixel_stride()], 526 bytes_per_pixel); 527 } 528 JSAMPROW row[] = {row_bytes.data()}; 529 jpegli_write_scanlines(&cinfo, row, 1); 530 } 531 } 532 } 533 jpegli_finish_compress(&cinfo); 534 compressed->resize(output_size); 535 std::copy_n(output_buffer, output_size, compressed->data()); 536 return true; 537 }; 538 bool success = try_catch_block(); 539 jpegli_destroy_compress(&cinfo); 540 if (output_buffer) free(output_buffer); 541 return success; 542 } 543 544 } // namespace extras 545 } // namespace jxl