blob: 8bf03b58102baa56fbde3fb2c9ac0170b39f99b0 [file] [log] [blame]
/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Author: Bryan McQuade, Matthew Steele
#include "pagespeed/kernel/image/jpeg_reader.h"
#include <setjmp.h>
#include <stdlib.h>
#include "pagespeed/kernel/base/message_handler.h"
#include "pagespeed/kernel/base/string.h"
extern "C" {
#ifdef USE_SYSTEM_LIBJPEG
#include "jerror.h" // NOLINT
#include "jpeglib.h" // NOLINT
#else
#include "third_party/libjpeg_turbo/src/jerror.h"
#include "third_party/libjpeg_turbo/src/jpeglib.h"
#endif
}
namespace {
// Unfortunately, libjpeg normally only supports reading images from C FILE
// pointers, wheras we want to read from a C++ string. Fortunately, libjpeg
// also provides an extension mechanism. Below, we define a new kind of
// jpeg_source_mgr for reading from strings.
// The below code was adapted from the JPEGMemoryReader class that can be found
// in src/o3d/core/cross/bitmap_jpg.cc in the Chromium source tree (r29423).
// That code is Copyright 2009, Google Inc.
METHODDEF(void) InitSource(j_decompress_ptr cinfo) {}
METHODDEF(boolean) FillInputBuffer(j_decompress_ptr cinfo) {
// Should not be called because we already have all the data
ERREXIT(cinfo, JERR_INPUT_EOF);
return TRUE;
}
METHODDEF(void) SkipInputData(j_decompress_ptr cinfo,
long num_bytes) { // NOLINT
jpeg_source_mgr &mgr = *(cinfo->src);
const int bytes_remaining = mgr.bytes_in_buffer - num_bytes;
mgr.bytes_in_buffer = bytes_remaining < 0 ? 0 : bytes_remaining;
mgr.next_input_byte += num_bytes;
}
METHODDEF(void) TermSource(j_decompress_ptr cinfo) {}
// Call this function on a j_decompress_ptr to install a reader that will read
// from the given string.
void JpegStringReader(j_decompress_ptr cinfo,
const void* image_data,
size_t image_length) {
if (cinfo->src == NULL) {
cinfo->src = (struct jpeg_source_mgr*)
(*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT,
sizeof(jpeg_source_mgr));
}
struct jpeg_source_mgr &src = *(cinfo->src);
src.init_source = InitSource;
src.fill_input_buffer = FillInputBuffer;
src.skip_input_data = SkipInputData;
src.resync_to_restart = jpeg_resync_to_restart; // default method
src.term_source = TermSource;
src.bytes_in_buffer = image_length;
src.next_input_byte = static_cast<const JOCTET*>(image_data);
}
// ErrorExit() is installed as a callback, called on errors
// encountered within libjpeg. The longjmp jumps back
// to the setjmp in JpegOptimizer::CreateOptimizedJpeg().
void ErrorExit(j_common_ptr jpeg_state_struct) {
jmp_buf *env = static_cast<jmp_buf *>(jpeg_state_struct->client_data);
(*jpeg_state_struct->err->output_message)(jpeg_state_struct);
if (env)
longjmp(*env, 1);
}
// OutputMessageFromReader is called by libjpeg code on an error when reading.
// Without this function, a default function would print to standard error.
void OutputMessage(j_common_ptr jpeg_decompress) {
// The following code is handy for debugging.
/*
char buf[JMSG_LENGTH_MAX];
(*jpeg_decompress->err->format_message)(jpeg_decompress, buf);
DLOG(INFO) << "JPEG Reader Error: " << buf;
*/
}
} // namespace
namespace pagespeed {
namespace image_compression {
using net_instaweb::MessageHandler;
struct JpegEnv {
jpeg_decompress_struct jpeg_decompress_;
jpeg_error_mgr decompress_error_;
jmp_buf jmp_buf_env_;
};
JpegReader::JpegReader(MessageHandler* handler)
: message_handler_(handler) {
jpeg_decompress_ = static_cast<jpeg_decompress_struct*>(
malloc(sizeof(jpeg_decompress_struct)));
decompress_error_ = static_cast<jpeg_error_mgr*>(
malloc(sizeof(jpeg_error_mgr)));
memset(jpeg_decompress_, 0, sizeof(jpeg_decompress_struct));
memset(decompress_error_, 0, sizeof(jpeg_error_mgr));
jpeg_decompress_->err = jpeg_std_error(decompress_error_);
decompress_error_->error_exit = &ErrorExit;
decompress_error_->output_message = &OutputMessage;
jpeg_create_decompress(jpeg_decompress_);
}
JpegReader::~JpegReader() {
jpeg_destroy_decompress(jpeg_decompress_);
free(decompress_error_);
free(jpeg_decompress_);
}
void JpegReader::PrepareForRead(const void* image_data, size_t image_length) {
// Prepare to read from a string.
JpegStringReader(jpeg_decompress_, image_data, image_length);
}
JpegScanlineReader::JpegScanlineReader(MessageHandler* handler) :
jpeg_env_(NULL),
pixel_format_(UNSUPPORTED),
height_(0),
width_(0),
row_(0),
bytes_per_row_(0),
was_initialized_(false),
is_progressive_(false),
message_handler_(handler) {
row_pointer_[0] = NULL;
}
JpegScanlineReader::~JpegScanlineReader() {
if (was_initialized_) {
Reset();
}
free(jpeg_env_);
}
bool JpegScanlineReader::Reset() {
pixel_format_ = UNSUPPORTED;
height_ = 0;
width_ = 0;
row_ = 0;
bytes_per_row_ = 0;
was_initialized_ = false;
jpeg_destroy_decompress(&(jpeg_env_->jpeg_decompress_));
memset(jpeg_env_, 0, sizeof(JpegEnv));
free(row_pointer_[0]);
row_pointer_[0] = NULL;
return true;
}
ScanlineStatus JpegScanlineReader::InitializeWithStatus(const void* image_data,
size_t image_length) {
if (was_initialized_) {
// Reset the reader if it has been initialized before.
Reset();
} else if (jpeg_env_ == NULL) {
jpeg_env_ = static_cast<JpegEnv*>(malloc(sizeof(JpegEnv)));
memset(jpeg_env_, 0, sizeof(JpegEnv));
}
// libjpeg's error handling mechanism requires that longjmp be used
// to get control after an error.
if (setjmp(jpeg_env_->jmp_buf_env_)) {
// This code is run only when libjpeg hit an error and called
// longjmp(env). It will reset the object to a state where it can be used
// again.
Reset();
return PS_LOGGED_STATUS(PS_LOG_INFO, message_handler_,
SCANLINE_STATUS_INTERNAL_ERROR,
SCANLINE_JPEGREADER,
"libjpeg failed to decode the image.");
}
jpeg_error_mgr* decompress_error = &(jpeg_env_->decompress_error_);
jpeg_decompress_struct* jpeg_decompress = &(jpeg_env_->jpeg_decompress_);
jpeg_decompress->err = jpeg_std_error(decompress_error);
decompress_error->error_exit = &ErrorExit;
decompress_error->output_message = &OutputMessage;
jpeg_create_decompress(jpeg_decompress);
// Need to install env so that it will be longjmp()ed to on error.
jpeg_decompress->client_data = static_cast<void *>(jpeg_env_->jmp_buf_env_);
// Prepare to read from a string.
JpegStringReader(jpeg_decompress, image_data, image_length);
// Read jpeg data into the decompression struct.
jpeg_read_header(jpeg_decompress, TRUE);
width_ = jpeg_decompress->image_width;
height_ = jpeg_decompress->image_height;
// Decode the image to GRAY_8 if it was in gray scale, or to RGB_888
// otherwise.
if (jpeg_decompress->jpeg_color_space == JCS_GRAYSCALE) {
jpeg_decompress->out_color_space = JCS_GRAYSCALE;
pixel_format_ = GRAY_8;
bytes_per_row_ = width_;
} else {
jpeg_decompress->out_color_space = JCS_RGB;
pixel_format_ = RGB_888;
bytes_per_row_ = 3 * width_;
}
is_progressive_ = jpeg_decompress->progressive_mode;
was_initialized_ = true;
return ScanlineStatus(SCANLINE_STATUS_SUCCESS);
}
ScanlineStatus JpegScanlineReader::ReadNextScanlineWithStatus(
void** out_scanline_bytes) {
if (!was_initialized_ || !HasMoreScanLines()) {
return PS_LOGGED_STATUS(PS_LOG_DFATAL, message_handler_,
SCANLINE_STATUS_INTERNAL_ERROR,
SCANLINE_JPEGREADER,
"The reader was not initialized or does not "
"have any more scanlines.");
}
if (setjmp(jpeg_env_->jmp_buf_env_)) {
// This code is run only when libjpeg hit an error and called
// longjmp(env). It will reset the object to a state where it can be used
// again.
Reset();
return PS_LOGGED_STATUS(PS_LOG_INFO, message_handler_,
SCANLINE_STATUS_INTERNAL_ERROR,
SCANLINE_JPEGREADER,
"libjpeg failed to decode the image.");
}
// At the time when ReadNextScanline is called, allocate buffer for holding
// a row of pixels, and initiate decompression.
jpeg_decompress_struct* jpeg_decompress = &(jpeg_env_->jpeg_decompress_);
if (row_ == 0) {
row_pointer_[0] = static_cast<JSAMPLE*>(malloc(bytes_per_row_));
jpeg_start_decompress(jpeg_decompress);
}
// Try to read a scanline.
const JDIMENSION num_scanlines_read =
jpeg_read_scanlines(jpeg_decompress, row_pointer_, 1);
if (num_scanlines_read != 1) {
Reset();
return PS_LOGGED_STATUS(PS_LOG_INFO, message_handler_,
SCANLINE_STATUS_PARSE_ERROR,
SCANLINE_JPEGREADER,
"libjpeg failed to read a scanline.");
}
*out_scanline_bytes = row_pointer_[0];
++row_;
// At the last row, ask libjpeg to finish decompression.
if (!HasMoreScanLines()) {
jpeg_finish_decompress(jpeg_decompress);
}
return ScanlineStatus(SCANLINE_STATUS_SUCCESS);
}
} // namespace image_compression
} // namespace pagespeed