blob: b7fd3a78b1655d02beefa3c5ec2311d3bc6a48fc [file] [log] [blame]
/*
* Copyright 2013 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: Huibao Lin
#include "pagespeed/kernel/image/read_image.h"
#include <setjmp.h>
#include <stdlib.h>
#include "pagespeed/kernel/base/message_handler.h"
#include "pagespeed/kernel/base/scoped_ptr.h"
#include "pagespeed/kernel/base/string.h"
#include "pagespeed/kernel/image/frame_interface_optimizer.h"
#include "pagespeed/kernel/image/gif_reader.h"
#include "pagespeed/kernel/image/image_frame_interface.h"
#include "pagespeed/kernel/image/jpeg_optimizer.h"
#include "pagespeed/kernel/image/jpeg_reader.h"
#include "pagespeed/kernel/image/png_optimizer.h"
#include "pagespeed/kernel/image/scanline_interface.h"
#include "pagespeed/kernel/image/scanline_interface_frame_adapter.h"
#include "pagespeed/kernel/image/scanline_utils.h"
#include "pagespeed/kernel/image/webp_optimizer.h"
namespace pagespeed {
namespace image_compression {
using net_instaweb::MessageHandler;
////////// Scanline API
// Forward declaration
MultipleFrameReader* InstantiateImageFrameReader(
ImageFormat image_type,
MessageHandler* handler,
ScanlineStatus* status);
// Instantiates an uninitialized scanline image reader.
ScanlineReaderInterface* InstantiateScanlineReader(
ImageFormat image_type,
MessageHandler* handler,
ScanlineStatus* status) {
ScanlineReaderInterface* reader = NULL;
const char* which = NULL;
*status = ScanlineStatus(SCANLINE_STATUS_SUCCESS);
switch (image_type) {
case IMAGE_PNG:
reader = new PngScanlineReaderRaw(handler);
which = "PngScanlineReaderRaw";
break;
case IMAGE_JPEG:
reader = new JpegScanlineReader(handler);
which = "JpegScanlineReader";
break;
case IMAGE_WEBP:
reader = new WebpScanlineReader(handler);
which = "WebpScanlineReader";
break;
case IMAGE_GIF: {
which = "FrameToScanlineReaderAdapter(GifFrameReader)";
net_instaweb::scoped_ptr<MultipleFrameReader> mf_reader(
InstantiateImageFrameReader(image_type, handler, status));
if (!mf_reader->set_quirks_mode(QUIRKS_CHROME, status)) {
return NULL;
}
reader =
new FrameToScanlineReaderAdapter(
new MultipleFramePaddingReader(mf_reader.release()));
break;
}
case IMAGE_UNKNOWN:
break;
// No default so compiler will complain if any enum is not processed.
}
if (which == NULL) {
*status = PS_LOGGED_STATUS(PS_LOG_DFATAL, handler,
SCANLINE_STATUS_UNSUPPORTED_FORMAT,
SCANLINE_UTIL,
"invalid image type for reader: %d",
image_type);
} else if (reader == NULL) {
*status = PS_LOGGED_STATUS(PS_LOG_ERROR, handler,
SCANLINE_STATUS_MEMORY_ERROR,
SCANLINE_UTIL,
"failed to allocate %s", which);
}
return reader;
}
// Returns an initialized scanline image reader.
ScanlineReaderInterface* CreateScanlineReader(
ImageFormat image_type,
const void* image_buffer,
size_t buffer_length,
MessageHandler* handler,
ScanlineStatus* status) {
net_instaweb::scoped_ptr<ScanlineReaderInterface> reader(
InstantiateScanlineReader(image_type, handler, status));
if (status->Success()) {
*status = reader->InitializeWithStatus(image_buffer, buffer_length);
}
return status->Success() ? reader.release() : NULL;
}
// Forward declaration.
MultipleFrameWriter* InstantiateImageFrameWriter(
ImageFormat image_type,
MessageHandler* handler,
ScanlineStatus* status);
// Instantiates an uninitialized scanline image writer.
ScanlineWriterInterface* InstantiateScanlineWriter(
ImageFormat image_type,
MessageHandler* handler,
ScanlineStatus* status) {
ScanlineWriterInterface* writer = NULL;
const char* which = NULL;
*status = ScanlineStatus(SCANLINE_STATUS_SUCCESS);
switch (image_type) {
case pagespeed::image_compression::IMAGE_JPEG:
{
net_instaweb::scoped_ptr<JpegScanlineWriter> jpeg_writer(
new JpegScanlineWriter(handler));
if (jpeg_writer != NULL) {
// TODO(huibao): Set up error handling inside JpegScanlineWriter.
// Remove 'setjmp' from the clients and remove the 'SetJmpBufEnv'
// method.
jmp_buf env;
if (setjmp(env)) {
// This code is run only when libjpeg hit an error, and
// called longjmp(env). Note that this only works for as
// long as this stack frame is valid.
jpeg_writer->AbortWrite();
return NULL;
}
jpeg_writer->SetJmpBufEnv(&env);
writer = jpeg_writer.release();
}
which = "JpegScanlineWriter";
}
break;
case pagespeed::image_compression::IMAGE_PNG:
writer = new PngScanlineWriter(handler);
which = "PngScanlineWriter";
break;
case pagespeed::image_compression::IMAGE_WEBP:
which = "FrameToScanlineWriterAdapter(WebpFrameWriter)";
writer = new FrameToScanlineWriterAdapter(
InstantiateImageFrameWriter(image_type, handler, status));
break;
case IMAGE_GIF:
// This library does not implement a GIF writer; intentional
// fall-through.
case IMAGE_UNKNOWN:
break;
// No default so compiler will complain if any enum is not processed.
}
if (which == NULL) {
*status = PS_LOGGED_STATUS(PS_LOG_DFATAL, handler,
SCANLINE_STATUS_UNSUPPORTED_FORMAT,
SCANLINE_UTIL,
"invalid image type for writer: %d",
image_type);
} else if (writer == NULL) {
*status = PS_LOGGED_STATUS(PS_LOG_ERROR, handler,
SCANLINE_STATUS_MEMORY_ERROR,
SCANLINE_UTIL,
"failed to allocate %s", which);
}
return writer;
}
// Returns an initialized scanline image writer.
ScanlineWriterInterface* CreateScanlineWriter(
ImageFormat image_type,
PixelFormat pixel_format,
size_t width,
size_t height,
const void* config,
GoogleString* image_data,
MessageHandler* handler,
ScanlineStatus* status) {
net_instaweb::scoped_ptr<ScanlineWriterInterface> writer(
InstantiateScanlineWriter(image_type, handler, status));
if (status->Success()) {
*status = writer->InitWithStatus(width, height, pixel_format);
}
if (status->Success()) {
*status = writer->InitializeWriteWithStatus(config, image_data);
}
return status->Success() ? writer.release() : NULL;
}
////////// ImageFrame API
// Instantiates an uninitialized image frame reader.
MultipleFrameReader* InstantiateImageFrameReader(
ImageFormat image_type,
MessageHandler* handler,
ScanlineStatus* status) {
MultipleFrameReader* reader = NULL;
*status = ScanlineStatus(SCANLINE_STATUS_SUCCESS);
if (image_type == IMAGE_GIF) {
// Native ImageFrame implementation
reader = new GifFrameReader(handler);
if (reader == NULL) {
*status = PS_LOGGED_STATUS(
PS_LOG_ERROR, handler,
SCANLINE_STATUS_MEMORY_ERROR,
SCANLINE_UTIL,
"failed to allocate GifFrameReader");
}
} else {
// Image formats for which we do not have an ImageFrame
// implementation result in a wrapper around the corresponding
// Scanline object.
net_instaweb::scoped_ptr<ScanlineReaderInterface> scanline_reader(
InstantiateScanlineReader(image_type, handler, status));
if (status->Success()) {
reader = new ScanlineToFrameReaderAdapter(
scanline_reader.release(), handler);
if (reader == NULL) {
*status = PS_LOGGED_STATUS(
PS_LOG_ERROR, handler,
SCANLINE_STATUS_MEMORY_ERROR,
SCANLINE_UTIL,
"failed to allocate ScanlineToFrameReaderAdapter");
}
}
}
return reader;
}
// Returns an initialized image frame reader.
MultipleFrameReader* CreateImageFrameReader(
ImageFormat image_type,
const void* image_buffer,
size_t buffer_length,
QuirksMode quirks_mode,
MessageHandler* handler,
ScanlineStatus* status) {
net_instaweb::scoped_ptr<MultipleFrameReader> reader(
InstantiateImageFrameReader(image_type, handler, status));
return (status->Success() &&
reader->set_quirks_mode(quirks_mode, status) &&
reader->Initialize(image_buffer, buffer_length, status)) ?
reader.release() : NULL;
}
// Instantiates an uninitialized image frame writer.
MultipleFrameWriter* InstantiateImageFrameWriter(
ImageFormat image_type,
MessageHandler* handler,
ScanlineStatus* status) {
MultipleFrameWriter* allocated_writer = NULL;
*status = ScanlineStatus(SCANLINE_STATUS_SUCCESS);
if (image_type == IMAGE_WEBP) {
// Native ImageFrame implementation
allocated_writer = new WebpFrameWriter(handler);
if (allocated_writer == NULL) {
*status = PS_LOGGED_STATUS(
PS_LOG_ERROR, handler,
SCANLINE_STATUS_MEMORY_ERROR,
SCANLINE_UTIL,
"failed to allocate WebpFrameReader");
}
} else {
// Image formats for which we do not have an ImageFrame
// implementation result in a wrapper around the corresponding
// Scanline object.
net_instaweb::scoped_ptr<ScanlineWriterInterface> scanline_writer(
InstantiateScanlineWriter(image_type, handler, status));
if (status->Success()) {
allocated_writer = new ScanlineToFrameWriterAdapter(
scanline_writer.release(), handler);
if (allocated_writer == NULL) {
*status = PS_LOGGED_STATUS(
PS_LOG_ERROR, handler,
SCANLINE_STATUS_MEMORY_ERROR,
SCANLINE_UTIL,
"failed to allocate ScanlineToFrameWriterAdapter");
}
}
}
return allocated_writer;
}
// Returns an initialized image frame writer.
MultipleFrameWriter* CreateImageFrameWriter(
ImageFormat image_type,
const void* config,
GoogleString* image_data,
MessageHandler* handler,
ScanlineStatus* status) {
net_instaweb::scoped_ptr<MultipleFrameWriter> writer(
InstantiateImageFrameWriter(image_type, handler, status));
return (status->Success() &&
writer->Initialize(config, image_data, status)) ?
writer.release() : NULL;
}
////////// Utilities
bool ReadImage(ImageFormat image_type,
const void* image_buffer,
size_t buffer_length,
void** pixels,
PixelFormat* pixel_format,
size_t* width,
size_t* height,
size_t* stride,
MessageHandler* handler) {
// Instantiate and initialize the reader based on image type.
net_instaweb::scoped_ptr<ScanlineReaderInterface> reader;
reader.reset(CreateScanlineReader(image_type, image_buffer, buffer_length,
handler));
if (reader.get() == NULL) {
return false;
}
// The following information is available after the reader is initialized.
// Copy them to the outputs if they are requested.
if (pixel_format != NULL) {
*pixel_format = reader->GetPixelFormat();
}
if (width != NULL) {
*width = reader->GetImageWidth();
}
if (height != NULL) {
*height = reader->GetImageHeight();
}
// Round up stride to a multiplier of 4.
size_t bytes_per_row4 = (((reader->GetBytesPerScanline() + 3) >> 2) << 2);
if (stride != NULL) {
*stride = bytes_per_row4;
}
// Decode the image data (pixels) if it has been requested.
if (pixels == NULL) {
return true;
}
*pixels = NULL;
const size_t data_length = reader->GetImageHeight() * bytes_per_row4;
unsigned char* image_data = static_cast<unsigned char*>(malloc(data_length));
if (image_data == NULL) {
return false;
}
unsigned char* row_data = image_data;
unsigned char* scanline = NULL;
while (reader->HasMoreScanLines()) {
if (!reader->ReadNextScanline(reinterpret_cast<void**>(&scanline))) {
free(image_data);
return false;
}
memcpy(row_data, scanline, reader->GetBytesPerScanline());
row_data += bytes_per_row4;
}
*pixels = static_cast<void*>(image_data);
return true;
}
} // namespace image_compression
} // namespace pagespeed