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