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