| /* |
| * 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 |