xref: /aosp_15_r20/external/tensorflow/tensorflow/core/lib/jpeg/jpeg_mem.cc (revision b6fb3261f9314811a0f4371741dbb8839866f948)
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