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