1 /* Copyright 2015 The TensorFlow Authors. All Rights Reserved. 2 3 Licensed under the Apache License, Version 2.0 (the "License"); 4 you may not use this file except in compliance with the License. 5 You may obtain a copy of the License at 6 7 http://www.apache.org/licenses/LICENSE-2.0 8 9 Unless required by applicable law or agreed to in writing, software 10 distributed under the License is distributed on an "AS IS" BASIS, 11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 See the License for the specific language governing permissions and 13 limitations under the License. 14 ==============================================================================*/ 15 16 // This file defines functions to compress and uncompress JPEG data 17 // to and from memory, as well as some direct manipulations of JPEG string 18 19 #include "tensorflow/core/lib/jpeg/jpeg_mem.h" 20 21 #include <setjmp.h> 22 #include <string.h> 23 #include <algorithm> 24 #include <memory> 25 #include <string> 26 #include <utility> 27 28 #include "tensorflow/core/lib/jpeg/jpeg_handle.h" 29 #include "tensorflow/core/platform/dynamic_annotations.h" 30 #include "tensorflow/core/platform/logging.h" 31 #include "tensorflow/core/platform/mem.h" 32 #include "tensorflow/core/platform/types.h" 33 34 namespace tensorflow { 35 namespace jpeg { 36 37 // ----------------------------------------------------------------------------- 38 // Decompression 39 40 namespace { 41 42 enum JPEGErrors { 43 JPEGERRORS_OK, 44 JPEGERRORS_UNEXPECTED_END_OF_DATA, 45 JPEGERRORS_BAD_PARAM 46 }; 47 48 // Prevent bad compiler behavior in ASAN mode by wrapping most of the 49 // arguments in a struct struct. 50 class FewerArgsForCompiler { 51 public: 52 FewerArgsForCompiler(int datasize, const UncompressFlags& flags, int64* nwarn, 53 std::function<uint8*(int, int, int)> allocate_output) 54 : datasize_(datasize), 55 flags_(flags), 56 pnwarn_(nwarn), 57 allocate_output_(std::move(allocate_output)), 58 height_read_(0), 59 height_(0), 60 stride_(0) { 61 if (pnwarn_ != nullptr) *pnwarn_ = 0; 62 } 63 64 const int datasize_; 65 const UncompressFlags flags_; 66 int64* const pnwarn_; 67 std::function<uint8*(int, int, int)> allocate_output_; 68 int height_read_; // number of scanline lines successfully read 69 int height_; 70 int stride_; 71 }; 72 73 // Check whether the crop window is valid, assuming crop is true. 74 bool IsCropWindowValid(const UncompressFlags& flags, int input_image_width, 75 int input_image_height) { 76 // Crop window is valid only if it is non zero and all the window region is 77 // within the original image. 78 return flags.crop_width > 0 && flags.crop_height > 0 && flags.crop_x >= 0 && 79 flags.crop_y >= 0 && 80 flags.crop_y + flags.crop_height <= input_image_height && 81 flags.crop_x + flags.crop_width <= input_image_width; 82 } 83 84 uint8* UncompressLow(const void* srcdata, FewerArgsForCompiler* argball) { 85 // unpack the argball 86 const int datasize = argball->datasize_; 87 const auto& flags = argball->flags_; 88 const int ratio = flags.ratio; 89 int components = flags.components; 90 int stride = flags.stride; // may be 0 91 int64* const nwarn = argball->pnwarn_; // may be NULL 92 93 // Can't decode if the ratio is not recognized by libjpeg 94 if ((ratio != 1) && (ratio != 2) && (ratio != 4) && (ratio != 8)) { 95 return nullptr; 96 } 97 98 // Channels must be autodetect, grayscale, or rgb. 99 if (!(components == 0 || components == 1 || components == 3)) { 100 return nullptr; 101 } 102 103 // if empty image, return 104 if (datasize == 0 || srcdata == nullptr) return nullptr; 105 106 // Declare temporary buffer pointer here so that we can free on error paths 107 JSAMPLE* tempdata = nullptr; 108 109 // Initialize libjpeg structures to have a memory source 110 // Modify the usual jpeg error manager to catch fatal errors. 111 JPEGErrors error = JPEGERRORS_OK; 112 struct jpeg_decompress_struct cinfo; 113 struct jpeg_error_mgr jerr; 114 cinfo.err = jpeg_std_error(&jerr); 115 jmp_buf jpeg_jmpbuf; 116 cinfo.client_data = &jpeg_jmpbuf; 117 jerr.error_exit = CatchError; 118 if (setjmp(jpeg_jmpbuf)) { 119 delete[] tempdata; 120 return nullptr; 121 } 122 123 jpeg_create_decompress(&cinfo); 124 SetSrc(&cinfo, srcdata, datasize, flags.try_recover_truncated_jpeg); 125 jpeg_read_header(&cinfo, TRUE); 126 127 // Set components automatically if desired, autoconverting cmyk to rgb. 128 if (components == 0) components = std::min(cinfo.num_components, 3); 129 130 // set grayscale and ratio parameters 131 switch (components) { 132 case 1: 133 cinfo.out_color_space = JCS_GRAYSCALE; 134 break; 135 case 3: 136 if (cinfo.jpeg_color_space == JCS_CMYK || 137 cinfo.jpeg_color_space == JCS_YCCK) { 138 // Always use cmyk for output in a 4 channel jpeg. libjpeg has a builtin 139 // decoder. We will further convert to rgb below. 140 cinfo.out_color_space = JCS_CMYK; 141 } else { 142 cinfo.out_color_space = JCS_RGB; 143 } 144 break; 145 default: 146 LOG(ERROR) << " Invalid components value " << components << std::endl; 147 jpeg_destroy_decompress(&cinfo); 148 return nullptr; 149 } 150 cinfo.do_fancy_upsampling = boolean(flags.fancy_upscaling); 151 cinfo.scale_num = 1; 152 cinfo.scale_denom = ratio; 153 cinfo.dct_method = flags.dct_method; 154 155 jpeg_start_decompress(&cinfo); 156 157 int64 total_size = static_cast<int64>(cinfo.output_height) * 158 static_cast<int64>(cinfo.output_width); 159 // Some of the internal routines do not gracefully handle ridiculously 160 // large images, so fail fast. 161 if (cinfo.output_width <= 0 || cinfo.output_height <= 0) { 162 LOG(ERROR) << "Invalid image size: " << cinfo.output_width << " x " 163 << cinfo.output_height; 164 jpeg_destroy_decompress(&cinfo); 165 return nullptr; 166 } 167 if (total_size >= (1LL << 29)) { 168 LOG(ERROR) << "Image too large: " << total_size; 169 jpeg_destroy_decompress(&cinfo); 170 return nullptr; 171 } 172 173 JDIMENSION target_output_width = cinfo.output_width; 174 JDIMENSION target_output_height = cinfo.output_height; 175 JDIMENSION skipped_scanlines = 0; 176 #if defined(LIBJPEG_TURBO_VERSION) 177 if (flags.crop) { 178 // Update target output height and width based on crop window. 179 target_output_height = flags.crop_height; 180 target_output_width = flags.crop_width; 181 182 // So far, cinfo holds the original input image information. 183 if (!IsCropWindowValid(flags, cinfo.output_width, cinfo.output_height)) { 184 LOG(ERROR) << "Invalid crop window: x=" << flags.crop_x 185 << ", y=" << flags.crop_y << ", w=" << target_output_width 186 << ", h=" << target_output_height 187 << " for image_width: " << cinfo.output_width 188 << " and image_height: " << cinfo.output_height; 189 jpeg_destroy_decompress(&cinfo); 190 return nullptr; 191 } 192 193 // Update cinfo.output_width. It is tricky that cinfo.output_width must 194 // fall on an Minimum Coded Unit (MCU) boundary; if it doesn't, then it will 195 // be moved left to the nearest MCU boundary, and width will be increased 196 // accordingly. Therefore, the final cinfo.crop_width might differ from the 197 // given flags.crop_width. Please see libjpeg library for details. 198 JDIMENSION crop_width = flags.crop_width; 199 JDIMENSION crop_x = flags.crop_x; 200 jpeg_crop_scanline(&cinfo, &crop_x, &crop_width); 201 202 // Update cinfo.output_scanline. 203 skipped_scanlines = jpeg_skip_scanlines(&cinfo, flags.crop_y); 204 CHECK_EQ(skipped_scanlines, flags.crop_y); 205 } 206 #endif 207 208 // check for compatible stride 209 const int min_stride = target_output_width * components * sizeof(JSAMPLE); 210 if (stride == 0) { 211 stride = min_stride; 212 } else if (stride < min_stride) { 213 LOG(ERROR) << "Incompatible stride: " << stride << " < " << min_stride; 214 jpeg_destroy_decompress(&cinfo); 215 return nullptr; 216 } 217 218 // Remember stride and height for use in Uncompress 219 argball->height_ = target_output_height; 220 argball->stride_ = stride; 221 222 #if !defined(LIBJPEG_TURBO_VERSION) 223 uint8* dstdata = nullptr; 224 if (flags.crop) { 225 dstdata = new JSAMPLE[stride * target_output_height]; 226 } else { 227 dstdata = argball->allocate_output_(target_output_width, 228 target_output_height, components); 229 } 230 #else 231 uint8* dstdata = argball->allocate_output_(target_output_width, 232 target_output_height, components); 233 #endif 234 if (dstdata == nullptr) { 235 jpeg_destroy_decompress(&cinfo); 236 return nullptr; 237 } 238 JSAMPLE* output_line = static_cast<JSAMPLE*>(dstdata); 239 240 // jpeg_read_scanlines requires the buffers to be allocated based on 241 // cinfo.output_width, but the target image width might be different if crop 242 // is enabled and crop_width is not MCU aligned. In this case, we need to 243 // realign the scanline output to achieve the exact cropping. Notably, only 244 // cinfo.output_width needs to fall on MCU boundary, while cinfo.output_height 245 // has no such constraint. 246 const bool need_realign_cropped_scanline = 247 (target_output_width != cinfo.output_width); 248 const bool use_cmyk = (cinfo.out_color_space == JCS_CMYK); 249 250 if (use_cmyk) { 251 // Temporary buffer used for CMYK -> RGB conversion. 252 tempdata = new JSAMPLE[cinfo.output_width * 4]; 253 } else if (need_realign_cropped_scanline) { 254 // Temporary buffer used for MCU-aligned scanline data. 255 tempdata = new JSAMPLE[cinfo.output_width * components]; 256 } 257 258 // If there is an error reading a line, this aborts the reading. 259 // Save the fraction of the image that has been read. 260 argball->height_read_ = target_output_height; 261 262 // These variables are just to avoid repeated computation in the loop. 263 const int max_scanlines_to_read = skipped_scanlines + target_output_height; 264 const int mcu_align_offset = 265 (cinfo.output_width - target_output_width) * (use_cmyk ? 4 : components); 266 while (cinfo.output_scanline < max_scanlines_to_read) { 267 int num_lines_read = 0; 268 if (use_cmyk) { 269 num_lines_read = jpeg_read_scanlines(&cinfo, &tempdata, 1); 270 if (num_lines_read > 0) { 271 // Convert CMYK to RGB if scanline read succeeded. 272 for (size_t i = 0; i < target_output_width; ++i) { 273 int offset = 4 * i; 274 if (need_realign_cropped_scanline) { 275 // Align the offset for MCU boundary. 276 offset += mcu_align_offset; 277 } 278 const int c = tempdata[offset + 0]; 279 const int m = tempdata[offset + 1]; 280 const int y = tempdata[offset + 2]; 281 const int k = tempdata[offset + 3]; 282 int r, g, b; 283 if (cinfo.saw_Adobe_marker) { 284 r = (k * c) / 255; 285 g = (k * m) / 255; 286 b = (k * y) / 255; 287 } else { 288 r = (255 - k) * (255 - c) / 255; 289 g = (255 - k) * (255 - m) / 255; 290 b = (255 - k) * (255 - y) / 255; 291 } 292 output_line[3 * i + 0] = r; 293 output_line[3 * i + 1] = g; 294 output_line[3 * i + 2] = b; 295 } 296 } 297 } else if (need_realign_cropped_scanline) { 298 num_lines_read = jpeg_read_scanlines(&cinfo, &tempdata, 1); 299 if (num_lines_read > 0) { 300 memcpy(output_line, tempdata + mcu_align_offset, min_stride); 301 } 302 } else { 303 num_lines_read = jpeg_read_scanlines(&cinfo, &output_line, 1); 304 } 305 // Handle error cases 306 if (num_lines_read == 0) { 307 LOG(ERROR) << "Premature end of JPEG data. Stopped at line " 308 << cinfo.output_scanline - skipped_scanlines << "/" 309 << target_output_height; 310 if (!flags.try_recover_truncated_jpeg) { 311 argball->height_read_ = cinfo.output_scanline - skipped_scanlines; 312 error = JPEGERRORS_UNEXPECTED_END_OF_DATA; 313 } else { 314 for (size_t line = cinfo.output_scanline; line < max_scanlines_to_read; 315 ++line) { 316 if (line == 0) { 317 // If even the first line is missing, fill with black color 318 memset(output_line, 0, min_stride); 319 } else { 320 // else, just replicate the line above. 321 memcpy(output_line, output_line - stride, min_stride); 322 } 323 output_line += stride; 324 } 325 argball->height_read_ = 326 target_output_height; // consider all lines as read 327 // prevent error-on-exit in libjpeg: 328 cinfo.output_scanline = max_scanlines_to_read; 329 } 330 break; 331 } 332 DCHECK_EQ(num_lines_read, 1); 333 TF_ANNOTATE_MEMORY_IS_INITIALIZED(output_line, min_stride); 334 output_line += stride; 335 } 336 delete[] tempdata; 337 tempdata = nullptr; 338 339 #if defined(LIBJPEG_TURBO_VERSION) 340 if (flags.crop && cinfo.output_scanline < cinfo.output_height) { 341 // Skip the rest of scanlines, required by jpeg_destroy_decompress. 342 jpeg_skip_scanlines(&cinfo, 343 cinfo.output_height - flags.crop_y - flags.crop_height); 344 // After this, cinfo.output_height must be equal to cinfo.output_height; 345 // otherwise, jpeg_destroy_decompress would fail. 346 } 347 #endif 348 349 // Convert the RGB data to RGBA, with alpha set to 0xFF to indicate 350 // opacity. 351 // RGBRGBRGB... --> RGBARGBARGBA... 352 if (components == 4) { 353 // Start on the last line. 354 JSAMPLE* scanlineptr = static_cast<JSAMPLE*>( 355 dstdata + static_cast<int64>(target_output_height - 1) * stride); 356 const JSAMPLE kOpaque = -1; // All ones appropriate for JSAMPLE. 357 const int right_rgb = (target_output_width - 1) * 3; 358 const int right_rgba = (target_output_width - 1) * 4; 359 360 for (int y = target_output_height; y-- > 0;) { 361 // We do all the transformations in place, going backwards for each row. 362 const JSAMPLE* rgb_pixel = scanlineptr + right_rgb; 363 JSAMPLE* rgba_pixel = scanlineptr + right_rgba; 364 scanlineptr -= stride; 365 for (int x = target_output_width; x-- > 0; 366 rgba_pixel -= 4, rgb_pixel -= 3) { 367 // We copy the 3 bytes at rgb_pixel into the 4 bytes at rgba_pixel 368 // The "a" channel is set to be opaque. 369 rgba_pixel[3] = kOpaque; 370 rgba_pixel[2] = rgb_pixel[2]; 371 rgba_pixel[1] = rgb_pixel[1]; 372 rgba_pixel[0] = rgb_pixel[0]; 373 } 374 } 375 } 376 377 switch (components) { 378 case 1: 379 if (cinfo.output_components != 1) { 380 error = JPEGERRORS_BAD_PARAM; 381 } 382 break; 383 case 3: 384 case 4: 385 if (cinfo.out_color_space == JCS_CMYK) { 386 if (cinfo.output_components != 4) { 387 error = JPEGERRORS_BAD_PARAM; 388 } 389 } else { 390 if (cinfo.output_components != 3) { 391 error = JPEGERRORS_BAD_PARAM; 392 } 393 } 394 break; 395 default: 396 // will never happen, should be catched by the previous switch 397 LOG(ERROR) << "Invalid components value " << components << std::endl; 398 jpeg_destroy_decompress(&cinfo); 399 return nullptr; 400 } 401 402 // save number of warnings if requested 403 if (nwarn != nullptr) { 404 *nwarn = cinfo.err->num_warnings; 405 } 406 407 // Handle errors in JPEG 408 switch (error) { 409 case JPEGERRORS_OK: 410 jpeg_finish_decompress(&cinfo); 411 break; 412 case JPEGERRORS_UNEXPECTED_END_OF_DATA: 413 case JPEGERRORS_BAD_PARAM: 414 jpeg_abort(reinterpret_cast<j_common_ptr>(&cinfo)); 415 break; 416 default: 417 LOG(ERROR) << "Unhandled case " << error; 418 break; 419 } 420 421 #if !defined(LIBJPEG_TURBO_VERSION) 422 // TODO(tanmingxing): delete all these code after migrating to libjpeg_turbo 423 // for Windows. 424 if (flags.crop) { 425 // Update target output height and width based on crop window. 426 target_output_height = flags.crop_height; 427 target_output_width = flags.crop_width; 428 429 // cinfo holds the original input image information. 430 if (!IsCropWindowValid(flags, cinfo.output_width, cinfo.output_height)) { 431 LOG(ERROR) << "Invalid crop window: x=" << flags.crop_x 432 << ", y=" << flags.crop_y << ", w=" << target_output_width 433 << ", h=" << target_output_height 434 << " for image_width: " << cinfo.output_width 435 << " and image_height: " << cinfo.output_height; 436 delete[] dstdata; 437 jpeg_destroy_decompress(&cinfo); 438 return nullptr; 439 } 440 441 const uint8* full_image = dstdata; 442 dstdata = argball->allocate_output_(target_output_width, 443 target_output_height, components); 444 if (dstdata == nullptr) { 445 delete[] full_image; 446 jpeg_destroy_decompress(&cinfo); 447 return nullptr; 448 } 449 450 const int full_image_stride = stride; 451 // Update stride and hight for crop window. 452 const int min_stride = target_output_width * components * sizeof(JSAMPLE); 453 if (flags.stride == 0) { 454 stride = min_stride; 455 } 456 argball->height_ = target_output_height; 457 argball->stride_ = stride; 458 459 if (argball->height_read_ > target_output_height) { 460 argball->height_read_ = target_output_height; 461 } 462 const int crop_offset = flags.crop_x * components * sizeof(JSAMPLE); 463 const uint8* full_image_ptr = full_image + flags.crop_y * full_image_stride; 464 uint8* crop_image_ptr = dstdata; 465 for (int i = 0; i < argball->height_read_; i++) { 466 memcpy(crop_image_ptr, full_image_ptr + crop_offset, min_stride); 467 crop_image_ptr += stride; 468 full_image_ptr += full_image_stride; 469 } 470 delete[] full_image; 471 } 472 #endif 473 474 jpeg_destroy_decompress(&cinfo); 475 return dstdata; 476 } 477 478 } // anonymous namespace 479 480 // ----------------------------------------------------------------------------- 481 // We do the apparently silly thing of packing 5 of the arguments 482 // into a structure that is then passed to another routine 483 // that does all the work. The reason is that we want to catch 484 // fatal JPEG library errors with setjmp/longjmp, and g++ and 485 // associated libraries aren't good enough to guarantee that 7 486 // parameters won't get clobbered by the longjmp. So we help 487 // it out a little. 488 uint8* Uncompress(const void* srcdata, int datasize, 489 const UncompressFlags& flags, int64* nwarn, 490 std::function<uint8*(int, int, int)> allocate_output) { 491 FewerArgsForCompiler argball(datasize, flags, nwarn, 492 std::move(allocate_output)); 493 uint8* const dstdata = UncompressLow(srcdata, &argball); 494 495 const float fraction_read = 496 argball.height_ == 0 497 ? 1.0 498 : (static_cast<float>(argball.height_read_) / argball.height_); 499 if (dstdata == nullptr || 500 fraction_read < std::min(1.0f, flags.min_acceptable_fraction)) { 501 // Major failure, none or too-partial read returned; get out 502 return nullptr; 503 } 504 505 // If there was an error in reading the jpeg data, 506 // set the unread pixels to black 507 if (argball.height_read_ != argball.height_) { 508 const int first_bad_line = argball.height_read_; 509 uint8* start = dstdata + first_bad_line * argball.stride_; 510 const int nbytes = (argball.height_ - first_bad_line) * argball.stride_; 511 memset(static_cast<void*>(start), 0, nbytes); 512 } 513 514 return dstdata; 515 } 516 517 uint8* Uncompress(const void* srcdata, int datasize, 518 const UncompressFlags& flags, int* pwidth, int* pheight, 519 int* pcomponents, int64* nwarn) { 520 uint8* buffer = nullptr; 521 uint8* result = 522 Uncompress(srcdata, datasize, flags, nwarn, 523 [=, &buffer](int width, int height, int components) { 524 if (pwidth != nullptr) *pwidth = width; 525 if (pheight != nullptr) *pheight = height; 526 if (pcomponents != nullptr) *pcomponents = components; 527 buffer = new uint8[height * width * components]; 528 return buffer; 529 }); 530 if (!result) delete[] buffer; 531 return result; 532 } 533 534 // ---------------------------------------------------------------------------- 535 // Computes image information from jpeg header. 536 // Returns true on success; false on failure. 537 bool GetImageInfo(const void* srcdata, int datasize, int* width, int* height, 538 int* components) { 539 // Init in case of failure 540 if (width) *width = 0; 541 if (height) *height = 0; 542 if (components) *components = 0; 543 544 // If empty image, return 545 if (datasize == 0 || srcdata == nullptr) return false; 546 547 // Initialize libjpeg structures to have a memory source 548 // Modify the usual jpeg error manager to catch fatal errors. 549 struct jpeg_decompress_struct cinfo; 550 struct jpeg_error_mgr jerr; 551 jmp_buf jpeg_jmpbuf; 552 cinfo.err = jpeg_std_error(&jerr); 553 cinfo.client_data = &jpeg_jmpbuf; 554 jerr.error_exit = CatchError; 555 if (setjmp(jpeg_jmpbuf)) { 556 return false; 557 } 558 559 // set up, read header, set image parameters, save size 560 jpeg_create_decompress(&cinfo); 561 SetSrc(&cinfo, srcdata, datasize, false); 562 563 jpeg_read_header(&cinfo, TRUE); 564 jpeg_start_decompress(&cinfo); // required to transfer image size to cinfo 565 if (width) *width = cinfo.output_width; 566 if (height) *height = cinfo.output_height; 567 if (components) *components = cinfo.output_components; 568 569 jpeg_destroy_decompress(&cinfo); 570 571 return true; 572 } 573 574 // ----------------------------------------------------------------------------- 575 // Compression 576 577 namespace { 578 bool CompressInternal(const uint8* srcdata, int width, int height, 579 const CompressFlags& flags, string* output) { 580 output->clear(); 581 const int components = (static_cast<int>(flags.format) & 0xff); 582 583 int64 total_size = static_cast<int64>(width) * static_cast<int64>(height); 584 // Some of the internal routines do not gracefully handle ridiculously 585 // large images, so fail fast. 586 if (width <= 0 || height <= 0) { 587 LOG(ERROR) << "Invalid image size: " << width << " x " << height; 588 return false; 589 } 590 if (total_size >= (1LL << 29)) { 591 LOG(ERROR) << "Image too large: " << total_size; 592 return false; 593 } 594 595 int in_stride = flags.stride; 596 if (in_stride == 0) { 597 in_stride = width * (static_cast<int>(flags.format) & 0xff); 598 } else if (in_stride < width * components) { 599 LOG(ERROR) << "Incompatible input stride"; 600 return false; 601 } 602 603 JOCTET* buffer = nullptr; 604 605 // NOTE: for broader use xmp_metadata should be made a unicode string 606 CHECK(srcdata != nullptr); 607 CHECK(output != nullptr); 608 // This struct contains the JPEG compression parameters and pointers to 609 // working space 610 struct jpeg_compress_struct cinfo; 611 // This struct represents a JPEG error handler. 612 struct jpeg_error_mgr jerr; 613 jmp_buf jpeg_jmpbuf; // recovery point in case of error 614 615 // Step 1: allocate and initialize JPEG compression object 616 // Use the usual jpeg error manager. 617 cinfo.err = jpeg_std_error(&jerr); 618 cinfo.client_data = &jpeg_jmpbuf; 619 jerr.error_exit = CatchError; 620 if (setjmp(jpeg_jmpbuf)) { 621 output->clear(); 622 delete[] buffer; 623 return false; 624 } 625 626 jpeg_create_compress(&cinfo); 627 628 // Step 2: specify data destination 629 // We allocate a buffer of reasonable size. If we have a small image, just 630 // estimate the size of the output using the number of bytes of the input. 631 // If this is getting too big, we will append to the string by chunks of 1MB. 632 // This seems like a reasonable compromise between performance and memory. 633 int bufsize = std::min(width * height * components, 1 << 20); 634 buffer = new JOCTET[bufsize]; 635 SetDest(&cinfo, buffer, bufsize, output); 636 637 // Step 3: set parameters for compression 638 cinfo.image_width = width; 639 cinfo.image_height = height; 640 switch (components) { 641 case 1: 642 cinfo.input_components = 1; 643 cinfo.in_color_space = JCS_GRAYSCALE; 644 break; 645 case 3: 646 case 4: 647 cinfo.input_components = 3; 648 cinfo.in_color_space = JCS_RGB; 649 break; 650 default: 651 LOG(ERROR) << " Invalid components value " << components << std::endl; 652 output->clear(); 653 delete[] buffer; 654 return false; 655 } 656 jpeg_set_defaults(&cinfo); 657 if (flags.optimize_jpeg_size) cinfo.optimize_coding = TRUE; 658 659 cinfo.density_unit = flags.density_unit; // JFIF code for pixel size units: 660 // 1 = in, 2 = cm 661 cinfo.X_density = flags.x_density; // Horizontal pixel density 662 cinfo.Y_density = flags.y_density; // Vertical pixel density 663 jpeg_set_quality(&cinfo, flags.quality, TRUE); 664 665 if (flags.progressive) { 666 jpeg_simple_progression(&cinfo); 667 } 668 669 if (!flags.chroma_downsampling) { 670 // Turn off chroma subsampling (it is on by default). For more details on 671 // chroma subsampling, see http://en.wikipedia.org/wiki/Chroma_subsampling. 672 for (int i = 0; i < cinfo.num_components; ++i) { 673 cinfo.comp_info[i].h_samp_factor = 1; 674 cinfo.comp_info[i].v_samp_factor = 1; 675 } 676 } 677 678 jpeg_start_compress(&cinfo, TRUE); 679 680 // Embed XMP metadata if any 681 if (!flags.xmp_metadata.empty()) { 682 // XMP metadata is embedded in the APP1 tag of JPEG and requires this 683 // namespace header string (null-terminated) 684 const string name_space = "http://ns.adobe.com/xap/1.0/"; 685 const int name_space_length = name_space.size(); 686 const int metadata_length = flags.xmp_metadata.size(); 687 const int packet_length = metadata_length + name_space_length + 1; 688 std::unique_ptr<JOCTET[]> joctet_packet(new JOCTET[packet_length]); 689 690 for (int i = 0; i < name_space_length; i++) { 691 // Conversion char --> JOCTET 692 joctet_packet[i] = name_space[i]; 693 } 694 joctet_packet[name_space_length] = 0; // null-terminate namespace string 695 696 for (int i = 0; i < metadata_length; i++) { 697 // Conversion char --> JOCTET 698 joctet_packet[i + name_space_length + 1] = flags.xmp_metadata[i]; 699 } 700 jpeg_write_marker(&cinfo, JPEG_APP0 + 1, joctet_packet.get(), 701 packet_length); 702 } 703 704 // JSAMPLEs per row in image_buffer 705 std::unique_ptr<JSAMPLE[]> row_temp( 706 new JSAMPLE[width * cinfo.input_components]); 707 while (cinfo.next_scanline < cinfo.image_height) { 708 JSAMPROW row_pointer[1]; // pointer to JSAMPLE row[s] 709 const uint8* r = &srcdata[cinfo.next_scanline * in_stride]; 710 uint8* p = static_cast<uint8*>(row_temp.get()); 711 switch (flags.format) { 712 case FORMAT_RGBA: { 713 for (int i = 0; i < width; ++i, p += 3, r += 4) { 714 p[0] = r[0]; 715 p[1] = r[1]; 716 p[2] = r[2]; 717 } 718 row_pointer[0] = row_temp.get(); 719 break; 720 } 721 case FORMAT_ABGR: { 722 for (int i = 0; i < width; ++i, p += 3, r += 4) { 723 p[0] = r[3]; 724 p[1] = r[2]; 725 p[2] = r[1]; 726 } 727 row_pointer[0] = row_temp.get(); 728 break; 729 } 730 default: { 731 row_pointer[0] = reinterpret_cast<JSAMPLE*>(const_cast<JSAMPLE*>(r)); 732 } 733 } 734 CHECK_EQ(jpeg_write_scanlines(&cinfo, row_pointer, 1), 1u); 735 } 736 jpeg_finish_compress(&cinfo); 737 738 // release JPEG compression object 739 jpeg_destroy_compress(&cinfo); 740 delete[] buffer; 741 return true; 742 } 743 744 } // anonymous namespace 745 746 // ----------------------------------------------------------------------------- 747 748 bool Compress(const void* srcdata, int width, int height, 749 const CompressFlags& flags, string* output) { 750 return CompressInternal(static_cast<const uint8*>(srcdata), width, height, 751 flags, output); 752 } 753 754 string Compress(const void* srcdata, int width, int height, 755 const CompressFlags& flags) { 756 string temp; 757 CompressInternal(static_cast<const uint8*>(srcdata), width, height, flags, 758 &temp); 759 // If CompressInternal fails, temp will be empty. 760 return temp; 761 } 762 763 } // namespace jpeg 764 } // namespace tensorflow 765