blob: dd43b3c0dd34bc709c4bab684d55578315fedf60 [file] [log] [blame]
/*
* Copyright 2009 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, Satyanarayana Manyam
#ifndef PAGESPEED_KERNEL_IMAGE_PNG_OPTIMIZER_H_
#define PAGESPEED_KERNEL_IMAGE_PNG_OPTIMIZER_H_
// Note: we should not include setjmp.h here, since libpng 1.2 headers
// include it themselves, and get unhappy if we do it ourselves.
extern "C" {
#ifdef USE_SYSTEM_LIBPNG
#include "png.h" // NOLINT
#else
#include "third_party/libpng/src/png.h"
#endif
} // extern "C"
#include <setjmp.h>
#include <cstddef>
#include "third_party/optipng/src/opngreduc/opngreduc.h"
#include "pagespeed/kernel/base/basictypes.h"
#include "pagespeed/kernel/base/scoped_ptr.h"
#include "pagespeed/kernel/base/string.h"
#include "pagespeed/kernel/image/image_util.h"
#include "pagespeed/kernel/image/scanline_interface.h"
#include "pagespeed/kernel/image/scanline_status.h"
namespace net_instaweb {
class MessageHandler;
}
namespace pagespeed {
namespace image_compression {
using net_instaweb::MessageHandler;
class ScanlineStreamInput;
struct PngCompressParams {
PngCompressParams(int level, int strategy, bool is_progressive);
PngCompressParams(bool try_best_compression, bool is_progressive);
// Indicates what png filter type to be used while compressing the image.
// Valid values for this are
// PNG_FILTER_NONE
// PNG_FILTER_SUB
// PNG_FILTER_UP
// PNG_FILTER_AVG
// PNG_FILTER_PAETH
// PNG_ALL_FILTERS
int filter_level;
// Indicates which compression strategy to use while compressing the image.
// Valid values for this are
// Z_FILTERED
// Z_HUFFMAN_ONLY
// Z_RLE
// Z_FIXED
// Z_DEFAULT_STRATEGY
int compression_strategy;
// Indicates whether to search for the smallest output by using Opti-PNG and
// multiple runs of compression. This mode will use more computation.
bool try_best_compression;
// Indicates whether to encode the image in progressive / interlacing format.
bool is_progressive;
};
// Helper that manages the lifetime of the png_ptr and info_ptr.
class ScopedPngStruct {
public:
enum Type {
READ,
WRITE
};
ScopedPngStruct(Type type, MessageHandler* handler);
~ScopedPngStruct();
bool valid() const { return png_ptr_ != NULL && info_ptr_ != NULL; }
// This will only return false as a result of a longjmp due to an
// unhandled libpng error.
bool reset();
png_structp png_ptr() const { return png_ptr_; }
png_infop info_ptr() const { return info_ptr_; }
private:
png_structp png_ptr_;
png_infop info_ptr_;
Type type_;
MessageHandler* message_handler_;
};
// Helper class that provides an API to read a PNG image from some
// source.
class PngReaderInterface {
public:
PngReaderInterface();
virtual ~PngReaderInterface();
// Parse the contents of body, convert to a PNG, and populate the
// PNG structures with the PNG representation. If 'require_opaque'
// is true, returns an image without an alpha channel if the
// original image has no transparent pixels, and fails
// otherwise. Returns true on success, false on failure.
virtual bool ReadPng(const GoogleString& body,
png_structp png_ptr,
png_infop info_ptr,
int transforms,
bool require_opaque) const = 0;
// Parse the contents of body, convert to a PNG, and populate the
// PNG structures with the PNG representation. Returns true on
// success, false on failure.
bool ReadPng(const GoogleString& body,
png_structp png_ptr,
png_infop info_ptr,
int transforms) const {
return ReadPng(body, png_ptr, info_ptr, transforms, false);
}
// Get just the attributes of the given image. out_bit_depth is the
// number of bits per channel. out_color_type is one of the
// PNG_COLOR_TYPE_* declared in png.h.
// TODO(bmcquade): consider merging this with ImageAttributes.
virtual bool GetAttributes(const GoogleString& body,
int* out_width,
int* out_height,
int* out_bit_depth,
int* out_color_type) const = 0;
// Get the background color, in the form of 8-bit RGB triplets. Note
// that if the underlying image uses a bit_depth other than 8, the
// background color will be scaled to 8-bits per channel.
static bool GetBackgroundColor(
png_structp png_ptr, png_infop info_ptr,
unsigned char *red, unsigned char* green, unsigned char* blue,
MessageHandler* handler);
// Returns true if the alpha channel is actually a opaque. Returns
// false otherwise. It is an error to call this method for an image
// that does not have an alpha channel.
static bool IsAlphaChannelOpaque(png_structp png_ptr, png_infop info_ptr,
MessageHandler* handler);
private:
DISALLOW_COPY_AND_ASSIGN(PngReaderInterface);
};
// Reader for PNG-encoded data.
// This is sample code on how someone can use the scanline reader
// interface.
// bool func() {
// if (setjmp(*GetJmpBuf())) {
// return false;
// }
//
// InitializeRead(...)
// while (HasMoreScanlines()) {
// Scanline line;
// ReadNextScanline(line);
// ....
// ....
// }
// }
class PngScanlineReader : public ScanlineReaderInterface {
public:
explicit PngScanlineReader(MessageHandler* handler);
virtual ~PngScanlineReader();
jmp_buf* GetJmpBuf();
// This will only return false as a result of a longjmp due to an
// unhandled libpng error.
virtual bool Reset();
// Initializes the read structures with the given input.
bool InitializeRead(const PngReaderInterface& reader, const GoogleString& in);
bool InitializeRead(const PngReaderInterface& reader, const GoogleString& in,
bool* is_opaque);
virtual size_t GetBytesPerScanline();
virtual bool HasMoreScanLines();
virtual ScanlineStatus ReadNextScanlineWithStatus(void** out_scanline_bytes);
virtual size_t GetImageHeight();
virtual size_t GetImageWidth();
virtual PixelFormat GetPixelFormat();
virtual bool IsProgressive();
void set_transform(int transform);
void set_require_opaque(bool require_opaque);
int GetColorType();
bool GetBackgroundColor(
unsigned char* red, unsigned char* green, unsigned char* blue);
// This is a no-op and should not be called.
virtual ScanlineStatus InitializeWithStatus(const void* image_buffer,
size_t buffer_length);
private:
ScopedPngStruct read_;
size_t current_scanline_;
int transform_;
bool require_opaque_;
MessageHandler* message_handler_;
DISALLOW_COPY_AND_ASSIGN(PngScanlineReader);
};
class PngOptimizer {
public:
static bool OptimizePng(const PngReaderInterface& reader,
const GoogleString& in,
GoogleString* out,
MessageHandler* handler);
static bool OptimizePngBestCompression(const PngReaderInterface& reader,
const GoogleString& in,
GoogleString* out,
MessageHandler* handler);
static bool CopyPngStructs(const ScopedPngStruct& from, ScopedPngStruct* to);
private:
explicit PngOptimizer(MessageHandler* handler);
~PngOptimizer();
// Take the given input and losslessly compress it by removing
// all unnecessary chunks, and by choosing an optimal PNG encoding.
// @return true on success, false on failure.
bool CreateOptimizedPng(const PngReaderInterface& reader,
const GoogleString& in,
GoogleString* out,
MessageHandler* handler);
// Turn on best compression. Requires additional CPU but produces
// smaller files.
void EnableBestCompression() { best_compression_ = true; }
bool WritePng(ScopedPngStruct* write, GoogleString* buffer);
bool CopyReadToWrite();
bool CreateBestOptimizedPngForParams(const PngCompressParams* param_list,
size_t param_list_size,
GoogleString* out);
bool CreateOptimizedPngWithParams(ScopedPngStruct* write,
const PngCompressParams& params,
GoogleString* out);
ScopedPngStruct read_;
ScopedPngStruct write_;
bool best_compression_;
MessageHandler* message_handler_;
DISALLOW_COPY_AND_ASSIGN(PngOptimizer);
};
// Reader for PNG-encoded data.
class PngReader : public PngReaderInterface {
public:
explicit PngReader(MessageHandler* handler);
virtual ~PngReader();
virtual bool ReadPng(const GoogleString& body,
png_structp png_ptr,
png_infop info_ptr,
int transforms,
bool require_opaque) const;
virtual bool GetAttributes(const GoogleString& body,
int* out_width,
int* out_height,
int* out_bit_depth,
int* out_color_type) const;
private:
MessageHandler* message_handler_;
DISALLOW_COPY_AND_ASSIGN(PngReader);
};
// Class PngScanlineReaderRaw decodes PNG images and outputs the raw pixel data,
// image size, pixel type, etc. The class accepts all formats supported by
// libpng. The output is Gray_8, RGB_888, or RGBA_8888. The following
// transformations are used:
// - Image with depth other than 8 bits/pixel is expanded or stripped to
// 8 bits/pixel.
// - Paletted image is converted to RGB or RGBA depending on whether
// transparency is specified.
// - Gray_Alpha is converted to RGBA.
//
// Note: The input image stream must be valid throughout the life of the
// object. In other words, the image_buffer input you set to the Initialize()
// method cannot be changed until your last call to the ReadNextScanline()
// method.
//
class PngScanlineReaderRaw : public ScanlineReaderInterface {
public:
explicit PngScanlineReaderRaw(MessageHandler* handler);
virtual ~PngScanlineReaderRaw();
// This will only return false as a result of a longjmp due to an
// unhandled libpng error.
virtual bool Reset();
// Initialize the reader with the given image stream. Note that image_buffer
// must remain unchanged until the last call to ReadNextScanline().
virtual ScanlineStatus InitializeWithStatus(const void* image_buffer,
size_t buffer_length);
// Return the next row of pixels. For non-progressive PNG,
// ReadNextScanlineWithStatus will decode one row of pixels each
// time when it is called, but for progressive PNG,
// ReadNextScanlineWithStatus will decode the entire image at the
// first time when it is called.
virtual ScanlineStatus ReadNextScanlineWithStatus(void** out_scanline_bytes);
// Return the number of bytes in a row (without padding).
virtual size_t GetBytesPerScanline() { return bytes_per_row_; }
virtual bool HasMoreScanLines() { return (row_ < height_); }
virtual PixelFormat GetPixelFormat() { return pixel_format_; }
virtual size_t GetImageHeight() { return height_; }
virtual size_t GetImageWidth() { return width_; }
virtual bool IsProgressive() { return is_progressive_; }
private:
PixelFormat pixel_format_;
bool is_progressive_;
size_t height_;
size_t width_;
size_t bytes_per_row_;
size_t row_;
bool was_initialized_;
net_instaweb::scoped_array<png_byte> image_buffer_;
net_instaweb::scoped_array<png_bytep> row_pointers_;
net_instaweb::scoped_ptr<ScopedPngStruct> png_struct_;
// png_input_ stores a pointer to the input image stream. It also keeps
// tracking the length of data that libpng has read. It is initialized
// in Initialize() and is updated in ReadNextScanline().
net_instaweb::scoped_ptr<ScanlineStreamInput> png_input_;
MessageHandler* message_handler_;
DISALLOW_COPY_AND_ASSIGN(PngScanlineReaderRaw);
};
// Class PngScanlineWriter writes a PNG image. It supports Gray_8, RGB_888,
// and RGBA_8888 formats.
class PngScanlineWriter : public ScanlineWriterInterface {
public:
explicit PngScanlineWriter(MessageHandler* handler);
virtual ~PngScanlineWriter();
// Initialize the basic parameters for writing the image. Size of the image
// must be 1-by-1 or larger.
virtual ScanlineStatus InitWithStatus(const size_t width, const size_t height,
PixelFormat pixel_format);
// Initialize additional parameters for writing the image using
// 'params', which should be a PngCompressParams*. You can set
// 'params' to NULL to use the default compression configuration.
virtual ScanlineStatus InitializeWriteWithStatus(const void* params,
GoogleString* png_image);
// Write a scanline with the data provided. Return false in case of error.
virtual ScanlineStatus WriteNextScanlineWithStatus(
const void *scanline_bytes);
// Finalize write structure once all scanlines are written.
// If FinalizeWriter() is called before all of the scanlines have been
// written, the object will be reset to the initial state.
virtual ScanlineStatus FinalizeWriteWithStatus();
private:
// Reset the object to the usable state.
bool Reset();
// Validate the input parameters.
bool Validate(const PngCompressParams* params,
GoogleString* png_image);
bool DoBestCompression();
private:
size_t width_;
size_t height_;
size_t bytes_per_row_;
size_t row_;
PixelFormat pixel_format_;
net_instaweb::scoped_ptr<ScopedPngStruct> png_struct_;
bool was_initialized_;
bool try_best_compression_;
net_instaweb::scoped_array<unsigned char> pixel_buffer_;
MessageHandler* message_handler_;
DISALLOW_COPY_AND_ASSIGN(PngScanlineWriter);
};
} // namespace image_compression
} // namespace pagespeed
#endif // PAGESPEED_KERNEL_IMAGE_PNG_OPTIMIZER_H_