aboutsummaryrefslogtreecommitdiffhomepage
path: root/tensorflow/core/lib/jpeg
diff options
context:
space:
mode:
Diffstat (limited to 'tensorflow/core/lib/jpeg')
-rw-r--r--tensorflow/core/lib/jpeg/jpeg_handle.cc162
-rw-r--r--tensorflow/core/lib/jpeg/jpeg_handle.h51
-rw-r--r--tensorflow/core/lib/jpeg/jpeg_mem.cc557
-rw-r--r--tensorflow/core/lib/jpeg/jpeg_mem.h130
-rw-r--r--tensorflow/core/lib/jpeg/jpeg_mem_unittest.cc304
-rw-r--r--tensorflow/core/lib/jpeg/testdata/bad_huffman.jpgbin0 -> 15416 bytes
-rw-r--r--tensorflow/core/lib/jpeg/testdata/corrupt.jpgbin0 -> 1552 bytes
-rw-r--r--tensorflow/core/lib/jpeg/testdata/corrupt34_2.jpgbin0 -> 755 bytes
-rw-r--r--tensorflow/core/lib/jpeg/testdata/corrupt34_3.jpgbin0 -> 5505 bytes
-rw-r--r--tensorflow/core/lib/jpeg/testdata/corrupt34_4.jpgbin0 -> 5092 bytes
-rw-r--r--tensorflow/core/lib/jpeg/testdata/jpeg_merge_test1.jpgbin0 -> 3771 bytes
-rw-r--r--tensorflow/core/lib/jpeg/testdata/jpeg_merge_test1_cmyk.jpgbin0 -> 5324 bytes
12 files changed, 1204 insertions, 0 deletions
diff --git a/tensorflow/core/lib/jpeg/jpeg_handle.cc b/tensorflow/core/lib/jpeg/jpeg_handle.cc
new file mode 100644
index 0000000000..4521be0afb
--- /dev/null
+++ b/tensorflow/core/lib/jpeg/jpeg_handle.cc
@@ -0,0 +1,162 @@
+// This file implements a memory destination for libjpeg
+// The design is very similar to jdatadst.c in libjpeg
+// These functions are not meant to be used directly, see jpeg_mem.h instead.
+// We are filling out stubs required by jpeglib, those stubs are private to
+// the implementation, we are just making available JPGMemSrc, JPGMemDest
+
+#include "tensorflow/core/lib/jpeg/jpeg_handle.h"
+
+#include <setjmp.h>
+#include <stddef.h>
+
+#include "tensorflow/core/platform/logging.h"
+
+namespace tensorflow {
+namespace jpeg {
+
+void CatchError(j_common_ptr cinfo) {
+ (*cinfo->err->output_message)(cinfo);
+ jmp_buf *jpeg_jmpbuf = reinterpret_cast<jmp_buf *>(cinfo->client_data);
+ jpeg_destroy(cinfo);
+ longjmp(*jpeg_jmpbuf, 1);
+}
+
+// *****************************************************************************
+// *****************************************************************************
+// *****************************************************************************
+// Destination functions
+
+// -----------------------------------------------------------------------------
+void MemInitDestination(j_compress_ptr cinfo) {
+ MemDestMgr *dest = reinterpret_cast<MemDestMgr *>(cinfo->dest);
+ VLOG(1) << "Initializing buffer=" << dest->bufsize << " bytes";
+ dest->pub.next_output_byte = dest->buffer;
+ dest->pub.free_in_buffer = dest->bufsize;
+ dest->datacount = 0;
+ if (dest->dest) {
+ dest->dest->clear();
+ }
+}
+
+// -----------------------------------------------------------------------------
+boolean MemEmptyOutputBuffer(j_compress_ptr cinfo) {
+ MemDestMgr *dest = reinterpret_cast<MemDestMgr *>(cinfo->dest);
+ VLOG(1) << "Writing " << dest->bufsize << " bytes";
+ if (dest->dest) {
+ dest->dest->append(reinterpret_cast<char *>(dest->buffer), dest->bufsize);
+ }
+ dest->pub.next_output_byte = dest->buffer;
+ dest->pub.free_in_buffer = dest->bufsize;
+ return TRUE;
+}
+
+// -----------------------------------------------------------------------------
+void MemTermDestination(j_compress_ptr cinfo) {
+ MemDestMgr *dest = reinterpret_cast<MemDestMgr *>(cinfo->dest);
+ VLOG(1) << "Writing " << dest->bufsize - dest->pub.free_in_buffer << " bytes";
+ if (dest->dest) {
+ dest->dest->append(reinterpret_cast<char *>(dest->buffer),
+ dest->bufsize - dest->pub.free_in_buffer);
+ VLOG(1) << "Total size= " << dest->dest->size();
+ }
+ dest->datacount = dest->bufsize - dest->pub.free_in_buffer;
+}
+
+// -----------------------------------------------------------------------------
+void SetDest(j_compress_ptr cinfo, void *buffer, int bufsize) {
+ SetDest(cinfo, buffer, bufsize, NULL);
+}
+
+// -----------------------------------------------------------------------------
+void SetDest(j_compress_ptr cinfo, void *buffer, int bufsize,
+ string *destination) {
+ MemDestMgr *dest;
+ if (cinfo->dest == NULL) {
+ cinfo->dest = reinterpret_cast<struct jpeg_destination_mgr *>(
+ (*cinfo->mem->alloc_small)(reinterpret_cast<j_common_ptr>(cinfo),
+ JPOOL_PERMANENT, sizeof(MemDestMgr)));
+ }
+
+ dest = reinterpret_cast<MemDestMgr *>(cinfo->dest);
+ dest->bufsize = bufsize;
+ dest->buffer = static_cast<JOCTET *>(buffer);
+ dest->dest = destination;
+ dest->pub.init_destination = MemInitDestination;
+ dest->pub.empty_output_buffer = MemEmptyOutputBuffer;
+ dest->pub.term_destination = MemTermDestination;
+}
+
+// *****************************************************************************
+// *****************************************************************************
+// *****************************************************************************
+// Source functions
+
+// -----------------------------------------------------------------------------
+void MemInitSource(j_decompress_ptr cinfo) {
+ MemSourceMgr *src = reinterpret_cast<MemSourceMgr *>(cinfo->src);
+ src->pub.next_input_byte = src->data;
+ src->pub.bytes_in_buffer = src->datasize;
+}
+
+// -----------------------------------------------------------------------------
+// We emulate the same error-handling as fill_input_buffer() from jdatasrc.c,
+// for coherency's sake.
+boolean MemFillInputBuffer(j_decompress_ptr cinfo) {
+ static const JOCTET kEOIBuffer[2] = {0xff, JPEG_EOI};
+ MemSourceMgr *src = reinterpret_cast<MemSourceMgr *>(cinfo->src);
+ if (src->pub.bytes_in_buffer == 0 && src->pub.next_input_byte == src->data) {
+ // empty file -> treated as an error.
+ ERREXIT(cinfo, JERR_INPUT_EMPTY);
+ return FALSE;
+ } else if (src->pub.bytes_in_buffer) {
+ // if there's still some data left, it's probably corrupted
+ return src->try_recover_truncated_jpeg ? TRUE : FALSE;
+ } else if (src->pub.next_input_byte != kEOIBuffer &&
+ src->try_recover_truncated_jpeg) {
+ // In an attempt to recover truncated files, we insert a fake EOI
+ WARNMS(cinfo, JWRN_JPEG_EOF);
+ src->pub.next_input_byte = kEOIBuffer;
+ src->pub.bytes_in_buffer = 2;
+ return TRUE;
+ } else {
+ // We already inserted a fake EOI and it wasn't enough, so this time
+ // it's really an error.
+ ERREXIT(cinfo, JERR_FILE_READ);
+ return FALSE;
+ }
+}
+
+// -----------------------------------------------------------------------------
+void MemTermSource(j_decompress_ptr cinfo) {}
+
+// -----------------------------------------------------------------------------
+void MemSkipInputData(j_decompress_ptr cinfo, long jump) {
+ MemSourceMgr *src = reinterpret_cast<MemSourceMgr *>(cinfo->src);
+ src->pub.bytes_in_buffer -= jump;
+ src->pub.next_input_byte += jump;
+}
+
+// -----------------------------------------------------------------------------
+void SetSrc(j_decompress_ptr cinfo, const void *data,
+ unsigned long int datasize, bool try_recover_truncated_jpeg) {
+ MemSourceMgr *src;
+
+ cinfo->src = reinterpret_cast<struct jpeg_source_mgr *>(
+ (*cinfo->mem->alloc_small)(reinterpret_cast<j_common_ptr>(cinfo),
+ JPOOL_PERMANENT, sizeof(MemSourceMgr)));
+
+ src = reinterpret_cast<MemSourceMgr *>(cinfo->src);
+ src->pub.init_source = MemInitSource;
+ src->pub.fill_input_buffer = MemFillInputBuffer;
+ src->pub.skip_input_data = MemSkipInputData;
+ src->pub.resync_to_restart = jpeg_resync_to_restart;
+ src->pub.term_source = MemTermSource;
+ src->data = reinterpret_cast<const unsigned char *>(data);
+ src->datasize = datasize;
+ src->pub.bytes_in_buffer = 0;
+ src->pub.next_input_byte = NULL;
+ src->try_recover_truncated_jpeg = try_recover_truncated_jpeg;
+}
+
+} // namespace jpeg
+} // namespace tensorflow
diff --git a/tensorflow/core/lib/jpeg/jpeg_handle.h b/tensorflow/core/lib/jpeg/jpeg_handle.h
new file mode 100644
index 0000000000..58f7f6f666
--- /dev/null
+++ b/tensorflow/core/lib/jpeg/jpeg_handle.h
@@ -0,0 +1,51 @@
+// This file declares the functions and structures for memory I/O with libjpeg
+// These functions are not meant to be used directly, see jpeg_mem.h isntead.
+
+#ifndef TENSORFLOW_LIB_JPEG_JPEG_HANDLE_H_
+#define TENSORFLOW_LIB_JPEG_JPEG_HANDLE_H_
+
+extern "C" {
+#include "external/jpeg_archive/jpeg-9a/jinclude.h"
+#include "external/jpeg_archive/jpeg-9a/jpeglib.h"
+#include "external/jpeg_archive/jpeg-9a/jerror.h"
+#include "external/jpeg_archive/jpeg-9a/transupp.h" // for rotations
+}
+
+#include "tensorflow/core/platform/port.h"
+
+namespace tensorflow {
+namespace jpeg {
+
+// Handler for fatal JPEG library errors: clean up & return
+void CatchError(j_common_ptr cinfo);
+
+typedef struct {
+ struct jpeg_destination_mgr pub;
+ JOCTET *buffer;
+ int bufsize;
+ int datacount;
+ string *dest;
+} MemDestMgr;
+
+typedef struct {
+ struct jpeg_source_mgr pub;
+ const unsigned char *data;
+ unsigned long int datasize;
+ bool try_recover_truncated_jpeg;
+} MemSourceMgr;
+
+void SetSrc(j_decompress_ptr cinfo, const void *data,
+ unsigned long int datasize, bool try_recover_truncated_jpeg);
+
+// JPEG destination: we will store all the data in a buffer "buffer" of total
+// size "bufsize", if the buffer overflows, we will be in trouble.
+void SetDest(j_compress_ptr cinfo, void *buffer, int bufsize);
+// Same as above, except that buffer is only used as a temporary structure and
+// is emptied into "destination" as soon as it fills up.
+void SetDest(j_compress_ptr cinfo, void *buffer, int bufsize,
+ string *destination);
+
+} // namespace jpeg
+} // namespace tensorflow
+
+#endif // TENSORFLOW_LIB_JPEG_JPEG_HANDLE_H_
diff --git a/tensorflow/core/lib/jpeg/jpeg_mem.cc b/tensorflow/core/lib/jpeg/jpeg_mem.cc
new file mode 100644
index 0000000000..556f13e388
--- /dev/null
+++ b/tensorflow/core/lib/jpeg/jpeg_mem.cc
@@ -0,0 +1,557 @@
+// This file defines functions to compress and uncompress JPEG data
+// to and from memory, as well as some direct manipulations of JPEG string
+
+#include "tensorflow/core/lib/jpeg/jpeg_mem.h"
+
+#include <setjmp.h>
+#include <string.h>
+#include <algorithm>
+#include <memory>
+#include <string>
+
+#include "tensorflow/core/lib/jpeg/jpeg_handle.h"
+#include "tensorflow/core/platform/logging.h"
+#include "tensorflow/core/platform/port.h"
+
+namespace tensorflow {
+namespace jpeg {
+
+// -----------------------------------------------------------------------------
+// Decompression
+
+namespace {
+
+enum JPEGErrors {
+ JPEGERRORS_OK,
+ JPEGERRORS_UNEXPECTED_END_OF_DATA,
+ JPEGERRORS_BAD_PARAM
+};
+
+// Prevent bad compiler behaviour in ASAN mode by wrapping most of the
+// arguments in a struct struct.
+class FewerArgsForCompiler {
+ public:
+ FewerArgsForCompiler(int datasize, const UncompressFlags& flags, int* nwarn,
+ std::function<uint8*(int, int, int)> allocate_output)
+ : datasize_(datasize),
+ flags_(flags),
+ pnwarn_(nwarn),
+ allocate_output_(allocate_output),
+ fraction_read_(0.),
+ height_(0),
+ stride_(0) {
+ if (pnwarn_ != nullptr) *pnwarn_ = 0;
+ }
+
+ const int datasize_;
+ const UncompressFlags flags_;
+ int* const pnwarn_;
+ std::function<uint8*(int, int, int)> allocate_output_;
+ float fraction_read_; // fraction of scanline lines successfully read
+ int height_;
+ int stride_;
+};
+
+uint8* UncompressLow(const void* srcdata, FewerArgsForCompiler* argball) {
+ // unpack the argball
+ const int datasize = argball->datasize_;
+ const auto& flags = argball->flags_;
+ const int ratio = flags.ratio;
+ int components = flags.components;
+ int stride = flags.stride; // may be 0
+ int* const nwarn = argball->pnwarn_; // may be NULL
+
+ // can't decode if the ratio is not recognized by libjpeg
+ if ((ratio != 1) && (ratio != 2) && (ratio != 4) && (ratio != 8)) {
+ return nullptr;
+ }
+
+ // if empty image, return
+ if (datasize == 0 || srcdata == NULL) return nullptr;
+
+ // Declare temporary buffer pointer here so that we can free on error paths
+ JSAMPLE* tempdata = nullptr;
+
+ // Initialize libjpeg structures to have a memory source
+ // Modify the usual jpeg error manager to catch fatal errors.
+ JPEGErrors error = JPEGERRORS_OK;
+ struct jpeg_decompress_struct cinfo;
+ struct jpeg_error_mgr jerr;
+ cinfo.err = jpeg_std_error(&jerr);
+ jmp_buf jpeg_jmpbuf;
+ cinfo.client_data = &jpeg_jmpbuf;
+ jerr.error_exit = CatchError;
+ if (setjmp(jpeg_jmpbuf)) {
+ return nullptr;
+ }
+
+ jpeg_create_decompress(&cinfo);
+ SetSrc(&cinfo, srcdata, datasize, flags.try_recover_truncated_jpeg);
+ jpeg_read_header(&cinfo, TRUE);
+
+ // Set components automatically if desired
+ if (components == 0) components = cinfo.num_components;
+
+ // set grayscale and ratio parameters
+ switch (components) {
+ case 1:
+ cinfo.out_color_space = JCS_GRAYSCALE;
+ break;
+ case 3:
+ case 4:
+ if (cinfo.jpeg_color_space == JCS_CMYK ||
+ cinfo.jpeg_color_space == JCS_YCCK) {
+ // always use cmyk for output in a 4 channel jpeg. libjpeg has a builtin
+ // decoder.
+ cinfo.out_color_space = JCS_CMYK;
+ } else {
+ cinfo.out_color_space = JCS_RGB;
+ }
+ break;
+ default:
+ LOG(ERROR) << " Invalid components value " << components << std::endl;
+ jpeg_destroy_decompress(&cinfo);
+ return nullptr;
+ }
+ cinfo.do_fancy_upsampling = boolean(flags.fancy_upscaling);
+ cinfo.scale_num = 1;
+ cinfo.scale_denom = ratio;
+ // Activating this has a quality/speed trade-off implication:
+ // cinfo.dct_method = JDCT_IFAST;
+
+ jpeg_start_decompress(&cinfo);
+
+ // check for compatible stride
+ const int min_stride = cinfo.output_width * components * sizeof(JSAMPLE);
+ if (stride == 0) {
+ stride = min_stride;
+ } else if (stride < min_stride) {
+ LOG(ERROR) << "Incompatible stride: " << stride << " < " << min_stride;
+ jpeg_destroy_decompress(&cinfo);
+ return nullptr;
+ }
+
+ // Remember stride and height for use in Uncompress
+ argball->height_ = cinfo.output_height;
+ argball->stride_ = stride;
+
+ uint8* const dstdata = argball->allocate_output_(
+ cinfo.output_width, cinfo.output_height, components);
+ if (dstdata == nullptr) {
+ jpeg_destroy_decompress(&cinfo);
+ return nullptr;
+ }
+ JSAMPLE* output_line = static_cast<JSAMPLE*>(dstdata);
+
+ // Temporary buffer used for CMYK -> RGB conversion.
+ const bool use_cmyk = (cinfo.out_color_space == JCS_CMYK);
+ tempdata = use_cmyk ? new JSAMPLE[cinfo.output_width * 4] : NULL;
+
+ // If there is an error reading a line, this aborts the reading.
+ // Save the fraction of the image that has been read.
+ argball->fraction_read_ = 1.0;
+ while (cinfo.output_scanline < cinfo.output_height) {
+ int num_lines_read = 0;
+ if (cinfo.out_color_space == JCS_CMYK) {
+ num_lines_read = jpeg_read_scanlines(&cinfo, &tempdata, 1);
+ // Convert CMYK to RGB
+ for (size_t i = 0; i < cinfo.output_width; ++i) {
+ int c = tempdata[4 * i + 0];
+ int m = tempdata[4 * i + 1];
+ int y = tempdata[4 * i + 2];
+ int k = tempdata[4 * i + 3];
+ int r, g, b;
+ if (cinfo.saw_Adobe_marker) {
+ r = (k * c) / 255;
+ g = (k * m) / 255;
+ b = (k * y) / 255;
+ } else {
+ r = (255 - k) * (255 - c) / 255;
+ g = (255 - k) * (255 - m) / 255;
+ b = (255 - k) * (255 - y) / 255;
+ }
+ output_line[3 * i + 0] = r;
+ output_line[3 * i + 1] = g;
+ output_line[3 * i + 2] = b;
+ }
+ } else {
+ num_lines_read = jpeg_read_scanlines(&cinfo, &output_line, 1);
+ }
+ // Handle error cases
+ if (num_lines_read == 0) {
+ LOG(ERROR) << "Premature end of JPEG data. Stopped at line "
+ << cinfo.output_scanline << "/" << cinfo.output_height;
+ if (!flags.try_recover_truncated_jpeg) {
+ argball->fraction_read_ =
+ static_cast<float>(cinfo.output_scanline) / cinfo.output_height;
+ error = JPEGERRORS_UNEXPECTED_END_OF_DATA;
+ } else {
+ for (size_t line = cinfo.output_scanline; line < cinfo.output_height;
+ ++line) {
+ if (line == 0) {
+ // If even the first line is missing, fill with black color
+ memset(output_line, 0, min_stride);
+ } else {
+ // else, just replicate the line above.
+ memcpy(output_line, output_line - stride, min_stride);
+ }
+ output_line += stride;
+ }
+ argball->fraction_read_ = 1.0; // consider all lines as read
+ // prevent error-on-exit in libjpeg:
+ cinfo.output_scanline = cinfo.output_height;
+ }
+ break;
+ }
+ DCHECK_EQ(num_lines_read, 1);
+ TF_ANNOTATE_MEMORY_IS_INITIALIZED(output_line, min_stride);
+ output_line += stride;
+ }
+ delete[] tempdata;
+
+ // Convert the RGB data to RGBA, with alpha set to 0xFF to indicate
+ // opacity.
+ // RGBRGBRGB... --> RGBARGBARGBA...
+ if (components == 4) {
+ // Start on the last line.
+ JSAMPLE* scanlineptr =
+ static_cast<JSAMPLE*>(dstdata + (cinfo.output_height - 1) * stride);
+ const JSAMPLE kOpaque = -1; // All ones appropriate for JSAMPLE.
+ const int right_rgb = (cinfo.output_width - 1) * 3;
+ const int right_rgba = (cinfo.output_width - 1) * 4;
+
+ for (int y = cinfo.output_height; y-- > 0;) {
+ // We do all the transformations in place, going backwards for each row.
+ const JSAMPLE* rgb_pixel = scanlineptr + right_rgb;
+ JSAMPLE* rgba_pixel = scanlineptr + right_rgba;
+ scanlineptr -= stride;
+ for (int x = cinfo.output_width; x-- > 0;
+ rgba_pixel -= 4, rgb_pixel -= 3) {
+ // We copy the 3 bytes at rgb_pixel into the 4 bytes at rgba_pixel
+ // The "a" channel is set to be opaque.
+ rgba_pixel[3] = kOpaque;
+ rgba_pixel[2] = rgb_pixel[2];
+ rgba_pixel[1] = rgb_pixel[1];
+ rgba_pixel[0] = rgb_pixel[0];
+ }
+ }
+ }
+
+ switch (components) {
+ case 1:
+ if (cinfo.output_components != 1) {
+ error = JPEGERRORS_BAD_PARAM;
+ }
+ break;
+ case 3:
+ case 4:
+ if (cinfo.out_color_space == JCS_CMYK) {
+ if (cinfo.output_components != 4) {
+ error = JPEGERRORS_BAD_PARAM;
+ }
+ } else {
+ if (cinfo.output_components != 3) {
+ error = JPEGERRORS_BAD_PARAM;
+ }
+ }
+ break;
+ default:
+ // will never happen, should be catched by the previous switch
+ LOG(ERROR) << "Invalid components value " << components << std::endl;
+ jpeg_destroy_decompress(&cinfo);
+ return nullptr;
+ }
+
+ // save number of warnings if requested
+ if (nwarn != nullptr) {
+ *nwarn = cinfo.err->num_warnings;
+ }
+
+ // Handle errors in JPEG
+ switch (error) {
+ case JPEGERRORS_OK:
+ jpeg_finish_decompress(&cinfo);
+ break;
+ case JPEGERRORS_UNEXPECTED_END_OF_DATA:
+ case JPEGERRORS_BAD_PARAM:
+ jpeg_abort(reinterpret_cast<j_common_ptr>(&cinfo));
+ break;
+ default:
+ LOG(ERROR) << "Unhandled case " << error;
+ break;
+ }
+ jpeg_destroy_decompress(&cinfo);
+
+ return dstdata;
+}
+
+} // anonymous namespace
+
+// -----------------------------------------------------------------------------
+// We do the apparently silly thing of packing 5 of the arguments
+// into a structure that is then passed to another routine
+// that does all the work. The reason is that we want to catch
+// fatal JPEG library errors with setjmp/longjmp, and g++ and
+// associated libraries aren't good enough to guarantee that 7
+// parameters won't get clobbered by the longjmp. So we help
+// it out a little.
+uint8* Uncompress(const void* srcdata, int datasize,
+ const UncompressFlags& flags, int* nwarn,
+ std::function<uint8*(int, int, int)> allocate_output) {
+ FewerArgsForCompiler argball(datasize, flags, nwarn, allocate_output);
+ uint8* const dstdata = UncompressLow(srcdata, &argball);
+ const float fraction_read = argball.fraction_read_;
+ if (dstdata == NULL ||
+ fraction_read < std::min(1.0f, flags.min_acceptable_fraction)) {
+ // Major failure, none or too-partial read returned; get out
+ return NULL;
+ }
+
+ // If there was an error in reading the jpeg data,
+ // set the unread pixels to black
+ if (fraction_read < 1.0) {
+ const int first_bad_line =
+ static_cast<int>(fraction_read * argball.height_);
+ uint8* start = dstdata + first_bad_line * argball.stride_;
+ const int nbytes = (argball.height_ - first_bad_line) * argball.stride_;
+ memset(static_cast<void*>(start), 0, nbytes);
+ }
+
+ return dstdata;
+}
+
+uint8* Uncompress(const void* srcdata, int datasize,
+ const UncompressFlags& flags, int* pwidth, int* pheight,
+ int* pcomponents, int* nwarn) {
+ uint8* buffer = NULL;
+ uint8* result =
+ Uncompress(srcdata, datasize, flags, nwarn,
+ [=, &buffer](int width, int height, int components) {
+ if (pwidth != nullptr) *pwidth = width;
+ if (pheight != nullptr) *pheight = height;
+ if (pcomponents != nullptr) *pcomponents = components;
+ buffer = new uint8[height * width * components];
+ return buffer;
+ });
+ if (!result) delete[] buffer;
+ return result;
+}
+
+// ----------------------------------------------------------------------------
+// Computes image information from jpeg header.
+// Returns true on success; false on failure.
+bool GetImageInfo(const void* srcdata, int datasize, int* width, int* height,
+ int* components) {
+ // Init in case of failure
+ if (width) *width = 0;
+ if (height) *height = 0;
+ if (components) *components = 0;
+
+ // If empty image, return
+ if (datasize == 0 || srcdata == NULL) return false;
+
+ // Initialize libjpeg structures to have a memory source
+ // Modify the usual jpeg error manager to catch fatal errors.
+ struct jpeg_decompress_struct cinfo;
+ struct jpeg_error_mgr jerr;
+ jmp_buf jpeg_jmpbuf;
+ cinfo.err = jpeg_std_error(&jerr);
+ cinfo.client_data = &jpeg_jmpbuf;
+ jerr.error_exit = CatchError;
+ if (setjmp(jpeg_jmpbuf)) {
+ return false;
+ }
+
+ // set up, read header, set image parameters, save size
+ jpeg_create_decompress(&cinfo);
+ SetSrc(&cinfo, srcdata, datasize, false);
+
+ jpeg_read_header(&cinfo, TRUE);
+ jpeg_start_decompress(&cinfo); // required to transfer image size to cinfo
+ if (width) *width = cinfo.output_width;
+ if (height) *height = cinfo.output_height;
+ if (components) *components = cinfo.output_components;
+
+ jpeg_destroy_decompress(&cinfo);
+
+ return true;
+}
+
+// -----------------------------------------------------------------------------
+// Compression
+
+namespace {
+bool CompressInternal(const uint8* srcdata, int width, int height,
+ const CompressFlags& flags, string* output) {
+ output->clear();
+ const int components = (static_cast<int>(flags.format) & 0xff);
+ int in_stride = flags.stride;
+ if (in_stride == 0) {
+ in_stride = width * (static_cast<int>(flags.format) & 0xff);
+ } else if (in_stride < width * components) {
+ LOG(ERROR) << "Incompatible input stride";
+ return false;
+ }
+
+ JOCTET* buffer = 0;
+
+ // NOTE: for broader use xmp_metadata should be made a unicode string
+ CHECK(srcdata != nullptr);
+ CHECK(output != nullptr);
+ // This struct contains the JPEG compression parameters and pointers to
+ // working space
+ struct jpeg_compress_struct cinfo;
+ // This struct represents a JPEG error handler.
+ struct jpeg_error_mgr jerr;
+ jmp_buf jpeg_jmpbuf; // recovery point in case of error
+
+ // Step 1: allocate and initialize JPEG compression object
+ // Use the usual jpeg error manager.
+ cinfo.err = jpeg_std_error(&jerr);
+ cinfo.client_data = &jpeg_jmpbuf;
+ jerr.error_exit = CatchError;
+ if (setjmp(jpeg_jmpbuf)) {
+ output->clear();
+ delete[] buffer;
+ return false;
+ }
+
+ jpeg_create_compress(&cinfo);
+
+ // Step 2: specify data destination
+ // We allocate a buffer of reasonable size. If we have a small image, just
+ // estimate the size of the output using the number of bytes of the input.
+ // If this is getting too big, we will append to the string by chunks of 1MB.
+ // This seems like a reasonable compromise between performance and memory.
+ int bufsize = std::min(width * height * components, 1 << 20);
+ buffer = new JOCTET[bufsize];
+ SetDest(&cinfo, buffer, bufsize, output);
+
+ // Step 3: set parameters for compression
+ cinfo.image_width = width;
+ cinfo.image_height = height;
+ switch (components) {
+ case 1:
+ cinfo.input_components = 1;
+ cinfo.in_color_space = JCS_GRAYSCALE;
+ break;
+ case 3:
+ case 4:
+ cinfo.input_components = 3;
+ cinfo.in_color_space = JCS_RGB;
+ break;
+ default:
+ LOG(ERROR) << " Invalid components value " << components << std::endl;
+ output->clear();
+ delete[] buffer;
+ return false;
+ }
+ jpeg_set_defaults(&cinfo);
+ if (flags.optimize_jpeg_size) cinfo.optimize_coding = TRUE;
+
+ cinfo.density_unit = flags.density_unit; // JFIF code for pixel size units:
+ // 1 = in, 2 = cm
+ cinfo.X_density = flags.x_density; // Horizontal pixel density
+ cinfo.Y_density = flags.y_density; // Vertical pixel density
+ jpeg_set_quality(&cinfo, flags.quality, TRUE);
+
+ if (flags.progressive) {
+ jpeg_simple_progression(&cinfo);
+ }
+
+ if (!flags.chroma_downsampling) {
+ // Turn off chroma subsampling (it is on by default). For more details on
+ // chroma subsampling, see http://en.wikipedia.org/wiki/Chroma_subsampling.
+ for (int i = 0; i < cinfo.num_components; ++i) {
+ cinfo.comp_info[i].h_samp_factor = 1;
+ cinfo.comp_info[i].v_samp_factor = 1;
+ }
+ }
+
+ jpeg_start_compress(&cinfo, TRUE);
+
+ // Embed XMP metadata if any
+ if (!flags.xmp_metadata.empty()) {
+ // XMP metadata is embedded in the APP1 tag of JPEG and requires this
+ // namespace header string (null-terminated)
+ const string name_space = "http://ns.adobe.com/xap/1.0/";
+ const int name_space_length = name_space.size();
+ const int metadata_length = flags.xmp_metadata.size();
+ const int packet_length = metadata_length + name_space_length + 1;
+ std::unique_ptr<JOCTET[]> joctet_packet(new JOCTET[packet_length]);
+
+ for (int i = 0; i < name_space_length; i++) {
+ // Conversion char --> JOCTET
+ joctet_packet[i] = name_space[i];
+ }
+ joctet_packet[name_space_length] = 0; // null-terminate namespace string
+
+ for (int i = 0; i < metadata_length; i++) {
+ // Conversion char --> JOCTET
+ joctet_packet[i + name_space_length + 1] = flags.xmp_metadata[i];
+ }
+ jpeg_write_marker(&cinfo, JPEG_APP0 + 1, joctet_packet.get(),
+ packet_length);
+ }
+
+ // JSAMPLEs per row in image_buffer
+ std::unique_ptr<JSAMPLE[]> row_temp(
+ new JSAMPLE[width * cinfo.input_components]);
+ while (cinfo.next_scanline < cinfo.image_height) {
+ JSAMPROW row_pointer[1]; // pointer to JSAMPLE row[s]
+ const uint8* r = &srcdata[cinfo.next_scanline * in_stride];
+ uint8* p = static_cast<uint8*>(row_temp.get());
+ switch (flags.format) {
+ case FORMAT_RGBA: {
+ for (int i = 0; i < width; ++i, p += 3, r += 4) {
+ p[0] = r[0];
+ p[1] = r[1];
+ p[2] = r[2];
+ }
+ row_pointer[0] = row_temp.get();
+ break;
+ }
+ case FORMAT_ABGR: {
+ for (int i = 0; i < width; ++i, p += 3, r += 4) {
+ p[0] = r[3];
+ p[1] = r[2];
+ p[2] = r[1];
+ }
+ row_pointer[0] = row_temp.get();
+ break;
+ }
+ default: {
+ row_pointer[0] = reinterpret_cast<JSAMPLE*>(const_cast<JSAMPLE*>(r));
+ }
+ }
+ CHECK_EQ(jpeg_write_scanlines(&cinfo, row_pointer, 1), 1);
+ }
+ jpeg_finish_compress(&cinfo);
+
+ // release JPEG compression object
+ jpeg_destroy_compress(&cinfo);
+ delete[] buffer;
+ return true;
+}
+
+} // anonymous namespace
+
+// -----------------------------------------------------------------------------
+
+bool Compress(const void* srcdata, int width, int height,
+ const CompressFlags& flags, string* output) {
+ return CompressInternal(static_cast<const uint8*>(srcdata), width, height,
+ flags, output);
+}
+
+string Compress(const void* srcdata, int width, int height,
+ const CompressFlags& flags) {
+ string temp;
+ CompressInternal(static_cast<const uint8*>(srcdata), width, height, flags,
+ &temp);
+ // If CompressInternal fails, temp will be empty.
+ return temp;
+}
+
+} // namespace jpeg
+} // namespace tensorflow
diff --git a/tensorflow/core/lib/jpeg/jpeg_mem.h b/tensorflow/core/lib/jpeg/jpeg_mem.h
new file mode 100644
index 0000000000..19ba7d4acf
--- /dev/null
+++ b/tensorflow/core/lib/jpeg/jpeg_mem.h
@@ -0,0 +1,130 @@
+// This file defines functions to compress and uncompress JPEG files
+// to and from memory. It provides interfaces for raw images
+// (data array and size fields).
+// Direct manipulation of JPEG strings are supplied: Flip, Rotate, Crop..
+
+#ifndef TENSORFLOW_LIB_JPEG_JPEG_MEM_H_
+#define TENSORFLOW_LIB_JPEG_JPEG_MEM_H_
+
+#include <functional>
+#include <string>
+#include <vector>
+
+#include "tensorflow/core/platform/port.h"
+#include "tensorflow/core/lib/core/stringpiece.h"
+
+namespace tensorflow {
+namespace jpeg {
+
+// Flags for Uncompress
+struct UncompressFlags {
+ // ratio can be 1, 2, 4, or 8 and represent the denominator for the scaling
+ // factor (eg ratio = 4 means that the resulting image will be at 1/4 original
+ // size in both directions).
+ int ratio = 1;
+
+ // The number of bytes per pixel (1, 3 or 4), or 0 for autodetect.
+ int components = 0;
+
+ // If true, decoder will use a slower but nicer upscaling of the chroma
+ // planes (yuv420/422 only).
+ bool fancy_upscaling = true;
+
+ // If true, will attempt to fill in missing lines of truncated files
+ bool try_recover_truncated_jpeg = false;
+
+ // The minimum required fraction of lines read before the image is accepted.
+ float min_acceptable_fraction = 1.0;
+
+ // The distance in bytes from one scanline to the other. Should be at least
+ // equal to width*components*sizeof(JSAMPLE). If 0 is passed, the stride
+ // used will be this minimal value.
+ int stride = 0;
+};
+
+// Uncompress some raw JPEG data given by the pointer srcdata and the length
+// datasize.
+// - width and height are the address where to store the size of the
+// uncompressed image in pixels. May be nullptr.
+// - components is the address where the number of read components are
+// stored. This is *output only*: to request a specific number of
+// components use flags.components. May be nullptr.
+// - nwarn is the address in which to store the number of warnings.
+// May be nullptr.
+// The function returns a pointer to the raw uncompressed data or NULL if
+// there was an error. The caller of the function is responsible for
+// freeing the memory (using delete []).
+uint8* Uncompress(const void* srcdata, int datasize,
+ const UncompressFlags& flags, int* width, int* height,
+ int* components, // Output only: useful with autodetect
+ int* nwarn);
+
+// Version of Uncompress that allocates memory via a callback. The callback
+// arguments are (width, height, components). If the size is known ahead of
+// time this function can return an existing buffer; passing a callback allows
+// the buffer to be shaped based on the JPEG header. The caller is responsible
+// for freeing the memory *even along error paths*.
+uint8* Uncompress(const void* srcdata, int datasize,
+ const UncompressFlags& flags, int* nwarn,
+ std::function<uint8*(int, int, int)> allocate_output);
+
+// Read jpeg header and get image information. Returns true on success.
+// The width, height, and components points may be null.
+bool GetImageInfo(const void* srcdata, int datasize, int* width, int* height,
+ int* components);
+
+// Note: (format & 0xff) = number of components (<=> bytes per pixels)
+enum Format {
+ FORMAT_GRAYSCALE = 0x001, // 1 byte/pixel
+ FORMAT_RGB = 0x003, // 3 bytes/pixel RGBRGBRGBRGB...
+ FORMAT_RGBA = 0x004, // 4 bytes/pixel RGBARGBARGBARGBA...
+ FORMAT_ABGR = 0x104 // 4 bytes/pixel ABGRABGRABGR...
+};
+
+// Flags for compression
+struct CompressFlags {
+ // Encoding of the input data for compression
+ Format format;
+
+ // Quality of the compression from 0-100
+ int quality = 95;
+
+ // If true, create a jpeg image that loads progressively
+ bool progressive = false;
+
+ // If true, reduce jpeg size without changing quality (at the cost of CPU/RAM)
+ bool optimize_jpeg_size = false;
+
+ // See http://en.wikipedia.org/wiki/Chroma_subsampling
+ bool chroma_downsampling = true;
+
+ // Resolution
+ int density_unit = 1; // 1 = in, 2 = cm
+ int x_density = 300;
+ int y_density = 300;
+
+ // If not empty, embed this XMP metadata in the image header
+ StringPiece xmp_metadata;
+
+ // The distance in bytes from one scanline to the other. Should be at least
+ // equal to width*components*sizeof(JSAMPLE). If 0 is passed, the stride
+ // used will be this minimal value.
+ int stride = 0;
+};
+
+// Compress some raw image given in srcdata, the data is a 2D array of size
+// stride*height with one of the formats enumerated above.
+// The encoded data is returned as a string.
+// If not empty, XMP metadata can be embedded in the image header
+// On error, returns the empty string (which is never a valid jpeg).
+string Compress(const void* srcdata, int width, int height,
+ const CompressFlags& flags);
+
+// On error, returns false and sets output to empty.
+bool Compress(const void* srcdata, int width, int height,
+ const CompressFlags& flags, string* output);
+
+} // namespace jpeg
+} // namespace tensorflow
+
+#endif // TENSORFLOW_LIB_JPEG_JPEG_MEM_H_
diff --git a/tensorflow/core/lib/jpeg/jpeg_mem_unittest.cc b/tensorflow/core/lib/jpeg/jpeg_mem_unittest.cc
new file mode 100644
index 0000000000..23e72f9d57
--- /dev/null
+++ b/tensorflow/core/lib/jpeg/jpeg_mem_unittest.cc
@@ -0,0 +1,304 @@
+#include "tensorflow/core/lib/jpeg/jpeg_mem.h"
+
+#include <setjmp.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <memory>
+
+#include "tensorflow/core/lib/jpeg/jpeg_handle.h"
+#include "tensorflow/core/platform/logging.h"
+#include "tensorflow/core/platform/port.h"
+#include "tensorflow/core/public/env.h"
+#include <gtest/gtest.h>
+
+#include "tensorflow/core/lib/core/casts.h"
+
+namespace tensorflow {
+namespace jpeg {
+namespace {
+
+const char kTestData[] = "tensorflow/core/lib/jpeg/testdata/";
+
+int ComputeSumAbsoluteDifference(const uint8* a, const uint8* b, int width,
+ int height, int a_stride, int b_stride) {
+ int totalerr = 0;
+ for (int i = 0; i < height; i++) {
+ const uint8* const pa = a + i * a_stride;
+ const uint8* const pb = b + i * b_stride;
+ for (int j = 0; j < 3 * width; j++) {
+ totalerr += abs(static_cast<int>(pa[j]) - static_cast<int>(pb[j]));
+ }
+ }
+ return totalerr;
+}
+
+// Reads the contents of the file into output
+void ReadFileToStringOrDie(Env* env, const string& filename, string* output) {
+ TF_CHECK_OK(ReadFileToString(env, filename, output));
+}
+
+void TestJPEG(Env* env, const string& jpegfile) {
+ // Read the data from the jpeg file into memory
+ string jpeg;
+ ReadFileToStringOrDie(Env::Default(), jpegfile, &jpeg);
+ const int fsize = jpeg.size();
+ const uint8* const temp = bit_cast<const uint8*>(jpeg.data());
+
+ // try partial decoding (half of the data)
+ int w, h, c;
+ std::unique_ptr<uint8[]> imgdata;
+
+ UncompressFlags flags;
+ flags.components = 3;
+
+ // set min_acceptable_fraction to something insufficient
+ flags.min_acceptable_fraction = 0.8;
+ imgdata.reset(Uncompress(temp, fsize / 2, flags, &w, &h, &c, NULL));
+ CHECK(imgdata.get() == NULL);
+
+ // now, use a value that makes fsize/2 be enough for a black-filling
+ flags.min_acceptable_fraction = 0.01;
+ imgdata.reset(Uncompress(temp, fsize / 2, flags, &w, &h, &c, NULL));
+ CHECK(imgdata.get() != NULL);
+
+ // finally, uncompress the whole data
+ flags.min_acceptable_fraction = 1.0;
+ imgdata.reset(Uncompress(temp, fsize, flags, &w, &h, &c, NULL));
+ CHECK(imgdata.get() != NULL);
+
+ // Uncompress the data to RGBA, too
+ flags.min_acceptable_fraction = 1.0;
+ flags.components = 4;
+ imgdata.reset(Uncompress(temp, fsize, flags, &w, &h, &c, NULL));
+ CHECK(imgdata.get() != NULL);
+}
+
+TEST(JpegMemTest, Jpeg) {
+ Env* env = Env::Default();
+ const string data_path = kTestData;
+
+ // Name of a valid jpeg file on the disk
+ TestJPEG(env, data_path + "jpeg_merge_test1.jpg");
+
+ // Exercise CMYK machinery as well
+ TestJPEG(env, data_path + "jpeg_merge_test1_cmyk.jpg");
+}
+
+TEST(JpegMemTest, Jpeg2) {
+ // create known data, for size in_w x in_h
+ const int in_w = 256;
+ const int in_h = 256;
+ const int stride1 = 3 * in_w;
+ const std::unique_ptr<uint8[]> refdata1(new uint8[stride1 * in_h]);
+ for (int i = 0; i < in_h; i++) {
+ for (int j = 0; j < in_w; j++) {
+ const int offset = i * stride1 + 3 * j;
+ refdata1[offset + 0] = i;
+ refdata1[offset + 1] = j;
+ refdata1[offset + 2] = static_cast<uint8>((i + j) >> 1);
+ }
+ }
+
+ // duplicate with weird input stride
+ const int stride2 = 3 * 357;
+ const std::unique_ptr<uint8[]> refdata2(new uint8[stride2 * in_h]);
+ for (int i = 0; i < in_h; i++) {
+ memcpy(&refdata2[i * stride2], &refdata1[i * stride1], 3 * in_w);
+ }
+
+ // Test compression
+ string cpdata1, cpdata2;
+ {
+ const string kXMP = "XMP_TEST_123";
+
+ // Compress it to JPEG
+ CompressFlags flags;
+ flags.format = FORMAT_RGB;
+ flags.quality = 97;
+ flags.xmp_metadata = kXMP;
+ cpdata1 = Compress(refdata1.get(), in_w, in_h, flags);
+ flags.stride = stride2;
+ cpdata2 = Compress(refdata2.get(), in_w, in_h, flags);
+ // Different input stride shouldn't change the output
+ CHECK_EQ(cpdata1, cpdata2);
+
+ // Verify valid XMP.
+ CHECK_NE(string::npos, cpdata1.find(kXMP));
+
+ // Test the other API, where a storage string is supplied
+ string cptest;
+ flags.stride = 0;
+ Compress(refdata1.get(), in_w, in_h, flags, &cptest);
+ CHECK_EQ(cptest, cpdata1);
+ flags.stride = stride2;
+ Compress(refdata2.get(), in_w, in_h, flags, &cptest);
+ CHECK_EQ(cptest, cpdata2);
+ }
+
+ // Uncompress twice: once with 3 components and once with autodetect
+ std::unique_ptr<uint8[]> imgdata1;
+ for (const int components : {0, 3}) {
+ // Uncompress it
+ UncompressFlags flags;
+ flags.components = components;
+ int w, h, c;
+ imgdata1.reset(
+ Uncompress(cpdata1.c_str(), cpdata1.length(), flags, &w, &h, &c, NULL));
+
+ // Check obvious formatting stuff
+ CHECK_EQ(w, in_w);
+ CHECK_EQ(h, in_h);
+ CHECK_EQ(c, 3);
+ CHECK(imgdata1.get());
+
+ // Compare the two images
+ const int totalerr = ComputeSumAbsoluteDifference(
+ imgdata1.get(), refdata1.get(), in_w, in_h, stride1, stride1);
+ CHECK_LE(totalerr, 85000);
+ }
+
+ // check the second image too. Should be bitwise identical to the first.
+ // uncompress using a weird stride
+ {
+ UncompressFlags flags;
+ flags.stride = 3 * 411;
+ const std::unique_ptr<uint8[]> imgdata2(new uint8[flags.stride * in_h]);
+ CHECK(imgdata2.get() == Uncompress(cpdata2.c_str(), cpdata2.length(), flags,
+ NULL, [&imgdata2](int w, int h, int c) {
+ CHECK_EQ(w, in_w);
+ CHECK_EQ(h, in_h);
+ CHECK_EQ(c, 3);
+ return imgdata2.get();
+ }));
+ const int totalerr = ComputeSumAbsoluteDifference(
+ imgdata1.get(), imgdata2.get(), in_w, in_h, stride1, flags.stride);
+ CHECK_EQ(totalerr, 0);
+ }
+}
+
+// Takes JPEG data and reads its headers to determine whether or not the JPEG
+// was chroma downsampled.
+bool IsChromaDownsampled(const string& jpegdata) {
+ // Initialize libjpeg structures to have a memory source
+ // Modify the usual jpeg error manager to catch fatal errors.
+ struct jpeg_decompress_struct cinfo;
+ struct jpeg_error_mgr jerr;
+ jmp_buf jpeg_jmpbuf;
+ cinfo.err = jpeg_std_error(&jerr);
+ cinfo.client_data = &jpeg_jmpbuf;
+ jerr.error_exit = CatchError;
+ if (setjmp(jpeg_jmpbuf)) return false;
+
+ // set up, read header, set image parameters, save size
+ jpeg_create_decompress(&cinfo);
+ SetSrc(&cinfo, jpegdata.c_str(), jpegdata.size(), false);
+
+ jpeg_read_header(&cinfo, TRUE);
+ jpeg_start_decompress(&cinfo); // required to transfer image size to cinfo
+ const int components = cinfo.output_components;
+ if (components == 1) return false;
+
+ // Check validity
+ CHECK_EQ(3, components);
+ CHECK_EQ(cinfo.comp_info[1].h_samp_factor, cinfo.comp_info[2].h_samp_factor)
+ << "The h sampling factors should be the same.";
+ CHECK_EQ(cinfo.comp_info[1].v_samp_factor, cinfo.comp_info[2].v_samp_factor)
+ << "The v sampling factors should be the same.";
+ for (int i = 0; i < components; ++i) {
+ CHECK_GT(cinfo.comp_info[i].h_samp_factor, 0) << "Invalid sampling factor.";
+ CHECK_EQ(cinfo.comp_info[i].h_samp_factor, cinfo.comp_info[i].v_samp_factor)
+ << "The sampling factor should be the same in both directions.";
+ }
+
+ // We're downsampled if we use fewer samples for color than for brightness.
+ // Do this before deallocating cinfo.
+ const bool downsampled =
+ cinfo.comp_info[1].h_samp_factor < cinfo.comp_info[0].h_samp_factor;
+
+ jpeg_destroy_decompress(&cinfo);
+ return downsampled;
+}
+
+TEST(JpegMemTest, ChromaDownsampling) {
+ // Read the data from a test jpeg file into memory
+ const string jpegfile = string(kTestData) + "jpeg_merge_test1.jpg";
+ string jpeg;
+ ReadFileToStringOrDie(Env::Default(), jpegfile, &jpeg);
+
+ // Verify that compressing the JPEG with chroma downsampling works.
+ //
+ // First, uncompress the JPEG.
+ UncompressFlags unflags;
+ unflags.components = 3;
+ int w, h, c, num_warnings;
+ std::unique_ptr<uint8[]> uncompressed(Uncompress(
+ jpeg.c_str(), jpeg.size(), unflags, &w, &h, &c, &num_warnings));
+ CHECK(uncompressed.get() != NULL);
+ CHECK_EQ(num_warnings, 0);
+
+ // Recompress the JPEG with and without chroma downsampling
+ for (const bool downsample : {false, true}) {
+ CompressFlags flags;
+ flags.format = FORMAT_RGB;
+ flags.quality = 85;
+ flags.chroma_downsampling = downsample;
+ string recompressed;
+ Compress(uncompressed.get(), w, h, flags, &recompressed);
+ CHECK(!recompressed.empty());
+ CHECK_EQ(IsChromaDownsampled(recompressed), downsample);
+ }
+}
+
+void TestBadJPEG(Env* env, const string& bad_jpeg_file, int expected_width,
+ int expected_height, const string& reference_RGB_file,
+ const bool try_recover_truncated_jpeg) {
+ string jpeg;
+ ReadFileToStringOrDie(env, bad_jpeg_file, &jpeg);
+
+ UncompressFlags flags;
+ flags.components = 3;
+ flags.try_recover_truncated_jpeg = try_recover_truncated_jpeg;
+
+ int width, height, components;
+ std::unique_ptr<uint8[]> imgdata;
+ imgdata.reset(Uncompress(jpeg.c_str(), jpeg.size(), flags, &width, &height,
+ &components, NULL));
+ if (expected_width > 0) { // we expect the file to decode into 'something'
+ CHECK_EQ(width, expected_width);
+ CHECK_EQ(height, expected_height);
+ CHECK_EQ(components, 3);
+ CHECK(imgdata.get());
+ if (!reference_RGB_file.empty()) {
+ string ref;
+ ReadFileToStringOrDie(env, reference_RGB_file, &ref);
+ CHECK(!memcmp(ref.data(), imgdata.get(), ref.size()));
+ }
+ } else { // no decodable
+ CHECK(!imgdata.get()) << "file:" << bad_jpeg_file;
+ }
+}
+
+TEST(JpegMemTest, BadJpeg) {
+ Env* env = Env::Default();
+ const string data_path = kTestData;
+
+ // Test corrupt file
+ TestBadJPEG(env, data_path + "bad_huffman.jpg", 1024, 768, "", false);
+ TestBadJPEG(env, data_path + "corrupt.jpg", 0 /*120*/, 90, "", false);
+
+ // Truncated files, undecodable because of missing lines:
+ TestBadJPEG(env, data_path + "corrupt34_2.jpg", 0, 3300, "", false);
+ TestBadJPEG(env, data_path + "corrupt34_3.jpg", 0, 3300, "", false);
+ TestBadJPEG(env, data_path + "corrupt34_4.jpg", 0, 3300, "", false);
+
+ // Try in 'recover' mode now:
+ TestBadJPEG(env, data_path + "corrupt34_2.jpg", 2544, 3300, "", true);
+ TestBadJPEG(env, data_path + "corrupt34_3.jpg", 2544, 3300, "", true);
+ TestBadJPEG(env, data_path + "corrupt34_4.jpg", 2544, 3300, "", true);
+}
+
+} // namespace
+} // namespace jpeg
+} // namespace tensorflow
diff --git a/tensorflow/core/lib/jpeg/testdata/bad_huffman.jpg b/tensorflow/core/lib/jpeg/testdata/bad_huffman.jpg
new file mode 100644
index 0000000000..ef5b6f12c5
--- /dev/null
+++ b/tensorflow/core/lib/jpeg/testdata/bad_huffman.jpg
Binary files differ
diff --git a/tensorflow/core/lib/jpeg/testdata/corrupt.jpg b/tensorflow/core/lib/jpeg/testdata/corrupt.jpg
new file mode 100644
index 0000000000..5e2fe6c56f
--- /dev/null
+++ b/tensorflow/core/lib/jpeg/testdata/corrupt.jpg
Binary files differ
diff --git a/tensorflow/core/lib/jpeg/testdata/corrupt34_2.jpg b/tensorflow/core/lib/jpeg/testdata/corrupt34_2.jpg
new file mode 100644
index 0000000000..4211155c45
--- /dev/null
+++ b/tensorflow/core/lib/jpeg/testdata/corrupt34_2.jpg
Binary files differ
diff --git a/tensorflow/core/lib/jpeg/testdata/corrupt34_3.jpg b/tensorflow/core/lib/jpeg/testdata/corrupt34_3.jpg
new file mode 100644
index 0000000000..c1c2a9d1e1
--- /dev/null
+++ b/tensorflow/core/lib/jpeg/testdata/corrupt34_3.jpg
Binary files differ
diff --git a/tensorflow/core/lib/jpeg/testdata/corrupt34_4.jpg b/tensorflow/core/lib/jpeg/testdata/corrupt34_4.jpg
new file mode 100644
index 0000000000..b8e7308ba0
--- /dev/null
+++ b/tensorflow/core/lib/jpeg/testdata/corrupt34_4.jpg
Binary files differ
diff --git a/tensorflow/core/lib/jpeg/testdata/jpeg_merge_test1.jpg b/tensorflow/core/lib/jpeg/testdata/jpeg_merge_test1.jpg
new file mode 100644
index 0000000000..5e348a12fd
--- /dev/null
+++ b/tensorflow/core/lib/jpeg/testdata/jpeg_merge_test1.jpg
Binary files differ
diff --git a/tensorflow/core/lib/jpeg/testdata/jpeg_merge_test1_cmyk.jpg b/tensorflow/core/lib/jpeg/testdata/jpeg_merge_test1_cmyk.jpg
new file mode 100644
index 0000000000..15f895960d
--- /dev/null
+++ b/tensorflow/core/lib/jpeg/testdata/jpeg_merge_test1_cmyk.jpg
Binary files differ