mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
623 lines
22 KiB
C++
623 lines
22 KiB
C++
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
#include "ui/gfx/codec/jpeg_codec.h"
|
|
|
|
#include <setjmp.h>
|
|
|
|
#include "base/logging.h"
|
|
#include "base/memory/scoped_ptr.h"
|
|
#include "third_party/skia/include/core/SkBitmap.h"
|
|
#include "third_party/skia/include/core/SkColorPriv.h"
|
|
|
|
extern "C" {
|
|
#if defined(USE_SYSTEM_LIBJPEG)
|
|
#include <jpeglib.h>
|
|
#elif defined(USE_LIBJPEG_TURBO)
|
|
#include "third_party/libjpeg_turbo/jpeglib.h"
|
|
#else
|
|
#include "third_party/libjpeg/jpeglib.h"
|
|
#endif
|
|
}
|
|
|
|
namespace gfx {
|
|
|
|
// Encoder/decoder shared stuff ------------------------------------------------
|
|
|
|
namespace {
|
|
|
|
// used to pass error info through the JPEG library
|
|
struct CoderErrorMgr {
|
|
jpeg_error_mgr pub;
|
|
jmp_buf setjmp_buffer;
|
|
};
|
|
|
|
void ErrorExit(jpeg_common_struct* cinfo) {
|
|
CoderErrorMgr *err = reinterpret_cast<CoderErrorMgr*>(cinfo->err);
|
|
|
|
// Return control to the setjmp point.
|
|
longjmp(err->setjmp_buffer, false);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
// This method helps identify at run time which library chromium is using.
|
|
JPEGCodec::LibraryVariant JPEGCodec::JpegLibraryVariant() {
|
|
#if defined(USE_SYSTEM_LIBJPEG)
|
|
return SYSTEM_LIBJPEG;
|
|
#elif defined(USE_LIBJPEG_TURBO)
|
|
return LIBJPEG_TURBO;
|
|
#else
|
|
return IJG_LIBJPEG;
|
|
#endif
|
|
}
|
|
|
|
// Encoder ---------------------------------------------------------------------
|
|
//
|
|
// This code is based on nsJPEGEncoder from Mozilla.
|
|
// Copyright 2005 Google Inc. (Brett Wilson, contributor)
|
|
|
|
namespace {
|
|
|
|
// Initial size for the output buffer in the JpegEncoderState below.
|
|
static const int initial_output_buffer_size = 8192;
|
|
|
|
struct JpegEncoderState {
|
|
explicit JpegEncoderState(std::vector<unsigned char>* o)
|
|
: out(o),
|
|
image_buffer_used(0) {
|
|
}
|
|
|
|
// Output buffer, of which 'image_buffer_used' bytes are actually used (this
|
|
// will often be less than the actual size of the vector because we size it
|
|
// so that libjpeg can write directly into it.
|
|
std::vector<unsigned char>* out;
|
|
|
|
// Number of bytes in the 'out' buffer that are actually used (see above).
|
|
size_t image_buffer_used;
|
|
};
|
|
|
|
// Initializes the JpegEncoderState for encoding, and tells libjpeg about where
|
|
// the output buffer is.
|
|
//
|
|
// From the JPEG library:
|
|
// "Initialize destination. This is called by jpeg_start_compress() before
|
|
// any data is actually written. It must initialize next_output_byte and
|
|
// free_in_buffer. free_in_buffer must be initialized to a positive value."
|
|
void InitDestination(jpeg_compress_struct* cinfo) {
|
|
JpegEncoderState* state = static_cast<JpegEncoderState*>(cinfo->client_data);
|
|
DCHECK(state->image_buffer_used == 0) << "initializing after use";
|
|
|
|
state->out->resize(initial_output_buffer_size);
|
|
state->image_buffer_used = 0;
|
|
|
|
cinfo->dest->next_output_byte = &(*state->out)[0];
|
|
cinfo->dest->free_in_buffer = initial_output_buffer_size;
|
|
}
|
|
|
|
// Resize the buffer that we give to libjpeg and update our and its state.
|
|
//
|
|
// From the JPEG library:
|
|
// "Callback used by libjpeg whenever the buffer has filled (free_in_buffer
|
|
// reaches zero). In typical applications, it should write out the *entire*
|
|
// buffer (use the saved start address and buffer length; ignore the current
|
|
// state of next_output_byte and free_in_buffer). Then reset the pointer &
|
|
// count to the start of the buffer, and return TRUE indicating that the
|
|
// buffer has been dumped. free_in_buffer must be set to a positive value
|
|
// when TRUE is returned. A FALSE return should only be used when I/O
|
|
// suspension is desired (this operating mode is discussed in the next
|
|
// section)."
|
|
boolean EmptyOutputBuffer(jpeg_compress_struct* cinfo) {
|
|
JpegEncoderState* state = static_cast<JpegEncoderState*>(cinfo->client_data);
|
|
|
|
// note the new size, the buffer is full
|
|
state->image_buffer_used = state->out->size();
|
|
|
|
// expand buffer, just double size each time
|
|
state->out->resize(state->out->size() * 2);
|
|
|
|
// tell libjpeg where to write the next data
|
|
cinfo->dest->next_output_byte = &(*state->out)[state->image_buffer_used];
|
|
cinfo->dest->free_in_buffer = state->out->size() - state->image_buffer_used;
|
|
return 1;
|
|
}
|
|
|
|
// Cleans up the JpegEncoderState to prepare for returning in the final form.
|
|
//
|
|
// From the JPEG library:
|
|
// "Terminate destination --- called by jpeg_finish_compress() after all data
|
|
// has been written. In most applications, this must flush any data
|
|
// remaining in the buffer. Use either next_output_byte or free_in_buffer to
|
|
// determine how much data is in the buffer."
|
|
void TermDestination(jpeg_compress_struct* cinfo) {
|
|
JpegEncoderState* state = static_cast<JpegEncoderState*>(cinfo->client_data);
|
|
DCHECK(state->out->size() >= state->image_buffer_used);
|
|
|
|
// update the used byte based on the next byte libjpeg would write to
|
|
state->image_buffer_used = cinfo->dest->next_output_byte - &(*state->out)[0];
|
|
DCHECK(state->image_buffer_used < state->out->size()) <<
|
|
"JPEG library busted, got a bad image buffer size";
|
|
|
|
// update our buffer so that it exactly encompases the desired data
|
|
state->out->resize(state->image_buffer_used);
|
|
}
|
|
|
|
#if !defined(JCS_EXTENSIONS)
|
|
// Converts RGBA to RGB (removing the alpha values) to prepare to send data to
|
|
// libjpeg. This converts one row of data in rgba with the given width in
|
|
// pixels the the given rgb destination buffer (which should have enough space
|
|
// reserved for the final data).
|
|
void StripAlpha(const unsigned char* rgba, int pixel_width, unsigned char* rgb)
|
|
{
|
|
for (int x = 0; x < pixel_width; x++)
|
|
memcpy(&rgb[x * 3], &rgba[x * 4], 3);
|
|
}
|
|
|
|
// Converts BGRA to RGB by reordering the color components and dropping the
|
|
// alpha. This converts one row of data in rgba with the given width in
|
|
// pixels the the given rgb destination buffer (which should have enough space
|
|
// reserved for the final data).
|
|
void BGRAtoRGB(const unsigned char* bgra, int pixel_width, unsigned char* rgb)
|
|
{
|
|
for (int x = 0; x < pixel_width; x++) {
|
|
const unsigned char* pixel_in = &bgra[x * 4];
|
|
unsigned char* pixel_out = &rgb[x * 3];
|
|
pixel_out[0] = pixel_in[2];
|
|
pixel_out[1] = pixel_in[1];
|
|
pixel_out[2] = pixel_in[0];
|
|
}
|
|
}
|
|
#endif // !defined(JCS_EXTENSIONS)
|
|
|
|
// This class destroys the given jpeg_compress object when it goes out of
|
|
// scope. It simplifies the error handling in Encode (and even applies to the
|
|
// success case).
|
|
class CompressDestroyer {
|
|
public:
|
|
CompressDestroyer() : cinfo_(NULL) {
|
|
}
|
|
~CompressDestroyer() {
|
|
DestroyManagedObject();
|
|
}
|
|
void SetManagedObject(jpeg_compress_struct* ci) {
|
|
DestroyManagedObject();
|
|
cinfo_ = ci;
|
|
}
|
|
void DestroyManagedObject() {
|
|
if (cinfo_) {
|
|
jpeg_destroy_compress(cinfo_);
|
|
cinfo_ = NULL;
|
|
}
|
|
}
|
|
private:
|
|
jpeg_compress_struct* cinfo_;
|
|
};
|
|
|
|
} // namespace
|
|
|
|
bool JPEGCodec::Encode(const unsigned char* input, ColorFormat format,
|
|
int w, int h, int row_byte_width,
|
|
int quality, std::vector<unsigned char>* output) {
|
|
jpeg_compress_struct cinfo;
|
|
CompressDestroyer destroyer;
|
|
destroyer.SetManagedObject(&cinfo);
|
|
output->clear();
|
|
#if !defined(JCS_EXTENSIONS)
|
|
unsigned char* row_buffer = NULL;
|
|
#endif
|
|
|
|
// We set up the normal JPEG error routines, then override error_exit.
|
|
// This must be done before the call to create_compress.
|
|
CoderErrorMgr errmgr;
|
|
cinfo.err = jpeg_std_error(&errmgr.pub);
|
|
errmgr.pub.error_exit = ErrorExit;
|
|
|
|
// Establish the setjmp return context for ErrorExit to use.
|
|
if (setjmp(errmgr.setjmp_buffer)) {
|
|
// If we get here, the JPEG code has signaled an error.
|
|
// MSDN notes: "if you intend your code to be portable, do not rely on
|
|
// correct destruction of frame-based objects when executing a nonlocal
|
|
// goto using a call to longjmp." So we delete the CompressDestroyer's
|
|
// object manually instead.
|
|
destroyer.DestroyManagedObject();
|
|
#if !defined(JCS_EXTENSIONS)
|
|
delete[] row_buffer;
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
// The destroyer will destroy() cinfo on exit.
|
|
jpeg_create_compress(&cinfo);
|
|
|
|
cinfo.image_width = w;
|
|
cinfo.image_height = h;
|
|
cinfo.input_components = 3;
|
|
#ifdef JCS_EXTENSIONS
|
|
// Choose an input colorspace and return if it is an unsupported one. Since
|
|
// libjpeg-turbo supports all input formats used by Chromium (i.e. RGB, RGBA,
|
|
// and BGRA), we just map the input parameters to a colorspace used by
|
|
// libjpeg-turbo.
|
|
if (format == FORMAT_RGB) {
|
|
cinfo.input_components = 3;
|
|
cinfo.in_color_space = JCS_RGB;
|
|
} else if (format == FORMAT_RGBA ||
|
|
(format == FORMAT_SkBitmap && SK_R32_SHIFT == 0)) {
|
|
cinfo.input_components = 4;
|
|
cinfo.in_color_space = JCS_EXT_RGBX;
|
|
} else if (format == FORMAT_BGRA ||
|
|
(format == FORMAT_SkBitmap && SK_B32_SHIFT == 0)) {
|
|
cinfo.input_components = 4;
|
|
cinfo.in_color_space = JCS_EXT_BGRX;
|
|
} else {
|
|
// We can exit this function without calling jpeg_destroy_compress() because
|
|
// CompressDestroyer automaticaly calls it.
|
|
NOTREACHED() << "Invalid pixel format";
|
|
return false;
|
|
}
|
|
#else
|
|
cinfo.in_color_space = JCS_RGB;
|
|
#endif
|
|
cinfo.data_precision = 8;
|
|
|
|
jpeg_set_defaults(&cinfo);
|
|
jpeg_set_quality(&cinfo, quality, 1); // quality here is 0-100
|
|
|
|
// set up the destination manager
|
|
jpeg_destination_mgr destmgr;
|
|
destmgr.init_destination = InitDestination;
|
|
destmgr.empty_output_buffer = EmptyOutputBuffer;
|
|
destmgr.term_destination = TermDestination;
|
|
cinfo.dest = &destmgr;
|
|
|
|
JpegEncoderState state(output);
|
|
cinfo.client_data = &state;
|
|
|
|
jpeg_start_compress(&cinfo, 1);
|
|
|
|
// feed it the rows, doing necessary conversions for the color format
|
|
#ifdef JCS_EXTENSIONS
|
|
// This function already returns when the input format is not supported by
|
|
// libjpeg-turbo and needs conversion. Therefore, we just encode lines without
|
|
// conversions.
|
|
while (cinfo.next_scanline < cinfo.image_height) {
|
|
const unsigned char* row = &input[cinfo.next_scanline * row_byte_width];
|
|
jpeg_write_scanlines(&cinfo, const_cast<unsigned char**>(&row), 1);
|
|
}
|
|
#else
|
|
if (format == FORMAT_RGB) {
|
|
// no conversion necessary
|
|
while (cinfo.next_scanline < cinfo.image_height) {
|
|
const unsigned char* row = &input[cinfo.next_scanline * row_byte_width];
|
|
jpeg_write_scanlines(&cinfo, const_cast<unsigned char**>(&row), 1);
|
|
}
|
|
} else {
|
|
// get the correct format converter
|
|
void (*converter)(const unsigned char* in, int w, unsigned char* rgb);
|
|
if (format == FORMAT_RGBA ||
|
|
(format == FORMAT_SkBitmap && SK_R32_SHIFT == 0)) {
|
|
converter = StripAlpha;
|
|
} else if (format == FORMAT_BGRA ||
|
|
(format == FORMAT_SkBitmap && SK_B32_SHIFT == 0)) {
|
|
converter = BGRAtoRGB;
|
|
} else {
|
|
NOTREACHED() << "Invalid pixel format";
|
|
return false;
|
|
}
|
|
|
|
// output row after converting
|
|
row_buffer = new unsigned char[w * 3];
|
|
|
|
while (cinfo.next_scanline < cinfo.image_height) {
|
|
converter(&input[cinfo.next_scanline * row_byte_width], w, row_buffer);
|
|
jpeg_write_scanlines(&cinfo, &row_buffer, 1);
|
|
}
|
|
delete[] row_buffer;
|
|
}
|
|
#endif
|
|
|
|
jpeg_finish_compress(&cinfo);
|
|
return true;
|
|
}
|
|
|
|
// Decoder --------------------------------------------------------------------
|
|
|
|
namespace {
|
|
|
|
struct JpegDecoderState {
|
|
JpegDecoderState(const unsigned char* in, size_t len)
|
|
: input_buffer(in), input_buffer_length(len) {
|
|
}
|
|
|
|
const unsigned char* input_buffer;
|
|
size_t input_buffer_length;
|
|
};
|
|
|
|
// Callback to initialize the source.
|
|
//
|
|
// From the JPEG library:
|
|
// "Initialize source. This is called by jpeg_read_header() before any data is
|
|
// actually read. May leave bytes_in_buffer set to 0 (in which case a
|
|
// fill_input_buffer() call will occur immediately)."
|
|
void InitSource(j_decompress_ptr cinfo) {
|
|
JpegDecoderState* state = static_cast<JpegDecoderState*>(cinfo->client_data);
|
|
cinfo->src->next_input_byte = state->input_buffer;
|
|
cinfo->src->bytes_in_buffer = state->input_buffer_length;
|
|
}
|
|
|
|
// Callback to fill the buffer. Since our buffer already contains all the data,
|
|
// we should never need to provide more data. If libjpeg thinks it needs more
|
|
// data, our input is probably corrupt.
|
|
//
|
|
// From the JPEG library:
|
|
// "This is called whenever bytes_in_buffer has reached zero and more data is
|
|
// wanted. In typical applications, it should read fresh data into the buffer
|
|
// (ignoring the current state of next_input_byte and bytes_in_buffer), reset
|
|
// the pointer & count to the start of the buffer, and return TRUE indicating
|
|
// that the buffer has been reloaded. It is not necessary to fill the buffer
|
|
// entirely, only to obtain at least one more byte. bytes_in_buffer MUST be
|
|
// set to a positive value if TRUE is returned. A FALSE return should only
|
|
// be used when I/O suspension is desired."
|
|
boolean FillInputBuffer(j_decompress_ptr cinfo) {
|
|
return false;
|
|
}
|
|
|
|
// Skip data in the buffer. Since we have all the data at once, this operation
|
|
// is easy. It is not clear if this ever gets called because the JPEG library
|
|
// should be able to do the skip itself (it has all the data).
|
|
//
|
|
// From the JPEG library:
|
|
// "Skip num_bytes worth of data. The buffer pointer and count should be
|
|
// advanced over num_bytes input bytes, refilling the buffer as needed. This
|
|
// is used to skip over a potentially large amount of uninteresting data
|
|
// (such as an APPn marker). In some applications it may be possible to
|
|
// optimize away the reading of the skipped data, but it's not clear that
|
|
// being smart is worth much trouble; large skips are uncommon.
|
|
// bytes_in_buffer may be zero on return. A zero or negative skip count
|
|
// should be treated as a no-op."
|
|
void SkipInputData(j_decompress_ptr cinfo, long num_bytes) {
|
|
if (num_bytes > static_cast<long>(cinfo->src->bytes_in_buffer)) {
|
|
// Since all our data should be in the buffer, trying to skip beyond it
|
|
// means that there is some kind of error or corrupt input data. A 0 for
|
|
// bytes left means it will call FillInputBuffer which will then fail.
|
|
cinfo->src->next_input_byte += cinfo->src->bytes_in_buffer;
|
|
cinfo->src->bytes_in_buffer = 0;
|
|
} else if (num_bytes > 0) {
|
|
cinfo->src->bytes_in_buffer -= static_cast<size_t>(num_bytes);
|
|
cinfo->src->next_input_byte += num_bytes;
|
|
}
|
|
}
|
|
|
|
// Our source doesn't need any cleanup, so this is a NOP.
|
|
//
|
|
// From the JPEG library:
|
|
// "Terminate source --- called by jpeg_finish_decompress() after all data has
|
|
// been read to clean up JPEG source manager. NOT called by jpeg_abort() or
|
|
// jpeg_destroy()."
|
|
void TermSource(j_decompress_ptr cinfo) {
|
|
}
|
|
|
|
#if !defined(JCS_EXTENSIONS)
|
|
// Converts one row of rgb data to rgba data by adding a fully-opaque alpha
|
|
// value.
|
|
void AddAlpha(const unsigned char* rgb, int pixel_width, unsigned char* rgba) {
|
|
for (int x = 0; x < pixel_width; x++) {
|
|
memcpy(&rgba[x * 4], &rgb[x * 3], 3);
|
|
rgba[x * 4 + 3] = 0xff;
|
|
}
|
|
}
|
|
|
|
// Converts one row of RGB data to BGRA by reordering the color components and
|
|
// adding alpha values of 0xff.
|
|
void RGBtoBGRA(const unsigned char* bgra, int pixel_width, unsigned char* rgb)
|
|
{
|
|
for (int x = 0; x < pixel_width; x++) {
|
|
const unsigned char* pixel_in = &bgra[x * 3];
|
|
unsigned char* pixel_out = &rgb[x * 4];
|
|
pixel_out[0] = pixel_in[2];
|
|
pixel_out[1] = pixel_in[1];
|
|
pixel_out[2] = pixel_in[0];
|
|
pixel_out[3] = 0xff;
|
|
}
|
|
}
|
|
#endif // !defined(JCS_EXTENSIONS)
|
|
|
|
// This class destroys the given jpeg_decompress object when it goes out of
|
|
// scope. It simplifies the error handling in Decode (and even applies to the
|
|
// success case).
|
|
class DecompressDestroyer {
|
|
public:
|
|
DecompressDestroyer() : cinfo_(NULL) {
|
|
}
|
|
~DecompressDestroyer() {
|
|
DestroyManagedObject();
|
|
}
|
|
void SetManagedObject(jpeg_decompress_struct* ci) {
|
|
DestroyManagedObject();
|
|
cinfo_ = ci;
|
|
}
|
|
void DestroyManagedObject() {
|
|
if (cinfo_) {
|
|
jpeg_destroy_decompress(cinfo_);
|
|
cinfo_ = NULL;
|
|
}
|
|
}
|
|
private:
|
|
jpeg_decompress_struct* cinfo_;
|
|
};
|
|
|
|
} // namespace
|
|
|
|
bool JPEGCodec::Decode(const unsigned char* input, size_t input_size,
|
|
ColorFormat format, std::vector<unsigned char>* output,
|
|
int* w, int* h) {
|
|
jpeg_decompress_struct cinfo;
|
|
DecompressDestroyer destroyer;
|
|
destroyer.SetManagedObject(&cinfo);
|
|
output->clear();
|
|
|
|
// We set up the normal JPEG error routines, then override error_exit.
|
|
// This must be done before the call to create_decompress.
|
|
CoderErrorMgr errmgr;
|
|
cinfo.err = jpeg_std_error(&errmgr.pub);
|
|
errmgr.pub.error_exit = ErrorExit;
|
|
// Establish the setjmp return context for ErrorExit to use.
|
|
if (setjmp(errmgr.setjmp_buffer)) {
|
|
// If we get here, the JPEG code has signaled an error.
|
|
// See note in JPEGCodec::Encode() for why we need to destroy the cinfo
|
|
// manually here.
|
|
destroyer.DestroyManagedObject();
|
|
return false;
|
|
}
|
|
|
|
// The destroyer will destroy() cinfo on exit. We don't want to set the
|
|
// destroyer's object until cinfo is initialized.
|
|
jpeg_create_decompress(&cinfo);
|
|
|
|
// set up the source manager
|
|
jpeg_source_mgr srcmgr;
|
|
srcmgr.init_source = InitSource;
|
|
srcmgr.fill_input_buffer = FillInputBuffer;
|
|
srcmgr.skip_input_data = SkipInputData;
|
|
srcmgr.resync_to_restart = jpeg_resync_to_restart; // use default routine
|
|
srcmgr.term_source = TermSource;
|
|
cinfo.src = &srcmgr;
|
|
|
|
JpegDecoderState state(input, input_size);
|
|
cinfo.client_data = &state;
|
|
|
|
// fill the file metadata into our buffer
|
|
if (jpeg_read_header(&cinfo, true) != JPEG_HEADER_OK)
|
|
return false;
|
|
|
|
// we want to always get RGB data out
|
|
switch (cinfo.jpeg_color_space) {
|
|
case JCS_GRAYSCALE:
|
|
case JCS_RGB:
|
|
case JCS_YCbCr:
|
|
#ifdef JCS_EXTENSIONS
|
|
// Choose an output colorspace and return if it is an unsupported one.
|
|
// Same as JPEGCodec::Encode(), libjpeg-turbo supports all input formats
|
|
// used by Chromium (i.e. RGB, RGBA, and BGRA) and we just map the input
|
|
// parameters to a colorspace.
|
|
if (format == FORMAT_RGB) {
|
|
cinfo.out_color_space = JCS_RGB;
|
|
cinfo.output_components = 3;
|
|
} else if (format == FORMAT_RGBA ||
|
|
(format == FORMAT_SkBitmap && SK_R32_SHIFT == 0)) {
|
|
cinfo.out_color_space = JCS_EXT_RGBX;
|
|
cinfo.output_components = 4;
|
|
} else if (format == FORMAT_BGRA ||
|
|
(format == FORMAT_SkBitmap && SK_B32_SHIFT == 0)) {
|
|
cinfo.out_color_space = JCS_EXT_BGRX;
|
|
cinfo.output_components = 4;
|
|
} else {
|
|
// We can exit this function without calling jpeg_destroy_decompress()
|
|
// because DecompressDestroyer automaticaly calls it.
|
|
NOTREACHED() << "Invalid pixel format";
|
|
return false;
|
|
}
|
|
#else
|
|
cinfo.out_color_space = JCS_RGB;
|
|
#endif
|
|
break;
|
|
case JCS_CMYK:
|
|
case JCS_YCCK:
|
|
default:
|
|
// Mozilla errors out on these color spaces, so I presume that the jpeg
|
|
// library can't do automatic color space conversion for them. We don't
|
|
// care about these anyway.
|
|
return false;
|
|
}
|
|
#ifndef JCS_EXTENSIONS
|
|
cinfo.output_components = 3;
|
|
#endif
|
|
|
|
jpeg_calc_output_dimensions(&cinfo);
|
|
*w = cinfo.output_width;
|
|
*h = cinfo.output_height;
|
|
|
|
jpeg_start_decompress(&cinfo);
|
|
|
|
// FIXME(brettw) we may want to allow the capability for callers to request
|
|
// how to align row lengths as we do for the compressor.
|
|
int row_read_stride = cinfo.output_width * cinfo.output_components;
|
|
|
|
#ifdef JCS_EXTENSIONS
|
|
// Create memory for a decoded image and write decoded lines to the memory
|
|
// without conversions same as JPEGCodec::Encode().
|
|
int row_write_stride = row_read_stride;
|
|
output->resize(row_write_stride * cinfo.output_height);
|
|
|
|
for (int row = 0; row < static_cast<int>(cinfo.output_height); row++) {
|
|
unsigned char* rowptr = &(*output)[row * row_write_stride];
|
|
if (!jpeg_read_scanlines(&cinfo, &rowptr, 1))
|
|
return false;
|
|
}
|
|
#else
|
|
if (format == FORMAT_RGB) {
|
|
// easy case, row needs no conversion
|
|
int row_write_stride = row_read_stride;
|
|
output->resize(row_write_stride * cinfo.output_height);
|
|
|
|
for (int row = 0; row < static_cast<int>(cinfo.output_height); row++) {
|
|
unsigned char* rowptr = &(*output)[row * row_write_stride];
|
|
if (!jpeg_read_scanlines(&cinfo, &rowptr, 1))
|
|
return false;
|
|
}
|
|
} else {
|
|
// Rows need conversion to output format: read into a temporary buffer and
|
|
// expand to the final one. Performance: we could avoid the extra
|
|
// allocation by doing the expansion in-place.
|
|
int row_write_stride;
|
|
void (*converter)(const unsigned char* rgb, int w, unsigned char* out);
|
|
if (format == FORMAT_RGBA ||
|
|
(format == FORMAT_SkBitmap && SK_R32_SHIFT == 0)) {
|
|
row_write_stride = cinfo.output_width * 4;
|
|
converter = AddAlpha;
|
|
} else if (format == FORMAT_BGRA ||
|
|
(format == FORMAT_SkBitmap && SK_B32_SHIFT == 0)) {
|
|
row_write_stride = cinfo.output_width * 4;
|
|
converter = RGBtoBGRA;
|
|
} else {
|
|
NOTREACHED() << "Invalid pixel format";
|
|
jpeg_destroy_decompress(&cinfo);
|
|
return false;
|
|
}
|
|
|
|
output->resize(row_write_stride * cinfo.output_height);
|
|
|
|
scoped_ptr<unsigned char[]> row_data(new unsigned char[row_read_stride]);
|
|
unsigned char* rowptr = row_data.get();
|
|
for (int row = 0; row < static_cast<int>(cinfo.output_height); row++) {
|
|
if (!jpeg_read_scanlines(&cinfo, &rowptr, 1))
|
|
return false;
|
|
converter(rowptr, *w, &(*output)[row * row_write_stride]);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
jpeg_finish_decompress(&cinfo);
|
|
jpeg_destroy_decompress(&cinfo);
|
|
return true;
|
|
}
|
|
|
|
// static
|
|
SkBitmap* JPEGCodec::Decode(const unsigned char* input, size_t input_size) {
|
|
int w, h;
|
|
std::vector<unsigned char> data_vector;
|
|
if (!Decode(input, input_size, FORMAT_SkBitmap, &data_vector, &w, &h))
|
|
return NULL;
|
|
|
|
// Skia only handles 32 bit images.
|
|
int data_length = w * h * 4;
|
|
|
|
SkBitmap* bitmap = new SkBitmap();
|
|
bitmap->allocN32Pixels(w, h);
|
|
memcpy(bitmap->getAddr32(0, 0), &data_vector[0], data_length);
|
|
|
|
return bitmap;
|
|
}
|
|
|
|
} // namespace gfx
|