blob: de6ae37b0518595b874d10f3383202d82554545c [file] [log] [blame]
/*
* Copyright 2011 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: abliss@google.com (Adam Bliss)
#include "net/instaweb/spriter/libpng_image_library.h"
#include <errno.h>
#include "pagespeed/kernel/base/string.h"
#include "pagespeed/kernel/base/string_util.h"
namespace {
// We always output at RGBA with 8 bits per channel.
const int BYTES_PER_PIXEL = 4;
// Largest PNG dimension (width or height) that we will attempt to process.
const int MAX_PNG_DIMENSION = 4096;
} // namespace
namespace net_instaweb {
namespace spriter {
LibpngImageLibrary::Image::Image(ImageLibraryInterface* lib,
png_structp png_struct, png_infop png_info,
png_bytep* rows)
: ImageLibraryInterface::Image(lib),
png_struct_(png_struct), png_info_(png_info), rows_(rows) {
}
LibpngImageLibrary::Image::~Image() {
int width, height;
GetDimensions(&width, &height);
png_destroy_read_struct(&png_struct_, &png_info_, NULL);
for (int i = height - 1; i >= 0; i--) {
delete[] rows_[i];
}
delete[] rows_;
}
bool LibpngImageLibrary::Image::GetDimensions(int* out_width, int* out_height)
const {
*out_width = png_get_image_width(png_struct_, png_info_);
*out_height = png_get_image_height(png_struct_, png_info_);
return true;
}
const png_bytep* LibpngImageLibrary::Image::Rows() const {
return rows_;
}
bool LibpngImageLibrary::Canvas::DrawImage(
const ImageLibraryInterface::Image* image, int x_start, int y_start) {
int w, h;
image->GetDimensions(&w, &h);
if ((w <= 0) || (h <= 0)) {
return true;
}
const png_bytep* rows = static_cast<const Image*>(image)->Rows();
int their_y = h - 1;
int my_y = y_start + h - 1;
int x_start_byte = x_start * BYTES_PER_PIXEL;
int num_bytes = w * BYTES_PER_PIXEL;
CHECK(x_start >= 0);
CHECK(y_start >= 0);
CHECK(x_start + w <= width_);
CHECK(y_start + h <= height_);
while (their_y >= 0) {
memcpy(rows_[my_y] + x_start_byte, rows[their_y], num_bytes);
their_y--;
my_y--;
}
return true;
}
bool LibpngImageLibrary::Canvas::WriteToFile(const FilePath& filename,
ImageFormat format) {
GoogleString write_path = StrCat(base_out_path_, filename);
FILE* file = fopen(write_path.c_str(), "wb");
if (file == NULL) {
delegate_->OnError(
StrCat("Writing image " , write_path, ": ", strerror(errno)));
fclose(file);
return false;
}
png_structp png_struct = png_create_write_struct(
PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
if (png_struct == NULL) {
delegate_->OnError(
StrCat("Writing image " , write_path, ": cannot create png struct"));
fclose(file);
return false;
}
png_infop png_info = png_create_info_struct(png_struct);
if (png_info == NULL) {
delegate_->OnError(
StrCat("Writing image " , write_path, ": cannot create png info"));
png_destroy_write_struct(&png_struct, &png_info);
fclose(file);
return false;
}
if (setjmp(png_jmpbuf(png_struct))) {
delegate_->OnError(
StrCat("Writing image " , write_path, ": cannot initialize libpng"));
png_destroy_write_struct(&png_struct, &png_info);
fclose(file);
return false;
}
png_init_io(png_struct, file);
if (setjmp(png_jmpbuf(png_struct))) {
delegate_->OnError(
StrCat("Writing image " , write_path, ": cannot write header"));
png_destroy_write_struct(&png_struct, &png_info);
fclose(file);
return false;
}
const png_byte bit_depth = 8;
const png_byte color_type = PNG_COLOR_TYPE_RGB_ALPHA;
png_set_IHDR(png_struct, png_info, width_, height_,
bit_depth, color_type, PNG_INTERLACE_NONE,
PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
png_write_info(png_struct, png_info);
if (setjmp(png_jmpbuf(png_struct))) {
delegate_->OnError(
StrCat("Writing image " , write_path, ": cannot write body"));
png_destroy_write_struct(&png_struct, &png_info);
fclose(file);
return false;
}
png_write_image(png_struct, rows_);
if (setjmp(png_jmpbuf(png_struct))) {
delegate_->OnError(
StrCat("Writing image " , write_path, ": cannot write end"));
fclose(file);
return false;
}
png_write_end(png_struct, NULL);
png_destroy_write_struct(&png_struct, &png_info);
if (fclose(file) != 0) {
delegate_->OnError(
StrCat("Writing image " , write_path, ": ", strerror(errno)));
return false;
}
return true;
}
LibpngImageLibrary::Canvas::Canvas(ImageLibraryInterface* lib,
const Delegate* d,
const GoogleString& base_out_path,
int width, int height)
: ImageLibraryInterface::Canvas(lib), delegate_(d),
base_out_path_(base_out_path), width_(width), height_(height) {
rows_ = new png_bytep[height];
for (int i = height - 1; i >= 0; i--) {
rows_[i] = new png_byte[width * BYTES_PER_PIXEL];
memset(rows_[i], 0, width * BYTES_PER_PIXEL);
}
}
LibpngImageLibrary::Canvas::~Canvas() {
for (int i = height_ - 1; i >= 0; i--) {
delete[] rows_[i];
}
delete[] rows_;
}
ImageLibraryInterface::Canvas* LibpngImageLibrary::CreateCanvas(int width,
int height) {
return new Canvas(this, delegate(), base_output_path(), width, height);
}
// Read an image from disk. Return NULL (after calling delegate
// method) on error. Caller owns the returned pointer.
ImageLibraryInterface::Image* LibpngImageLibrary::ReadFromFile(
const FilePath& filename) {
GoogleString path = StrCat(base_input_path(), filename);
FILE* file = fopen(path.c_str(), "rb");
if (file == NULL) {
delegate()->OnError(StrCat("Reading image " , path, ": ", strerror(errno)));
return NULL;
}
png_byte header[8];
if (fread(header, 1, 8, file) != 8) {
delegate()->OnError(StrCat("Image " , path, " has no header."));
fclose(file);
return NULL;
}
if (png_sig_cmp(header, 0, 8) != 0) {
delegate()->OnError(StrCat("Image " , path, " not PNG."));
fclose(file);
return NULL;
}
png_structp png_struct = png_create_read_struct(PNG_LIBPNG_VER_STRING,
NULL, NULL, NULL);
png_infop png_info = png_create_info_struct(png_struct);
if (!png_info) {
png_destroy_read_struct(&png_struct, NULL, NULL);
delegate()->OnError(StrCat("Image " , path, " could not create png_info"));
return NULL;
}
if (setjmp(png_jmpbuf(png_struct))) {
png_destroy_read_struct(&png_struct, &png_info, NULL);
fclose(file);
delegate()->OnError(StrCat("Image " , path, " could not be decoded."));
return NULL;
}
png_init_io(png_struct, file);
png_set_sig_bytes(png_struct, 8);
png_read_info(png_struct, png_info);
int width = png_get_image_width(png_struct, png_info);
int height = png_get_image_height(png_struct, png_info);
if ((width > MAX_PNG_DIMENSION) || (height > MAX_PNG_DIMENSION)) {
fclose(file);
delegate()->OnError(StrCat("Image " , path, " is too big."));
return NULL;
}
if ((width <= 0) || (height <= 0)) {
delegate()->OnError(StrCat("Image " , path, " has nonpositive dimension."));
return NULL;
}
// Expand the image to 8-bit RGBA. Code taken from libpng manpage.
int color_type = png_get_color_type(png_struct, png_info);
int bit_depth = png_get_bit_depth(png_struct, png_info);
if (color_type == PNG_COLOR_TYPE_PALETTE) {
png_set_palette_to_rgb(png_struct);
}
if ((color_type == PNG_COLOR_TYPE_GRAY) && (bit_depth < 8)) {
png_set_expand_gray_1_2_4_to_8(png_struct);
}
if (png_get_valid(png_struct, png_info, PNG_INFO_tRNS)) {
png_set_tRNS_to_alpha(png_struct);
}
if (bit_depth == 16) {
png_set_strip_16(png_struct);
}
if (bit_depth < 8) {
png_set_packing(png_struct);
}
if ((color_type == PNG_COLOR_TYPE_RGB) ||
(color_type == PNG_COLOR_TYPE_GRAY)) {
png_set_add_alpha(png_struct, 0xff, PNG_FILLER_AFTER);
}
if ((color_type == PNG_COLOR_TYPE_GRAY) ||
(color_type == PNG_COLOR_TYPE_GRAY_ALPHA)) {
png_set_gray_to_rgb(png_struct);
}
// TODO(abliss): what to do with background color and gamma?
png_read_update_info(png_struct, png_info);
png_bytep* rows = new png_bytep[height];
for (int i = height - 1; i >= 0; i--) {
rows[i] = new png_byte[width * BYTES_PER_PIXEL];
}
png_read_image(png_struct, rows);
png_read_end(png_struct, png_info);
fclose(file);
return new Image(this, png_struct, png_info, rows);
}
LibpngImageLibrary::LibpngImageLibrary(const FilePath& base_input_path,
const FilePath& base_output_path,
Delegate* delegate)
: ImageLibraryInterface(base_input_path, base_output_path, delegate) {
}
} // namespace spriter
} // namespace net_instaweb