jpg.cc (22308B)
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/jpg.h" 7 8 #if JPEGXL_ENABLE_JPEG 9 #include "lib/jxl/base/include_jpeglib.h" // NOLINT 10 #endif 11 12 #include <algorithm> 13 #include <array> 14 #include <cmath> 15 #include <cstdint> 16 #include <fstream> 17 #include <memory> 18 #include <sstream> 19 #include <utility> 20 #include <vector> 21 22 #include "lib/extras/exif.h" 23 #include "lib/jxl/base/common.h" 24 #include "lib/jxl/base/sanitizers.h" 25 #include "lib/jxl/base/status.h" 26 #if JPEGXL_ENABLE_SJPEG 27 #include "sjpeg.h" 28 #include "sjpegi.h" 29 #endif 30 31 namespace jxl { 32 namespace extras { 33 34 #if JPEGXL_ENABLE_JPEG 35 namespace { 36 37 constexpr unsigned char kICCSignature[12] = { 38 0x49, 0x43, 0x43, 0x5F, 0x50, 0x52, 0x4F, 0x46, 0x49, 0x4C, 0x45, 0x00}; 39 constexpr int kICCMarker = JPEG_APP0 + 2; 40 constexpr size_t kMaxBytesInMarker = 65533; 41 42 constexpr unsigned char kExifSignature[6] = {0x45, 0x78, 0x69, 43 0x66, 0x00, 0x00}; 44 constexpr int kExifMarker = JPEG_APP0 + 1; 45 46 enum class JpegEncoder { 47 kLibJpeg, 48 kSJpeg, 49 }; 50 51 // Popular jpeg scan scripts 52 // The fields of the individual scans are: 53 // comps_in_scan, component_index[], Ss, Se, Ah, Al 54 constexpr auto kScanScript1 = to_array<jpeg_scan_info>({ 55 {1, {0}, 0, 0, 0, 0}, // 56 {1, {1}, 0, 0, 0, 0}, // 57 {1, {2}, 0, 0, 0, 0}, // 58 {1, {0}, 1, 8, 0, 0}, // 59 {1, {0}, 9, 63, 0, 0}, // 60 {1, {1}, 1, 63, 0, 0}, // 61 {1, {2}, 1, 63, 0, 0} // 62 }); 63 constexpr size_t kNumScans1 = kScanScript1.size(); 64 65 constexpr auto kScanScript2 = to_array<jpeg_scan_info>({ 66 {1, {0}, 0, 0, 0, 0}, // 67 {1, {1}, 0, 0, 0, 0}, // 68 {1, {2}, 0, 0, 0, 0}, // 69 {1, {0}, 1, 2, 0, 1}, // 70 {1, {0}, 3, 63, 0, 1}, // 71 {1, {0}, 1, 63, 1, 0}, // 72 {1, {1}, 1, 63, 0, 0}, // 73 {1, {2}, 1, 63, 0, 0} // 74 }); 75 constexpr size_t kNumScans2 = kScanScript2.size(); 76 77 constexpr auto kScanScript3 = to_array<jpeg_scan_info>({ 78 {1, {0}, 0, 0, 0, 0}, // 79 {1, {1}, 0, 0, 0, 0}, // 80 {1, {2}, 0, 0, 0, 0}, // 81 {1, {0}, 1, 63, 0, 2}, // 82 {1, {0}, 1, 63, 2, 1}, // 83 {1, {0}, 1, 63, 1, 0}, // 84 {1, {1}, 1, 63, 0, 0}, // 85 {1, {2}, 1, 63, 0, 0} // 86 }); 87 constexpr size_t kNumScans3 = kScanScript3.size(); 88 89 constexpr auto kScanScript4 = to_array<jpeg_scan_info>({ 90 {3, {0, 1, 2}, 0, 0, 0, 1}, // 91 {1, {0}, 1, 5, 0, 2}, // 92 {1, {2}, 1, 63, 0, 1}, // 93 {1, {1}, 1, 63, 0, 1}, // 94 {1, {0}, 6, 63, 0, 2}, // 95 {1, {0}, 1, 63, 2, 1}, // 96 {3, {0, 1, 2}, 0, 0, 1, 0}, // 97 {1, {2}, 1, 63, 1, 0}, // 98 {1, {1}, 1, 63, 1, 0}, // 99 {1, {0}, 1, 63, 1, 0} // 100 }); 101 constexpr size_t kNumScans4 = kScanScript4.size(); 102 103 constexpr auto kScanScript5 = to_array<jpeg_scan_info>({ 104 {3, {0, 1, 2}, 0, 0, 0, 1}, // 105 {1, {0}, 1, 5, 0, 2}, // 106 {1, {1}, 1, 5, 0, 2}, // 107 {1, {2}, 1, 5, 0, 2}, // 108 {1, {1}, 6, 63, 0, 2}, // 109 {1, {2}, 6, 63, 0, 2}, // 110 {1, {0}, 6, 63, 0, 2}, // 111 {1, {0}, 1, 63, 2, 1}, // 112 {1, {1}, 1, 63, 2, 1}, // 113 {1, {2}, 1, 63, 2, 1}, // 114 {3, {0, 1, 2}, 0, 0, 1, 0}, // 115 {1, {0}, 1, 63, 1, 0}, // 116 {1, {1}, 1, 63, 1, 0}, // 117 {1, {2}, 1, 63, 1, 0} // 118 }); 119 constexpr size_t kNumScans5 = kScanScript5.size(); 120 121 // default progressive mode of jpegli 122 constexpr auto kScanScript6 = to_array<jpeg_scan_info>({ 123 {3, {0, 1, 2}, 0, 0, 0, 0}, // 124 {1, {0}, 1, 2, 0, 0}, // 125 {1, {1}, 1, 2, 0, 0}, // 126 {1, {2}, 1, 2, 0, 0}, // 127 {1, {0}, 3, 63, 0, 2}, // 128 {1, {1}, 3, 63, 0, 2}, // 129 {1, {2}, 3, 63, 0, 2}, // 130 {1, {0}, 3, 63, 2, 1}, // 131 {1, {1}, 3, 63, 2, 1}, // 132 {1, {2}, 3, 63, 2, 1}, // 133 {1, {0}, 3, 63, 1, 0}, // 134 {1, {1}, 3, 63, 1, 0}, // 135 {1, {2}, 3, 63, 1, 0}, // 136 }); 137 constexpr size_t kNumScans6 = kScanScript6.size(); 138 139 // Adapt RGB scan info to grayscale jpegs. 140 void FilterScanComponents(const jpeg_compress_struct* cinfo, 141 jpeg_scan_info* si) { 142 const int all_comps_in_scan = si->comps_in_scan; 143 si->comps_in_scan = 0; 144 for (int j = 0; j < all_comps_in_scan; ++j) { 145 const int component = si->component_index[j]; 146 if (component < cinfo->input_components) { 147 si->component_index[si->comps_in_scan++] = component; 148 } 149 } 150 } 151 152 Status SetJpegProgression(int progressive_id, 153 std::vector<jpeg_scan_info>* scan_infos, 154 jpeg_compress_struct* cinfo) { 155 if (progressive_id < 0) { 156 return true; 157 } 158 if (progressive_id == 0) { 159 jpeg_simple_progression(cinfo); 160 return true; 161 } 162 const jpeg_scan_info* kScanScripts[] = { 163 kScanScript1.data(), kScanScript2.data(), kScanScript3.data(), 164 kScanScript4.data(), kScanScript5.data(), kScanScript6.data()}; 165 constexpr auto kNumScans = to_array<size_t>( 166 {kNumScans1, kNumScans2, kNumScans3, kNumScans4, kNumScans5, kNumScans6}); 167 if (progressive_id > static_cast<int>(kNumScans.size())) { 168 return JXL_FAILURE("Unknown jpeg scan script id %d", progressive_id); 169 } 170 const jpeg_scan_info* scan_script = kScanScripts[progressive_id - 1]; 171 const size_t num_scans = kNumScans[progressive_id - 1]; 172 // filter scan script for number of components 173 for (size_t i = 0; i < num_scans; ++i) { 174 jpeg_scan_info scan_info = scan_script[i]; 175 FilterScanComponents(cinfo, &scan_info); 176 if (scan_info.comps_in_scan > 0) { 177 scan_infos->emplace_back(scan_info); 178 } 179 } 180 cinfo->scan_info = scan_infos->data(); 181 cinfo->num_scans = scan_infos->size(); 182 return true; 183 } 184 185 void WriteICCProfile(jpeg_compress_struct* const cinfo, 186 const std::vector<uint8_t>& icc) { 187 constexpr size_t kMaxIccBytesInMarker = 188 kMaxBytesInMarker - sizeof kICCSignature - 2; 189 const int num_markers = 190 static_cast<int>(DivCeil(icc.size(), kMaxIccBytesInMarker)); 191 size_t begin = 0; 192 for (int current_marker = 0; current_marker < num_markers; ++current_marker) { 193 const size_t length = std::min(kMaxIccBytesInMarker, icc.size() - begin); 194 jpeg_write_m_header( 195 cinfo, kICCMarker, 196 static_cast<unsigned int>(length + sizeof kICCSignature + 2)); 197 for (const unsigned char c : kICCSignature) { 198 jpeg_write_m_byte(cinfo, c); 199 } 200 jpeg_write_m_byte(cinfo, current_marker + 1); 201 jpeg_write_m_byte(cinfo, num_markers); 202 for (size_t i = 0; i < length; ++i) { 203 jpeg_write_m_byte(cinfo, icc[begin]); 204 ++begin; 205 } 206 } 207 } 208 void WriteExif(jpeg_compress_struct* const cinfo, 209 const std::vector<uint8_t>& exif) { 210 jpeg_write_m_header( 211 cinfo, kExifMarker, 212 static_cast<unsigned int>(exif.size() + sizeof kExifSignature)); 213 for (const unsigned char c : kExifSignature) { 214 jpeg_write_m_byte(cinfo, c); 215 } 216 for (uint8_t c : exif) { 217 jpeg_write_m_byte(cinfo, c); 218 } 219 } 220 221 Status SetChromaSubsampling(const std::string& subsampling, 222 jpeg_compress_struct* const cinfo) { 223 const std::pair<const char*, 224 std::pair<std::array<uint8_t, 3>, std::array<uint8_t, 3>>> 225 options[] = {{"444", {{{1, 1, 1}}, {{1, 1, 1}}}}, 226 {"420", {{{2, 1, 1}}, {{2, 1, 1}}}}, 227 {"422", {{{2, 1, 1}}, {{1, 1, 1}}}}, 228 {"440", {{{1, 1, 1}}, {{2, 1, 1}}}}}; 229 for (const auto& option : options) { 230 if (subsampling == option.first) { 231 for (size_t i = 0; i < 3; i++) { 232 cinfo->comp_info[i].h_samp_factor = option.second.first[i]; 233 cinfo->comp_info[i].v_samp_factor = option.second.second[i]; 234 } 235 return true; 236 } 237 } 238 return false; 239 } 240 241 struct JpegParams { 242 // Common between sjpeg and libjpeg 243 int quality = 100; 244 std::string chroma_subsampling = "444"; 245 // Libjpeg parameters 246 int progressive_id = -1; 247 bool optimize_coding = true; 248 bool is_xyb = false; 249 // Sjpeg parameters 250 int libjpeg_quality = 0; 251 std::string libjpeg_chroma_subsampling = "444"; 252 float psnr_target = 0; 253 std::string custom_base_quant_fn; 254 float search_q_start = 65.0f; 255 float search_q_min = 1.0f; 256 float search_q_max = 100.0f; 257 int search_max_iters = 20; 258 float search_tolerance = 0.1f; 259 float search_q_precision = 0.01f; 260 float search_first_iter_slope = 3.0f; 261 bool enable_adaptive_quant = true; 262 }; 263 264 Status EncodeWithLibJpeg(const PackedImage& image, const JxlBasicInfo& info, 265 const std::vector<uint8_t>& icc, 266 std::vector<uint8_t> exif, const JpegParams& params, 267 std::vector<uint8_t>* bytes) { 268 if (BITS_IN_JSAMPLE != 8 || sizeof(JSAMPLE) != 1) { 269 return JXL_FAILURE("Only 8 bit JSAMPLE is supported."); 270 } 271 jpeg_compress_struct cinfo = {}; 272 jpeg_error_mgr jerr; 273 cinfo.err = jpeg_std_error(&jerr); 274 jpeg_create_compress(&cinfo); 275 unsigned char* buffer = nullptr; 276 #ifdef LIBJPEG_TURBO_VERSION 277 unsigned long size = 0; // NOLINT 278 #else 279 size_t size = 0; // NOLINT 280 #endif 281 jpeg_mem_dest(&cinfo, &buffer, &size); 282 cinfo.image_width = image.xsize; 283 cinfo.image_height = image.ysize; 284 cinfo.input_components = info.num_color_channels; 285 cinfo.in_color_space = info.num_color_channels == 1 ? JCS_GRAYSCALE : JCS_RGB; 286 jpeg_set_defaults(&cinfo); 287 cinfo.optimize_coding = static_cast<boolean>(params.optimize_coding); 288 if (cinfo.input_components == 3) { 289 JXL_RETURN_IF_ERROR( 290 SetChromaSubsampling(params.chroma_subsampling, &cinfo)); 291 } 292 if (params.is_xyb) { 293 // Tell libjpeg not to convert XYB data to YCbCr. 294 jpeg_set_colorspace(&cinfo, JCS_RGB); 295 } 296 jpeg_set_quality(&cinfo, params.quality, TRUE); 297 std::vector<jpeg_scan_info> scan_infos; 298 JXL_RETURN_IF_ERROR( 299 SetJpegProgression(params.progressive_id, &scan_infos, &cinfo)); 300 jpeg_start_compress(&cinfo, TRUE); 301 if (!icc.empty()) { 302 WriteICCProfile(&cinfo, icc); 303 } 304 if (!exif.empty()) { 305 ResetExifOrientation(exif); 306 WriteExif(&cinfo, exif); 307 } 308 if (cinfo.input_components > 3 || cinfo.input_components < 0) 309 return JXL_FAILURE("invalid numbers of components"); 310 311 std::vector<uint8_t> row_bytes(image.stride); 312 const uint8_t* pixels = reinterpret_cast<const uint8_t*>(image.pixels()); 313 if (cinfo.num_components == static_cast<int>(image.format.num_channels) && 314 image.format.data_type == JXL_TYPE_UINT8) { 315 for (size_t y = 0; y < info.ysize; ++y) { 316 memcpy(row_bytes.data(), pixels + y * image.stride, image.stride); 317 JSAMPROW row[] = {row_bytes.data()}; 318 jpeg_write_scanlines(&cinfo, row, 1); 319 } 320 } else if (image.format.data_type == JXL_TYPE_UINT8) { 321 for (size_t y = 0; y < info.ysize; ++y) { 322 const uint8_t* image_row = pixels + y * image.stride; 323 for (size_t x = 0; x < info.xsize; ++x) { 324 const uint8_t* image_pixel = image_row + x * image.pixel_stride(); 325 memcpy(&row_bytes[x * cinfo.num_components], image_pixel, 326 cinfo.num_components); 327 } 328 JSAMPROW row[] = {row_bytes.data()}; 329 jpeg_write_scanlines(&cinfo, row, 1); 330 } 331 } else { 332 for (size_t y = 0; y < info.ysize; ++y) { 333 const uint8_t* image_row = pixels + y * image.stride; 334 for (size_t x = 0; x < info.xsize; ++x) { 335 const uint8_t* image_pixel = image_row + x * image.pixel_stride(); 336 for (int c = 0; c < cinfo.num_components; ++c) { 337 uint32_t val16 = (image_pixel[2 * c] << 8) + image_pixel[2 * c + 1]; 338 row_bytes[x * cinfo.num_components + c] = (val16 + 128) / 257; 339 } 340 } 341 JSAMPROW row[] = {row_bytes.data()}; 342 jpeg_write_scanlines(&cinfo, row, 1); 343 } 344 } 345 jpeg_finish_compress(&cinfo); 346 jpeg_destroy_compress(&cinfo); 347 bytes->resize(size); 348 // Compressed image data is initialized by libjpeg, which we are not 349 // instrumenting with msan. 350 msan::UnpoisonMemory(buffer, size); 351 std::copy_n(buffer, size, bytes->data()); 352 std::free(buffer); 353 return true; 354 } 355 356 #if JPEGXL_ENABLE_SJPEG 357 struct MySearchHook : public sjpeg::SearchHook { 358 uint8_t base_tables[2][64]; 359 float q_start; 360 float q_precision; 361 float first_iter_slope; 362 void ReadBaseTables(const std::string& fn) { 363 const uint8_t kJPEGAnnexKMatrices[2][64] = { 364 {16, 11, 10, 16, 24, 40, 51, 61, 12, 12, 14, 19, 26, 58, 60, 55, 365 14, 13, 16, 24, 40, 57, 69, 56, 14, 17, 22, 29, 51, 87, 80, 62, 366 18, 22, 37, 56, 68, 109, 103, 77, 24, 35, 55, 64, 81, 104, 113, 92, 367 49, 64, 78, 87, 103, 121, 120, 101, 72, 92, 95, 98, 112, 100, 103, 99}, 368 {17, 18, 24, 47, 99, 99, 99, 99, 18, 21, 26, 66, 99, 99, 99, 99, 369 24, 26, 56, 99, 99, 99, 99, 99, 47, 66, 99, 99, 99, 99, 99, 99, 370 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 371 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99}}; 372 memcpy(base_tables[0], kJPEGAnnexKMatrices[0], sizeof(base_tables[0])); 373 memcpy(base_tables[1], kJPEGAnnexKMatrices[1], sizeof(base_tables[1])); 374 if (!fn.empty()) { 375 std::ifstream f(fn); 376 std::string line; 377 int idx = 0; 378 while (idx < 128 && std::getline(f, line)) { 379 if (line.empty() || line[0] == '#') continue; 380 std::istringstream line_stream(line); 381 std::string token; 382 while (idx < 128 && std::getline(line_stream, token, ',')) { 383 uint8_t val = std::stoi(token); 384 base_tables[idx / 64][idx % 64] = val; 385 idx++; 386 } 387 } 388 } 389 } 390 bool Setup(const sjpeg::EncoderParam& param) override { 391 sjpeg::SearchHook::Setup(param); 392 q = q_start; 393 return true; 394 } 395 void NextMatrix(int idx, uint8_t dst[64]) override { 396 float factor = (q <= 0) ? 5000.0f 397 : (q < 50.0f) ? 5000.0f / q 398 : (q < 100.0f) ? 2 * (100.0f - q) 399 : 0.0f; 400 sjpeg::SetQuantMatrix(base_tables[idx], factor, dst); 401 } 402 bool Update(float result) override { 403 value = result; 404 if (std::fabs(value - target) < tolerance * target) { 405 return true; 406 } 407 if (value > target) { 408 qmax = q; 409 } else { 410 qmin = q; 411 } 412 if (qmin == qmax) { 413 return true; 414 } 415 const float last_q = q; 416 if (pass == 0) { 417 q += first_iter_slope * 418 (for_size ? 0.1 * std::log(target / value) : (target - value)); 419 q = std::max(qmin, std::min(qmax, q)); 420 } else { 421 q = (qmin + qmax) / 2.; 422 } 423 return (pass > 0 && std::fabs(q - last_q) < q_precision); 424 } 425 ~MySearchHook() override = default; 426 }; 427 #endif 428 429 Status EncodeWithSJpeg(const PackedImage& image, const JxlBasicInfo& info, 430 const std::vector<uint8_t>& icc, 431 std::vector<uint8_t> exif, const JpegParams& params, 432 std::vector<uint8_t>* bytes) { 433 #if !JPEGXL_ENABLE_SJPEG 434 return JXL_FAILURE("JPEG XL was built without sjpeg support"); 435 #else 436 if (image.format.data_type != JXL_TYPE_UINT8) { 437 return JXL_FAILURE("Unsupported pixel data type"); 438 } 439 if (info.alpha_bits > 0) { 440 return JXL_FAILURE("alpha is not supported"); 441 } 442 sjpeg::EncoderParam param(params.quality); 443 if (!icc.empty()) { 444 param.iccp.assign(icc.begin(), icc.end()); 445 } 446 if (!exif.empty()) { 447 ResetExifOrientation(exif); 448 param.exif.assign(exif.begin(), exif.end()); 449 } 450 if (params.chroma_subsampling == "444") { 451 param.yuv_mode = SJPEG_YUV_444; 452 } else if (params.chroma_subsampling == "420") { 453 param.yuv_mode = SJPEG_YUV_420; 454 } else if (params.chroma_subsampling == "420sharp") { 455 param.yuv_mode = SJPEG_YUV_SHARP; 456 } else { 457 return JXL_FAILURE("sjpeg does not support this chroma subsampling mode"); 458 } 459 param.adaptive_quantization = params.enable_adaptive_quant; 460 std::unique_ptr<MySearchHook> hook; 461 if (params.libjpeg_quality > 0) { 462 JpegParams libjpeg_params; 463 libjpeg_params.quality = params.libjpeg_quality; 464 libjpeg_params.chroma_subsampling = params.libjpeg_chroma_subsampling; 465 std::vector<uint8_t> libjpeg_bytes; 466 JXL_RETURN_IF_ERROR(EncodeWithLibJpeg(image, info, icc, exif, 467 libjpeg_params, &libjpeg_bytes)); 468 param.target_mode = sjpeg::EncoderParam::TARGET_SIZE; 469 param.target_value = libjpeg_bytes.size(); 470 } 471 if (params.psnr_target > 0) { 472 param.target_mode = sjpeg::EncoderParam::TARGET_PSNR; 473 param.target_value = params.psnr_target; 474 } 475 if (param.target_mode != sjpeg::EncoderParam::TARGET_NONE) { 476 param.passes = params.search_max_iters; 477 param.tolerance = params.search_tolerance; 478 param.qmin = params.search_q_min; 479 param.qmax = params.search_q_max; 480 hook = jxl::make_unique<MySearchHook>(); 481 hook->ReadBaseTables(params.custom_base_quant_fn); 482 hook->q_start = params.search_q_start; 483 hook->q_precision = params.search_q_precision; 484 hook->first_iter_slope = params.search_first_iter_slope; 485 param.search_hook = hook.get(); 486 } 487 size_t stride = info.xsize * 3; 488 const uint8_t* pixels = reinterpret_cast<const uint8_t*>(image.pixels()); 489 std::string output; 490 JXL_RETURN_IF_ERROR( 491 sjpeg::Encode(pixels, image.xsize, image.ysize, stride, param, &output)); 492 bytes->assign( 493 reinterpret_cast<const uint8_t*>(output.data()), 494 reinterpret_cast<const uint8_t*>(output.data() + output.size())); 495 return true; 496 #endif 497 } 498 499 Status EncodeImageJPG(const PackedImage& image, const JxlBasicInfo& info, 500 const std::vector<uint8_t>& icc, 501 std::vector<uint8_t> exif, JpegEncoder encoder, 502 const JpegParams& params, ThreadPool* pool, 503 std::vector<uint8_t>* bytes) { 504 if (params.quality > 100) { 505 return JXL_FAILURE("please specify a 0-100 JPEG quality"); 506 } 507 508 switch (encoder) { 509 case JpegEncoder::kLibJpeg: 510 JXL_RETURN_IF_ERROR( 511 EncodeWithLibJpeg(image, info, icc, std::move(exif), params, bytes)); 512 break; 513 case JpegEncoder::kSJpeg: 514 JXL_RETURN_IF_ERROR( 515 EncodeWithSJpeg(image, info, icc, std::move(exif), params, bytes)); 516 break; 517 default: 518 return JXL_FAILURE("tried to use an unknown JPEG encoder"); 519 } 520 521 return true; 522 } 523 524 class JPEGEncoder : public Encoder { 525 std::vector<JxlPixelFormat> AcceptedFormats() const override { 526 std::vector<JxlPixelFormat> formats; 527 for (const uint32_t num_channels : {1, 2, 3, 4}) { 528 for (JxlEndianness endianness : {JXL_BIG_ENDIAN, JXL_LITTLE_ENDIAN}) { 529 formats.push_back(JxlPixelFormat{/*num_channels=*/num_channels, 530 /*data_type=*/JXL_TYPE_UINT8, 531 /*endianness=*/endianness, 532 /*align=*/0}); 533 } 534 formats.push_back(JxlPixelFormat{/*num_channels=*/num_channels, 535 /*data_type=*/JXL_TYPE_UINT16, 536 /*endianness=*/JXL_BIG_ENDIAN, 537 /*align=*/0}); 538 } 539 return formats; 540 } 541 Status Encode(const PackedPixelFile& ppf, EncodedImage* encoded_image, 542 ThreadPool* pool) const override { 543 JXL_RETURN_IF_ERROR(VerifyBasicInfo(ppf.info)); 544 JpegEncoder jpeg_encoder = JpegEncoder::kLibJpeg; 545 JpegParams params; 546 for (const auto& it : options()) { 547 if (it.first == "q") { 548 std::istringstream is(it.second); 549 JXL_RETURN_IF_ERROR(static_cast<bool>(is >> params.quality)); 550 } else if (it.first == "libjpeg_quality") { 551 std::istringstream is(it.second); 552 JXL_RETURN_IF_ERROR(static_cast<bool>(is >> params.libjpeg_quality)); 553 } else if (it.first == "chroma_subsampling") { 554 params.chroma_subsampling = it.second; 555 } else if (it.first == "libjpeg_chroma_subsampling") { 556 params.libjpeg_chroma_subsampling = it.second; 557 } else if (it.first == "jpeg_encoder") { 558 if (it.second == "libjpeg") { 559 jpeg_encoder = JpegEncoder::kLibJpeg; 560 } else if (it.second == "sjpeg") { 561 jpeg_encoder = JpegEncoder::kSJpeg; 562 } else { 563 return JXL_FAILURE("unknown jpeg encoder \"%s\"", it.second.c_str()); 564 } 565 } else if (it.first == "progressive") { 566 std::istringstream is(it.second); 567 JXL_RETURN_IF_ERROR(static_cast<bool>(is >> params.progressive_id)); 568 } else if (it.first == "optimize" && it.second == "OFF") { 569 params.optimize_coding = false; 570 } else if (it.first == "adaptive_q" && it.second == "OFF") { 571 params.enable_adaptive_quant = false; 572 } else if (it.first == "psnr") { 573 params.psnr_target = std::stof(it.second); 574 } else if (it.first == "base_quant_fn") { 575 params.custom_base_quant_fn = it.second; 576 } else if (it.first == "search_q_start") { 577 params.search_q_start = std::stof(it.second); 578 } else if (it.first == "search_q_min") { 579 params.search_q_min = std::stof(it.second); 580 } else if (it.first == "search_q_max") { 581 params.search_q_max = std::stof(it.second); 582 } else if (it.first == "search_max_iters") { 583 params.search_max_iters = std::stoi(it.second); 584 } else if (it.first == "search_tolerance") { 585 params.search_tolerance = std::stof(it.second); 586 } else if (it.first == "search_q_precision") { 587 params.search_q_precision = std::stof(it.second); 588 } else if (it.first == "search_first_iter_slope") { 589 params.search_first_iter_slope = std::stof(it.second); 590 } 591 } 592 params.is_xyb = (ppf.color_encoding.color_space == JXL_COLOR_SPACE_XYB); 593 encoded_image->bitstreams.clear(); 594 encoded_image->bitstreams.reserve(ppf.frames.size()); 595 for (const auto& frame : ppf.frames) { 596 JXL_RETURN_IF_ERROR(VerifyPackedImage(frame.color, ppf.info)); 597 encoded_image->bitstreams.emplace_back(); 598 JXL_RETURN_IF_ERROR(EncodeImageJPG( 599 frame.color, ppf.info, ppf.icc, ppf.metadata.exif, jpeg_encoder, 600 params, pool, &encoded_image->bitstreams.back())); 601 } 602 return true; 603 } 604 }; 605 606 } // namespace 607 #endif 608 609 std::unique_ptr<Encoder> GetJPEGEncoder() { 610 #if JPEGXL_ENABLE_JPEG 611 return jxl::make_unique<JPEGEncoder>(); 612 #else 613 return nullptr; 614 #endif 615 } 616 617 } // namespace extras 618 } // namespace jxl