| /* |
| * 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 |
| |
| #include "pagespeed/kernel/image/png_optimizer.h" |
| |
| #include "base/logging.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/scanline_utils.h" |
| |
| #ifdef __native_client__ |
| // For some reason that is not yet clear, invoking png_longjmp on |
| // native client causes a crash. Invoking longjmp on the jump buffer |
| // directly does not crash. The jump buffer is defined in the |
| // following libpng private header, so we include it here. See |
| // http://code.google.com/p/page-speed/issues/detail?id=644 for more |
| // information. |
| #include "third_party/libpng/pngstruct.h" |
| #endif |
| |
| extern "C" { |
| #ifdef USE_SYSTEM_ZLIB |
| #include "zlib.h" // NOLINT |
| #else |
| #include "third_party/zlib/zlib.h" |
| #endif |
| |
| #include "third_party/optipng/src/opngreduc/opngreduc.h" |
| } |
| |
| using net_instaweb::MessageHandler; |
| using pagespeed::image_compression::PngCompressParams; |
| |
| namespace { |
| |
| // we use these four combinations because different images seem to benefit from |
| // different parameters and this combination of 4 seems to work best for a large |
| // set of PNGs from the web. |
| const PngCompressParams kPngCompressionParams[] = { |
| PngCompressParams(PNG_ALL_FILTERS, Z_DEFAULT_STRATEGY, false), |
| PngCompressParams(PNG_ALL_FILTERS, Z_FILTERED, false), |
| PngCompressParams(PNG_FILTER_NONE, Z_DEFAULT_STRATEGY, false), |
| PngCompressParams(PNG_FILTER_NONE, Z_FILTERED, false) |
| }; |
| |
| const size_t kParamCount = arraysize(kPngCompressionParams); |
| |
| void ReadPngFromStream(png_structp read_ptr, |
| png_bytep data, |
| png_size_t length) { |
| pagespeed::image_compression::ScanlineStreamInput* input = |
| reinterpret_cast<pagespeed::image_compression::ScanlineStreamInput*>( |
| png_get_io_ptr(read_ptr)); |
| |
| if (input->offset() + length <= input->length()) { |
| memcpy(data, input->data() + input->offset(), length); |
| input->set_offset(input->offset() + length); |
| |
| } else { |
| PS_DLOG_INFO(input->message_handler(), "Unexpected EOF."); |
| |
| // We weren't able to satisfy the read, so abort. |
| #if PNG_LIBPNG_VER >= 10400 |
| #ifndef __native_client__ |
| png_longjmp(read_ptr, 1); |
| #else |
| // On native client, invoking png_longjmp as above causes a |
| // crash. Invoking longjmp directly, however, works fine. For the |
| // time being we use this workaround for native client builds. See |
| // http://code.google.com/p/page-speed/issues/detail?id=644 for |
| // more information. |
| longjmp(read_ptr->longjmp_buffer, 1); |
| #endif |
| #else |
| longjmp(read_ptr->jmpbuf, 1); |
| #endif |
| } |
| } |
| |
| void WritePngToString(png_structp write_ptr, |
| png_bytep data, |
| png_size_t length) { |
| GoogleString& buffer = |
| *reinterpret_cast<GoogleString*>(png_get_io_ptr(write_ptr)); |
| buffer.append(reinterpret_cast<char*>(data), length); |
| } |
| |
| void PngErrorFn(png_structp png_ptr, png_const_charp msg) { |
| PS_DLOG_INFO(static_cast<MessageHandler*>(png_get_error_ptr(png_ptr)), \ |
| "libpng error: %s", msg); |
| |
| // Invoking the error function indicates a terminal failure, which |
| // means we must longjmp to abort the libpng invocation. |
| #if PNG_LIBPNG_VER >= 10400 |
| #ifndef __native_client__ |
| png_longjmp(png_ptr, 1); |
| #else |
| // On native client, invoking png_longjmp as above causes a |
| // crash. Invoking longjmp directly, however, works fine. For the |
| // time being we use this workaround for native client builds. See |
| // http://code.google.com/p/page-speed/issues/detail?id=644 for |
| // more information. |
| longjmp(png_ptr->longjmp_buffer, 1); |
| #endif |
| |
| #else |
| longjmp(png_ptr->jmpbuf, 1); |
| #endif |
| } |
| |
| void PngWarningFn(png_structp png_ptr, png_const_charp msg) { |
| PS_DLOG_INFO(static_cast<MessageHandler*>(png_get_error_ptr(png_ptr)), \ |
| "libpng warning: %s", msg); |
| } |
| |
| // no-op |
| void PngFlush(png_structp write_ptr) {} |
| |
| // Helper that reads an unsigned 32-bit integer from a stream of |
| // big-endian bytes. |
| inline uint32 ReadUint32FromBigEndianBytes(const unsigned char* read_head) { |
| return (static_cast<uint32>(*read_head) << 24) + |
| (static_cast<uint32>(*(read_head + 1)) << 16) + |
| (static_cast<uint32>(*(read_head + 2)) << 8) + |
| static_cast<uint32>(*(read_head + 3)); |
| } |
| |
| } // namespace |
| |
| namespace pagespeed { |
| |
| namespace image_compression { |
| |
| PngCompressParams::PngCompressParams(int level, int strategy, bool progressive) |
| : filter_level(level), |
| compression_strategy(strategy), |
| try_best_compression(false), |
| is_progressive(progressive) { |
| } |
| |
| PngCompressParams::PngCompressParams(bool compression, bool progressive) |
| : filter_level(PNG_FILTER_NONE), |
| compression_strategy(Z_NO_COMPRESSION), |
| try_best_compression(compression), |
| is_progressive(progressive) { |
| } |
| |
| ScopedPngStruct::ScopedPngStruct(Type type, |
| MessageHandler* handler) |
| : png_ptr_(NULL), |
| info_ptr_(NULL), |
| type_(type), |
| message_handler_(handler) { |
| DCHECK(type == READ || type == WRITE); |
| switch (type) { |
| case READ: |
| png_ptr_ = png_create_read_struct(PNG_LIBPNG_VER_STRING, |
| NULL, NULL, NULL); |
| break; |
| case WRITE: |
| png_ptr_ = png_create_write_struct(PNG_LIBPNG_VER_STRING, |
| NULL, NULL, NULL); |
| break; |
| default: |
| PS_LOG_DFATAL(handler, "Invalid type"); |
| } |
| if (png_ptr_ != NULL) { |
| info_ptr_ = png_create_info_struct(png_ptr_); |
| } |
| |
| png_set_error_fn(png_ptr_, message_handler_, &PngErrorFn, &PngWarningFn); |
| } |
| |
| bool ScopedPngStruct::reset() { |
| DCHECK(type_ == READ || type_ == WRITE); |
| if (type_ == READ) { |
| png_destroy_read_struct(&png_ptr_, &info_ptr_, NULL); |
| png_ptr_ = png_create_read_struct(PNG_LIBPNG_VER_STRING, |
| NULL, NULL, NULL); |
| } else { |
| png_destroy_write_struct(&png_ptr_, &info_ptr_); |
| png_ptr_ = png_create_write_struct(PNG_LIBPNG_VER_STRING, |
| NULL, NULL, NULL); |
| } |
| |
| if (setjmp(png_jmpbuf(png_ptr_))) { |
| PS_LOG_DFATAL(message_handler_, "Failed to initialize libpng."); |
| return false; |
| } |
| |
| if (png_ptr_ != NULL) { |
| info_ptr_ = png_create_info_struct(png_ptr_); |
| } |
| |
| png_set_error_fn(png_ptr_, message_handler_, &PngErrorFn, &PngWarningFn); |
| |
| return true; |
| } |
| |
| ScopedPngStruct::~ScopedPngStruct() { |
| switch (type_) { |
| case READ: |
| png_destroy_read_struct(&png_ptr_, &info_ptr_, NULL); |
| break; |
| case WRITE: |
| png_destroy_write_struct(&png_ptr_, &info_ptr_); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| PngReaderInterface::PngReaderInterface() { |
| } |
| |
| PngReaderInterface::~PngReaderInterface() { |
| } |
| |
| PngOptimizer::PngOptimizer(MessageHandler* handler) |
| : read_(ScopedPngStruct::READ, handler), |
| write_(ScopedPngStruct::WRITE, handler), |
| best_compression_(false), |
| message_handler_(handler) { |
| } |
| |
| PngOptimizer::~PngOptimizer() { |
| } |
| |
| bool PngOptimizer::CreateOptimizedPng(const PngReaderInterface& reader, |
| const GoogleString& in, |
| GoogleString* out, |
| MessageHandler* handler) { |
| if (!read_.valid() || !write_.valid()) { |
| PS_LOG_DFATAL(handler, "Invalid ScopedPngStruct r: %d, w: %d", \ |
| read_.valid(), write_.valid()); |
| return false; |
| } |
| |
| out->clear(); |
| |
| // Configure error handlers. |
| if (setjmp(png_jmpbuf(read_.png_ptr()))) { |
| PS_LOG_INFO(handler, "libpng failed to decode the input image."); |
| return false; |
| } |
| |
| if (setjmp(png_jmpbuf(write_.png_ptr()))) { |
| PS_LOG_INFO(handler, "libpng failed to create the output image."); |
| return false; |
| } |
| |
| if (!reader.ReadPng(in, read_.png_ptr(), read_.info_ptr(), |
| PNG_TRANSFORM_IDENTITY)) { |
| return false; |
| } |
| |
| if (!opng_validate_image(read_.png_ptr(), read_.info_ptr())) { |
| return false; |
| } |
| |
| // Copy the image data from the read structures to the write structures. |
| if (!CopyReadToWrite()) { |
| return false; |
| } |
| |
| // Perform all possible lossless image reductions |
| // (e.g. RGB->palette, etc). |
| opng_reduce_image(write_.png_ptr(), write_.info_ptr(), OPNG_REDUCE_ALL); |
| |
| if (best_compression_) { |
| return CreateBestOptimizedPngForParams(kPngCompressionParams, kParamCount, |
| out); |
| } else { |
| PngCompressParams params(PNG_FILTER_NONE, Z_DEFAULT_STRATEGY, false); |
| return CreateOptimizedPngWithParams(&write_, params, out); |
| } |
| } |
| |
| bool PngOptimizer::CreateBestOptimizedPngForParams( |
| const PngCompressParams* param_list, size_t param_list_size, |
| GoogleString* out) { |
| bool success = false; |
| for (size_t idx = 0; idx < param_list_size; ++idx) { |
| ScopedPngStruct write(ScopedPngStruct::WRITE, message_handler_); |
| GoogleString temp_output; |
| // libpng doesn't allow for reuse of the write structs, so we must copy on |
| // each iteration of the loop. |
| CopyPngStructs(write_, &write); |
| if (CreateOptimizedPngWithParams(&write, param_list[idx], &temp_output)) { |
| // If this gives better compression update the output. |
| if (out->empty() || out->size() > temp_output.size()) { |
| out->swap(temp_output); |
| } |
| success |= true; |
| } |
| } |
| return success; |
| } |
| |
| bool PngOptimizer::CreateOptimizedPngWithParams(ScopedPngStruct* write, |
| const PngCompressParams& params, |
| GoogleString *out) { |
| int compression_level = |
| best_compression_ ? Z_BEST_COMPRESSION : Z_DEFAULT_COMPRESSION; |
| png_set_compression_level(write->png_ptr(), compression_level); |
| png_set_compression_mem_level(write->png_ptr(), 8); |
| png_set_compression_strategy(write->png_ptr(), params.compression_strategy); |
| png_set_filter(write->png_ptr(), PNG_FILTER_TYPE_BASE, params.filter_level); |
| png_set_compression_window_bits(write->png_ptr(), 15); |
| if (!WritePng(write, out)) { |
| return false; |
| } |
| return true; |
| } |
| |
| bool PngOptimizer::OptimizePng(const PngReaderInterface& reader, |
| const GoogleString& in, |
| GoogleString* out, |
| MessageHandler* handler) { |
| PngOptimizer o(handler); |
| return o.CreateOptimizedPng(reader, in, out, handler); |
| } |
| |
| bool PngOptimizer::OptimizePngBestCompression(const PngReaderInterface& reader, |
| const GoogleString& in, |
| GoogleString* out, |
| MessageHandler* handler) { |
| PngOptimizer o(handler); |
| o.EnableBestCompression(); |
| return o.CreateOptimizedPng(reader, in, out, handler); |
| } |
| |
| PngReader::PngReader(MessageHandler* handler) |
| : message_handler_(handler) { |
| } |
| |
| PngReader::~PngReader() { |
| } |
| |
| bool PngReader::ReadPng(const GoogleString& body, |
| png_structp png_ptr, |
| png_infop info_ptr, |
| int transforms, |
| bool require_opaque) const { |
| ScanlineStreamInput input(message_handler_); |
| input.Initialize(body); |
| |
| if (setjmp(png_jmpbuf(png_ptr))) { |
| return false; |
| } |
| png_set_read_fn(png_ptr, &input, &ReadPngFromStream); |
| png_read_png(png_ptr, info_ptr, transforms, NULL); |
| |
| if (require_opaque && |
| ((transforms & PNG_TRANSFORM_STRIP_ALPHA) == 0)) { |
| // We're not guaranteed that the image is opaque already. |
| |
| int color_type = png_get_color_type(png_ptr, info_ptr); |
| if ((color_type & PNG_COLOR_MASK_ALPHA) != 0) { |
| // Image has an alpha channel. Make sure it's opaque, and |
| // strip it. |
| |
| if (!IsAlphaChannelOpaque(png_ptr, info_ptr, message_handler_)) { |
| return false; |
| } |
| if ((OPNG_REDUCE_STRIP_ALPHA & |
| opng_reduce_image(png_ptr, info_ptr, OPNG_REDUCE_STRIP_ALPHA)) |
| == 0) { |
| return false; |
| } |
| } |
| } |
| return true; |
| } |
| |
| bool PngReader::GetAttributes(const GoogleString& body, |
| int* out_width, |
| int* out_height, |
| int* out_bit_depth, |
| int* out_color_type) const { |
| // We need to read the PNG signature plus the IDAT chunk. |
| // |
| // Signature is 8 bytes, documentation: |
| // http://www.libpng.org/pub/png/spec/1.2/png-1.2-pdg.html#PNG-file-signature |
| // |
| // Chunk layout is 4 bytes chunk len + 4 bytes chunk name + chunk + |
| // 4 bytes chunk CRC, documentation: |
| // http://www.libpng.org/pub/png/spec/1.2/png-1.2-pdg.html#Chunk-layout |
| // |
| // IDAT chunk is 13 bytes (see code for details), documentation: |
| // http://www.libpng.org/pub/png/spec/1.2/png-1.2-pdg.html#C.IHDR |
| |
| const size_t kPngSigBytesSize = 8; |
| const size_t kChunkLenSize = 4; |
| const size_t kChunkNameSize = 4; |
| const size_t kIHDRChunkSize = 13; |
| const size_t kChunkCRCSize = 4; |
| |
| const size_t kPngMinHeaderSize = |
| kPngSigBytesSize + |
| kChunkLenSize + |
| kChunkNameSize + |
| kIHDRChunkSize + |
| kChunkCRCSize; |
| |
| if (body.size() < kPngMinHeaderSize) { |
| // Not enough bytes for us to read, so abort early. |
| return false; |
| } |
| |
| const unsigned char* read_head = |
| reinterpret_cast<const unsigned char*>(body.data()); |
| |
| // Validate the PNG signature. |
| if (png_sig_cmp( |
| const_cast<unsigned char*>(read_head), 0, kPngSigBytesSize) != 0) { |
| return false; |
| } |
| read_head += kPngSigBytesSize; |
| |
| // The first 4 bytes of the chunk contains the chunk length. |
| const uint32 first_chunk_len = ReadUint32FromBigEndianBytes(read_head); |
| if (first_chunk_len != kIHDRChunkSize) { |
| return false; |
| } |
| read_head += kChunkLenSize; |
| |
| if (strncmp("IHDR", reinterpret_cast<const char*>(read_head), 4) != 0) { |
| return false; |
| } |
| |
| // Compute the CRC for the chunk (using zlib's CRC computer since |
| // it's already available to us). |
| uint32 computed_crc = crc32(0L, Z_NULL, 0); |
| computed_crc = |
| crc32(computed_crc, read_head, kChunkNameSize + kIHDRChunkSize); |
| read_head += kChunkNameSize; |
| |
| // Extract the expected CRC, after the end of the IHDR data. |
| uint32 expected_crc = |
| ReadUint32FromBigEndianBytes(read_head + kIHDRChunkSize); |
| if (expected_crc != computed_crc) { |
| // CRC mismatch. Invalid chunk. Abort. |
| return false; |
| } |
| |
| // Now read the IHDR chunk contents. Its layout is: |
| // width: 4 bytes |
| // height: 4 bytes |
| // bit_depth: 1 byte |
| // color_type: 1 byte |
| // other data: 3 bytes |
| *out_width = ReadUint32FromBigEndianBytes(read_head); |
| *out_height = ReadUint32FromBigEndianBytes(read_head + 4); |
| *out_bit_depth = read_head[8]; |
| *out_color_type = read_head[9]; |
| return true; |
| } |
| |
| bool PngOptimizer::WritePng(ScopedPngStruct* write, GoogleString* buffer) { |
| if (setjmp(png_jmpbuf(write->png_ptr()))) { |
| return false; |
| } |
| png_set_write_fn(write->png_ptr(), buffer, &WritePngToString, &PngFlush); |
| png_write_png( |
| write->png_ptr(), write->info_ptr(), PNG_TRANSFORM_IDENTITY, NULL); |
| |
| return true; |
| } |
| |
| bool PngOptimizer::CopyReadToWrite() { |
| return CopyPngStructs(read_, &write_); |
| } |
| |
| bool PngOptimizer::CopyPngStructs(const ScopedPngStruct& from, |
| ScopedPngStruct* to) { |
| png_uint_32 width, height; |
| int bit_depth, color_type, interlace_type, compression_type, filter_type; |
| if (setjmp(png_jmpbuf(from.png_ptr()))) { |
| return false; |
| } |
| png_get_IHDR(from.png_ptr(), |
| from.info_ptr(), |
| &width, |
| &height, |
| &bit_depth, |
| &color_type, |
| &interlace_type, |
| &compression_type, |
| &filter_type); |
| |
| if (setjmp(png_jmpbuf(to->png_ptr()))) { |
| return false; |
| } |
| png_set_IHDR(to->png_ptr(), |
| to->info_ptr(), |
| width, |
| height, |
| bit_depth, |
| color_type, |
| interlace_type, |
| compression_type, |
| filter_type); |
| |
| // NOTE: if libpng's free_me capability is not enabled, sharing |
| // rowbytes between the read and write structs will lead to a |
| // double-free. Thus we test for the PNG_FREE_ME_SUPPORTED define |
| // here. |
| #ifndef PNG_FREE_ME_SUPPORTED |
| #error PNG_FREE_ME_SUPPORTED is required or double-frees may happen. |
| #endif |
| png_bytepp row_pointers = png_get_rows(from.png_ptr(), from.info_ptr()); |
| png_set_rows(to->png_ptr(), to->info_ptr(), row_pointers); |
| |
| png_colorp palette; |
| int num_palette; |
| if (png_get_PLTE(from.png_ptr(), from.info_ptr(), &palette, &num_palette) != |
| 0) { |
| png_set_PLTE(to->png_ptr(), to->info_ptr(), palette, num_palette); |
| } |
| |
| // Transparency is not considered metadata, although tRNS is |
| // ancillary. |
| png_bytep trans; |
| int num_trans; |
| png_color_16p trans_values; |
| if (png_get_tRNS(from.png_ptr(), from.info_ptr(), &trans, &num_trans, |
| &trans_values) != 0) { |
| png_set_tRNS(to->png_ptr(), to->info_ptr(), trans, num_trans, trans_values); |
| } |
| |
| double gamma; |
| if (png_get_gAMA(from.png_ptr(), from.info_ptr(), &gamma) != 0) { |
| png_set_gAMA(to->png_ptr(), to->info_ptr(), gamma); |
| } |
| |
| // Do not copy bkgd, hist or sbit sections, since they are not |
| // supported in most browsers. |
| |
| return true; |
| } |
| |
| // static |
| bool PngReaderInterface::IsAlphaChannelOpaque( |
| png_structp png_ptr, png_infop info_ptr, |
| MessageHandler* handler) { |
| png_uint_32 height; |
| png_uint_32 width; |
| int bit_depth; |
| int color_type; |
| if (setjmp(png_jmpbuf(png_ptr))) { |
| return false; |
| } |
| png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, |
| NULL, NULL, NULL); |
| |
| if ((color_type & PNG_COLOR_MASK_ALPHA) == 0) { |
| // Image doesn't have alpha. |
| PS_LOG_DFATAL(handler, \ |
| "IsAlphaChannelOpaque called for image without alpha channel."); |
| return false; |
| } |
| |
| png_bytep trans; |
| int num_trans; |
| png_color_16p trans_values; |
| if (png_get_tRNS(png_ptr, |
| info_ptr, |
| &trans, |
| &num_trans, |
| &trans_values) != 0) { |
| if ((color_type & PNG_COLOR_MASK_PALETTE) != 0) { |
| for (int idx = 0; idx < num_trans; ++idx) { |
| if (trans[idx] != 0xff) { |
| return false; |
| } |
| } |
| return true; |
| } else { |
| // Non-paletted image with a tRNS block is transparent |
| return false; |
| } |
| } else { |
| // There is no tRNS block. |
| if ((color_type & PNG_COLOR_MASK_PALETTE) != 0) { |
| // If we go this far, we have an image with |
| // PNG_COLOR_MASK_ALPHA but no tRNS block. We're confused. |
| PS_LOG_INFO(handler, "PNG_COLOR_MASK is set but could not read tRNS."); |
| return false; |
| } |
| } |
| |
| int channels = png_get_channels(png_ptr, info_ptr); |
| |
| if (color_type == PNG_COLOR_TYPE_RGB_ALPHA) { |
| if (channels != 4) { |
| PS_LOG_DFATAL(handler, \ |
| "Encountered unexpected number of channels for RGBA image: %d", \ |
| channels); |
| return false; |
| } |
| } else if (color_type == PNG_COLOR_TYPE_GRAY_ALPHA) { |
| if (channels != 2) { |
| PS_LOG_DFATAL(handler, \ |
| "Encountered unexpected number of channels for Gray + Alpha image:" \ |
| " %d", channels); |
| return false; |
| } |
| } else { |
| PS_LOG_DFATAL(handler, \ |
| "Encountered alpha image of unknown type :%d", color_type); |
| return false; |
| } |
| |
| // We currently detect alpha only for 8/16 bit Gray/TrueColor with Alpha |
| // channel. Only 8 or 16 bit depths are supported for these modes. |
| if (bit_depth % 8 != 0) { |
| PS_DLOG_INFO(handler, "Received unexpected bit_depth: %d", bit_depth); |
| return false; |
| } |
| |
| int bytes_per_channel = bit_depth / 8; |
| int bytes_per_pixel = channels * bytes_per_channel; |
| png_bytepp row_pointers = png_get_rows(png_ptr, info_ptr); |
| |
| // Alpha channel is always the last channel. |
| png_uint_32 alpha_byte_offset = (channels - 1) * bytes_per_channel; |
| for (png_uint_32 row = 0; row < height; ++row) { |
| unsigned char* row_bytes = |
| static_cast<unsigned char*>(*(row_pointers + row)); |
| for (png_uint_32 pixel = 0; pixel < width * bytes_per_pixel; |
| pixel += bytes_per_pixel) { |
| for (int alpha_byte = 0; alpha_byte < bytes_per_channel; |
| ++alpha_byte) { |
| if ((row_bytes[pixel + alpha_byte_offset + alpha_byte] & 0xff) != |
| 0xff) { |
| return false; |
| } |
| } |
| } |
| } |
| |
| return true; |
| } |
| |
| // static |
| bool PngReaderInterface::GetBackgroundColor( |
| png_structp png_ptr, png_infop info_ptr, |
| unsigned char *red, unsigned char* green, unsigned char* blue, |
| MessageHandler* handler) { |
| if (setjmp(png_jmpbuf(png_ptr))) { |
| return false; |
| } |
| if (!png_get_valid(png_ptr, info_ptr, PNG_INFO_bKGD)) { |
| return false; |
| } |
| png_color_16p bg; |
| png_get_bKGD(png_ptr, info_ptr, &bg); |
| const png_byte bit_depth = png_get_bit_depth(png_ptr, info_ptr); |
| const png_byte color_type = png_get_color_type(png_ptr, info_ptr); |
| |
| if (bit_depth == 16) { |
| // Downsample 16bit to 8bit. |
| *red = bg->red >> 8; |
| *green = bg->green >> 8; |
| *blue = bg->blue >> 8; |
| } else if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) { |
| // Upsample to 8bit. |
| const int scale = 255 / ((bit_depth << 1) - 1); |
| const unsigned char gray_8bit = bg->gray * scale; |
| *red = gray_8bit; |
| *green = gray_8bit; |
| *blue = gray_8bit; |
| } else if (bit_depth == 8) { |
| *red = static_cast<unsigned char>(bg->red); |
| *green = static_cast<unsigned char>(bg->green); |
| *blue = static_cast<unsigned char>(bg->blue); |
| } else { |
| // TODO(bmcquade): we currently fall through to this case for |
| // 1-bit paletted images. Consider adding support. |
| PS_DLOG_INFO(handler, \ |
| "Unsupported bit_depth: %d color type: %d", \ |
| static_cast<int>(bit_depth), static_cast<int>(color_type)); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| PngScanlineReader::PngScanlineReader(MessageHandler* handler) |
| : read_(ScopedPngStruct::READ, handler), |
| current_scanline_(0), |
| transform_(PNG_TRANSFORM_IDENTITY), |
| require_opaque_(false), |
| message_handler_(handler) { |
| } |
| |
| jmp_buf* PngScanlineReader::GetJmpBuf() { |
| jmp_buf& buf = png_jmpbuf(read_.png_ptr()); |
| return &buf; |
| } |
| |
| bool PngScanlineReader::Reset() { |
| if (!read_.reset()) { |
| return false; |
| } |
| current_scanline_ = 0; |
| transform_ = PNG_TRANSFORM_IDENTITY; |
| require_opaque_ = false; |
| return true; |
| } |
| |
| bool PngScanlineReader::InitializeRead(const PngReaderInterface& reader, |
| const GoogleString& in) { |
| bool is_opaque = false; |
| return InitializeRead(reader, in, &is_opaque); |
| } |
| |
| bool PngScanlineReader::InitializeRead(const PngReaderInterface& reader, |
| const GoogleString& in, |
| bool* is_opaque) { |
| if (!read_.valid()) { |
| PS_LOG_DFATAL(message_handler_, \ |
| "Invalid ScopedPngStruct r: %d", read_.valid()); |
| return false; |
| } |
| |
| *is_opaque = require_opaque_; |
| if (!reader.ReadPng(in, read_.png_ptr(), read_.info_ptr(), transform_, |
| require_opaque_)) { |
| return false; |
| } |
| |
| if (setjmp(png_jmpbuf(read_.png_ptr()))) { |
| return false; |
| } |
| if (!require_opaque_) { |
| int color_type = png_get_color_type(read_.png_ptr(), read_.info_ptr()); |
| *is_opaque = ((color_type & PNG_COLOR_MASK_ALPHA) == 0); |
| if (!(*is_opaque) && |
| PngReaderInterface::IsAlphaChannelOpaque( |
| read_.png_ptr(), read_.info_ptr(), message_handler_)) { |
| // Clear the read pointers. |
| if (!read_.reset()) { |
| return false; |
| } |
| *is_opaque = true; |
| return reader.ReadPng(in, read_.png_ptr(), read_.info_ptr(), |
| transform_ | PNG_TRANSFORM_STRIP_ALPHA); |
| } |
| } |
| |
| return true; |
| } |
| |
| PngScanlineReader::~PngScanlineReader() { |
| } |
| |
| size_t PngScanlineReader::GetBytesPerScanline() { |
| return png_get_rowbytes(read_.png_ptr(), read_.info_ptr()); |
| } |
| |
| bool PngScanlineReader::HasMoreScanLines() { |
| size_t height = png_get_image_height(read_.png_ptr(), read_.info_ptr()); |
| return current_scanline_ < height; |
| } |
| |
| ScanlineStatus PngScanlineReader::ReadNextScanlineWithStatus( |
| void** out_scanline_bytes) { |
| if (!HasMoreScanLines()) { |
| return PS_LOGGED_STATUS(PS_LOG_DFATAL, message_handler_, |
| SCANLINE_STATUS_INVOCATION_ERROR, |
| SCANLINE_PNGREADER, |
| "No more scanlines in the input image."); |
| } |
| |
| if (setjmp(png_jmpbuf(read_.png_ptr()))) { |
| return PS_LOGGED_STATUS(PS_LOG_INFO, message_handler_, |
| SCANLINE_STATUS_INTERNAL_ERROR, |
| SCANLINE_PNGREADER, |
| "libpng failed to decode the image."); |
| } |
| png_bytepp row_pointers = png_get_rows(read_.png_ptr(), read_.info_ptr()); |
| *out_scanline_bytes = static_cast<void*>(*(row_pointers + current_scanline_)); |
| current_scanline_++; |
| return ScanlineStatus(SCANLINE_STATUS_SUCCESS); |
| } |
| |
| void PngScanlineReader::set_transform(int transform) { |
| transform_ = transform; |
| } |
| |
| void PngScanlineReader::set_require_opaque(bool require_opaque) { |
| require_opaque_ = require_opaque; |
| } |
| |
| size_t PngScanlineReader::GetImageHeight() { |
| return png_get_image_height(read_.png_ptr(), read_.info_ptr()); |
| } |
| |
| size_t PngScanlineReader::GetImageWidth() { |
| return png_get_image_width(read_.png_ptr(), read_.info_ptr()); |
| } |
| |
| int PngScanlineReader::GetColorType() { |
| return png_get_color_type(read_.png_ptr(), read_.info_ptr()); |
| } |
| |
| bool PngScanlineReader::IsProgressive() { |
| return (png_get_interlace_type( |
| read_.png_ptr(), read_.info_ptr()) == PNG_INTERLACE_ADAM7); |
| } |
| |
| PixelFormat PngScanlineReader::GetPixelFormat() { |
| int bit_depth = png_get_bit_depth(read_.png_ptr(), read_.info_ptr()); |
| int color_type = png_get_color_type(read_.png_ptr(), read_.info_ptr()); |
| if (bit_depth == 8 && color_type == PNG_COLOR_TYPE_GRAY) { |
| return GRAY_8; |
| } else if (bit_depth == 8 && color_type == PNG_COLOR_TYPE_RGB) { |
| return RGB_888; |
| } else if (bit_depth == 8 && color_type == PNG_COLOR_TYPE_RGB_ALPHA) { |
| return RGBA_8888; |
| } |
| |
| return UNSUPPORTED; |
| } |
| |
| bool PngScanlineReader::GetBackgroundColor( |
| unsigned char* red, unsigned char* green, unsigned char* blue) { |
| return PngReaderInterface::GetBackgroundColor( |
| read_.png_ptr(), read_.info_ptr(), red, green, blue, message_handler_); |
| } |
| |
| ScanlineStatus PngScanlineReader::InitializeWithStatus( |
| const void* /* image_buffer */, |
| size_t /* buffer_length */) { |
| return PS_LOGGED_STATUS(PS_LOG_DFATAL, message_handler_, |
| SCANLINE_STATUS_INVOCATION_ERROR, |
| SCANLINE_PNGREADER, |
| "unexpected call to InitializeWithStatus()"); |
| } |
| |
| PngScanlineReaderRaw::PngScanlineReaderRaw( |
| MessageHandler* handler) |
| : pixel_format_(UNSUPPORTED), |
| is_progressive_(false), |
| height_(0), |
| width_(0), |
| bytes_per_row_(0), |
| row_(0), |
| was_initialized_(false), |
| message_handler_(handler) { |
| } |
| |
| PngScanlineReaderRaw::~PngScanlineReaderRaw() { |
| } |
| |
| bool PngScanlineReaderRaw::Reset() { |
| pixel_format_ = UNSUPPORTED; |
| is_progressive_ = false; |
| height_ = 0; |
| width_ = 0; |
| bytes_per_row_ = 0; |
| row_ = 0; |
| was_initialized_ = false; |
| row_pointers_.reset(); |
| png_struct_.reset(); |
| png_input_->Reset(); |
| return true; |
| } |
| |
| // Initialize the reader with the given image stream. Note that image_buffer |
| // must remain unchanged until the last call to ReadNextScanline(). |
| ScanlineStatus PngScanlineReaderRaw::InitializeWithStatus( |
| const void* image_buffer, |
| size_t buffer_length) { |
| // Reset the reader if it has been initialized before. |
| if (was_initialized_ && !Reset()) { |
| return PS_LOGGED_STATUS(PS_LOG_DFATAL, message_handler_, |
| SCANLINE_STATUS_INTERNAL_ERROR, |
| SCANLINE_PNGREADERRAW, |
| "Reset()"); |
| } |
| |
| png_struct_.reset(new ScopedPngStruct(ScopedPngStruct::READ, |
| message_handler_)); |
| if (png_struct_ == NULL) { |
| return PS_LOGGED_STATUS(PS_LOG_ERROR, message_handler_, |
| SCANLINE_STATUS_MEMORY_ERROR, |
| SCANLINE_PNGREADERRAW, |
| "Failed to create ScopedPngStruct"); |
| } |
| |
| // Allocate and initialize png_input_, if that has not been done. |
| if (png_input_ == NULL) { |
| png_input_.reset(new ScanlineStreamInput(message_handler_)); |
| if (png_input_ == NULL) { |
| return PS_LOGGED_STATUS(PS_LOG_ERROR, message_handler_, |
| SCANLINE_STATUS_MEMORY_ERROR, |
| SCANLINE_PNGREADERRAW, |
| "new ScanlineStreamInput"); |
| } |
| } |
| |
| if (!png_struct_->valid()) { |
| return PS_LOGGED_STATUS(PS_LOG_DFATAL, message_handler_, |
| SCANLINE_STATUS_INTERNAL_ERROR, |
| SCANLINE_PNGREADERRAW, |
| "png_struct_->valid()"); |
| } |
| |
| png_structp png_ptr = png_struct_->png_ptr(); |
| png_infop info_ptr = png_struct_->info_ptr(); |
| |
| if (setjmp(png_jmpbuf(png_ptr)) != 0) { |
| // Jump to here if any error happens. |
| png_struct_.reset(); |
| return PS_LOGGED_STATUS(PS_LOG_INFO, message_handler_, |
| SCANLINE_STATUS_INTERNAL_ERROR, |
| SCANLINE_PNGREADERRAW, |
| "libpng failed to decode the image."); |
| } |
| |
| // Set up data feed for libpng. |
| png_input_->Initialize(image_buffer, buffer_length); |
| png_set_read_fn(png_ptr, png_input_.get(), ReadPngFromStream); |
| |
| png_uint_32 width, height; |
| int32 bit_depth, color_type, interlace_type; |
| png_read_info(png_ptr, info_ptr); |
| const int ok = png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, |
| &color_type, &interlace_type, NULL, NULL); |
| if (ok == 0) { |
| png_struct_.reset(); |
| return PS_LOGGED_STATUS(PS_LOG_INFO, message_handler_, |
| SCANLINE_STATUS_INTERNAL_ERROR, |
| SCANLINE_PNGREADERRAW, |
| "png_get_IHDR() failed."); |
| } |
| |
| // Set up transformations. We will transform the input to one of these |
| // formats: GRAY_8, RGB_888, and RGBA_8888. |
| // |
| // Reference for setting up transformations is in the png_read_png() function |
| // in pngread.c. |
| // |
| // Strip 16 bit per color down to 8 bits per color. |
| png_set_strip_16(png_ptr); |
| |
| // Expand grayscale images to full 8 bits from 1, 2, or 4 bits per pixel. |
| // Expand paletted or RGB images with transparency to full alpha channels |
| // so the data will be available as RGBA quartets. |
| if ((bit_depth < 8) || |
| (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS))) { |
| png_set_expand(png_ptr); |
| } |
| |
| // Set up callbacks for interlacing (progressive) image. |
| png_set_interlace_handling(png_ptr); |
| |
| // Update the reader struct after setting the transformations. |
| png_read_update_info(png_ptr, info_ptr); |
| |
| // Get the updated color type. |
| color_type = png_get_color_type(png_ptr, info_ptr); |
| |
| if (color_type == PNG_COLOR_TYPE_GRAY_ALPHA || |
| color_type == PNG_COLOR_TYPE_PALETTE) { |
| if (color_type == PNG_COLOR_TYPE_GRAY_ALPHA) { |
| // Expand Gray_Alpha to RGBA. |
| png_set_gray_to_rgb(png_ptr); |
| } else { |
| // Expand paletted colors into true RGB triplets. |
| png_set_palette_to_rgb(png_ptr); |
| } |
| |
| // Update the reader struct after modifying the transformations. |
| png_read_update_info(png_ptr, info_ptr); |
| |
| // Get the updated color type. |
| color_type = png_get_color_type(png_ptr, info_ptr); |
| } |
| |
| // Determine the pixel format and the number of channels. |
| switch (color_type) { |
| case PNG_COLOR_TYPE_GRAY: |
| pixel_format_ = GRAY_8; |
| break; |
| case PNG_COLOR_TYPE_RGB: |
| case PNG_COLOR_TYPE_PALETTE: |
| pixel_format_ = RGB_888; |
| break; |
| case PNG_COLOR_TYPE_RGBA: |
| pixel_format_ = RGBA_8888; |
| break; |
| default: // Unrecognized format. |
| png_struct_.reset(); |
| return PS_LOGGED_STATUS(PS_LOG_ERROR, message_handler_, |
| SCANLINE_STATUS_INTERNAL_ERROR, |
| SCANLINE_PNGREADERRAW, |
| "unrecognized color type"); |
| } |
| |
| // Copy the information to the object properties. |
| width_ = width; |
| height_ = height; |
| bytes_per_row_ = width_ * GetNumChannelsFromPixelFormat(pixel_format_, |
| message_handler_); |
| row_ = 0; |
| is_progressive_ = (interlace_type == PNG_INTERLACE_ADAM7); |
| was_initialized_ = true; |
| return ScanlineStatus(SCANLINE_STATUS_SUCCESS); |
| } |
| |
| ScanlineStatus PngScanlineReaderRaw::ReadNextScanlineWithStatus( |
| void** out_scanline_bytes) { |
| if (!was_initialized_ || !HasMoreScanLines()) { |
| return PS_LOGGED_STATUS(PS_LOG_DFATAL, message_handler_, |
| SCANLINE_STATUS_INVOCATION_ERROR, |
| SCANLINE_PNGREADERRAW, |
| "The reader was not initialized or the image " |
| "does not have any more scanlines."); |
| } |
| |
| png_structp png_ptr = png_struct_->png_ptr(); |
| |
| // In case libpng has an error, program will jump to the following 'setjmp', |
| // which will have value of non-zero. To clean up memory properly, we have |
| // to define row_pointers before 'setjmp' and clean it up when error happens. |
| if (setjmp(png_jmpbuf(png_ptr)) != 0) { |
| Reset(); |
| return PS_LOGGED_STATUS(PS_LOG_INFO, message_handler_, |
| SCANLINE_STATUS_INTERNAL_ERROR, |
| SCANLINE_PNGREADERRAW, |
| "libpng failed to decode the image."); |
| } |
| |
| // At the first time when ReadNextScanline() is called, we allocate buffer |
| // to store the decoded pixels. For non-progressive (non-interlacing) |
| // image, we need buffer to store a row of pixels. For progressive image, |
| // we need buffer to store the entire image. For progressive image, we |
| // also decode the entire image at the first call. |
| if (row_ == 0) { |
| if (!is_progressive_) { |
| image_buffer_.reset(new png_byte[bytes_per_row_]); |
| } else { |
| image_buffer_.reset(new png_byte[bytes_per_row_ * height_]); |
| // For a progressive PNG, we have to decode the entire image before |
| // rendering any row. So at the first time when ReadNextScanline() |
| // is called, we decode the entire image into image_buffer_. |
| if (image_buffer_ != NULL) { |
| // Initialize an array of pointers, which specify the address of rows. |
| row_pointers_.reset(new png_bytep[height_]); |
| if (row_pointers_ == NULL) { |
| Reset(); |
| return PS_LOGGED_STATUS(PS_LOG_ERROR, message_handler_, |
| SCANLINE_STATUS_MEMORY_ERROR, |
| SCANLINE_PNGREADERRAW, |
| "Failed to allocate memory."); |
| } |
| for (size_t i = 0; i < height_; ++i) { |
| row_pointers_[i] = image_buffer_.get() + i * bytes_per_row_; |
| } |
| |
| // Decode the entire image. The results are stored in image_buffer_. |
| png_read_image(png_ptr, row_pointers_.get()); |
| } |
| } |
| if (image_buffer_ == NULL) { |
| Reset(); |
| return PS_LOGGED_STATUS(PS_LOG_ERROR, message_handler_, |
| SCANLINE_STATUS_MEMORY_ERROR, |
| SCANLINE_PNGREADERRAW, |
| "Failed to allocate memory."); |
| } |
| } |
| |
| if (!is_progressive_) { |
| // For a non-progressive PNG, we decode the image a row at a time. |
| png_read_row(png_ptr, image_buffer_.get(), NULL); |
| *out_scanline_bytes = static_cast<void*>(image_buffer_.get()); |
| } else { |
| // For a progressive PNG, we simply point the output to the corresponding |
| // row, because the image has already been decoded. |
| *out_scanline_bytes = |
| static_cast<void*>(image_buffer_.get() + row_ * bytes_per_row_); |
| } |
| |
| ++row_; |
| row_pointers_.reset(); |
| return ScanlineStatus(SCANLINE_STATUS_SUCCESS); |
| } |
| |
| PngScanlineWriter::PngScanlineWriter(MessageHandler* handler) : |
| width_(0), |
| height_(0), |
| row_(0), |
| pixel_format_(UNSUPPORTED), |
| was_initialized_(false), |
| message_handler_(handler) { |
| } |
| |
| PngScanlineWriter::~PngScanlineWriter() { |
| } |
| |
| bool PngScanlineWriter::Reset() { |
| width_ = 0; |
| height_ = 0; |
| row_ = 0; |
| pixel_format_ = UNSUPPORTED; |
| png_struct_.reset(); |
| was_initialized_ = false; |
| return true; |
| } |
| |
| bool PngScanlineWriter::Validate(const PngCompressParams* params, |
| GoogleString* png_image) { |
| if (params != NULL) { |
| // PNG_NO_FILTERS == 0 |
| // PNG_ALL_FILTERS == (PNG_FILTER_NONE | PNG_FILTER_SUB | PNG_FILTER_UP | |
| // PNG_FILTER_AVG | PNG_FILTER_PAETH) |
| if (params->filter_level & (~PNG_ALL_FILTERS)) { |
| PS_LOG_DFATAL(message_handler_, \ |
| "Filter level must be one of the following values, " \ |
| "or bitwise OR of some of them: PNG_NO_FILTERS, PNG_FILTER_NONE, " \ |
| "PNG_FILTER_SUB, PNG_FILTER_UP, PNG_FILTER_AVG, PNG_FILTER_PAETH."); |
| } |
| |
| switch (params->compression_strategy) { |
| case Z_DEFAULT_STRATEGY: |
| case Z_FILTERED: |
| case Z_HUFFMAN_ONLY: |
| case Z_RLE: |
| case Z_FIXED: |
| break; |
| default: |
| PS_LOG_DFATAL(message_handler_, \ |
| "Compression strategy must be one of the following values: " \ |
| "Z_DEFAULT_STRATEGY, Z_FILTERED, Z_HUFFMAN_ONLY, Z_RLE, Z_FIXED."); |
| return false; |
| } |
| } |
| |
| if (png_image == NULL) { |
| PS_LOG_DFATAL(message_handler_, "Ouput PNG image cannot be NULL."); |
| return false; |
| } |
| return true; |
| } |
| |
| ScanlineStatus PngScanlineWriter::InitWithStatus(const size_t width, |
| const size_t height, |
| PixelFormat pixel_format) { |
| // Reset the writer if it has been initialized before. |
| if (was_initialized_ && !Reset()) { |
| return PS_LOGGED_STATUS(PS_LOG_DFATAL, message_handler_, |
| SCANLINE_STATUS_INTERNAL_ERROR, |
| SCANLINE_PNGWRITER, "Reset()"); |
| } |
| |
| if (png_struct_ == NULL) { |
| png_struct_.reset(new ScopedPngStruct(ScopedPngStruct::WRITE, |
| message_handler_)); |
| if (png_struct_ == NULL) { |
| return PS_LOGGED_STATUS(PS_LOG_DFATAL, message_handler_, |
| SCANLINE_STATUS_MEMORY_ERROR, |
| SCANLINE_PNGWRITER, |
| "Failed to create ScopedPngStruct"); |
| } |
| } |
| |
| if (!png_struct_->valid()) { |
| return PS_LOGGED_STATUS(PS_LOG_DFATAL, message_handler_, |
| SCANLINE_STATUS_INTERNAL_ERROR, |
| SCANLINE_PNGWRITER, |
| "png_struct_->valid()"); |
| } |
| |
| if (width < 1 || height < 1) { |
| return PS_LOGGED_STATUS(PS_LOG_DFATAL, message_handler_, |
| SCANLINE_STATUS_INTERNAL_ERROR, |
| SCANLINE_PNGWRITER, |
| "dimensions are not positive"); |
| } |
| |
| switch (pixel_format) { |
| case GRAY_8: |
| case RGB_888: |
| case RGBA_8888: |
| break; |
| default: |
| return PS_LOGGED_STATUS(PS_LOG_DFATAL, message_handler_, |
| SCANLINE_STATUS_INTERNAL_ERROR, |
| SCANLINE_PNGWRITER, |
| "unknown pixel format: %d", |
| pixel_format); |
| } |
| |
| width_ = width; |
| height_ = height; |
| pixel_format_ = pixel_format; |
| bytes_per_row_ = width * GetBytesPerPixel(pixel_format); |
| return ScanlineStatus(SCANLINE_STATUS_SUCCESS); |
| } |
| |
| // Initialize the basic parameter for writing the image. To use the default |
| // compression parameters, set 'params' to NULL. |
| ScanlineStatus PngScanlineWriter::InitializeWriteWithStatus( |
| const void* const params, |
| GoogleString* const png_image) { |
| const PngCompressParams* png_params = |
| static_cast<const PngCompressParams*>(params); |
| |
| // Validate input arguments. |
| if (!Validate(png_params, png_image)) { |
| return PS_LOGGED_STATUS(PS_LOG_DFATAL, message_handler_, |
| SCANLINE_STATUS_INVOCATION_ERROR, |
| SCANLINE_PNGWRITER, |
| "Validate()"); |
| } |
| |
| png_image->clear(); |
| |
| const int bit_depth = 8; |
| int color_type = -1; |
| switch (pixel_format_) { |
| case GRAY_8: |
| color_type = PNG_COLOR_TYPE_GRAY; |
| break; |
| case RGB_888: |
| color_type = PNG_COLOR_TYPE_RGB; |
| break; |
| default: // RGBA_8888. Init() has filtered out invalid values. |
| color_type = PNG_COLOR_TYPE_RGB_ALPHA; |
| } |
| |
| png_structp png_ptr = png_struct_->png_ptr(); |
| png_infop info_ptr = png_struct_->info_ptr(); |
| |
| if (setjmp(png_jmpbuf(png_ptr)) != 0) { |
| // Jump to here if any error happens. |
| Reset(); |
| return PS_LOGGED_STATUS(PS_LOG_INFO, message_handler_, |
| SCANLINE_STATUS_INTERNAL_ERROR, |
| SCANLINE_PNGWRITER, |
| "libpng failed to compress the image."); |
| } |
| |
| png_set_compression_strategy(png_ptr, png_params->compression_strategy); |
| png_set_filter(png_ptr, PNG_FILTER_TYPE_BASE, png_params->filter_level); |
| png_set_write_fn(png_ptr, png_image, &WritePngToString, &PngFlush); |
| |
| int interlace_type = (png_params->is_progressive ? |
| PNG_INTERLACE_ADAM7 : |
| PNG_INTERLACE_NONE); |
| png_set_IHDR(png_ptr, info_ptr, width_, height_, bit_depth, color_type, |
| interlace_type, PNG_COMPRESSION_TYPE_DEFAULT, |
| PNG_FILTER_TYPE_DEFAULT); |
| |
| png_write_info(png_ptr, info_ptr); |
| try_best_compression_ = png_params->try_best_compression; |
| pixel_buffer_.reset(new unsigned char[height_ * bytes_per_row_]); |
| was_initialized_ = true; |
| return ScanlineStatus(SCANLINE_STATUS_SUCCESS); |
| } |
| |
| // Write a scanline with the data provided. Return false in case of error. |
| ScanlineStatus PngScanlineWriter::WriteNextScanlineWithStatus( |
| const void* const scanline_bytes) { |
| if (was_initialized_ && row_ < height_) { |
| // Buffer the scanlines. |
| memcpy(pixel_buffer_.get() + row_ * bytes_per_row_, scanline_bytes, |
| bytes_per_row_); |
| ++row_; |
| return ScanlineStatus(SCANLINE_STATUS_SUCCESS); |
| } |
| return PS_LOGGED_STATUS(PS_LOG_DFATAL, message_handler_, |
| SCANLINE_STATUS_INVOCATION_ERROR, |
| SCANLINE_PNGWRITER, |
| "failed preconditions to write scanline"); |
| } |
| |
| // Finalize write structure once all scanlines are written. |
| ScanlineStatus PngScanlineWriter::FinalizeWriteWithStatus() { |
| if (!was_initialized_ || row_ != height_) { |
| Reset(); |
| return PS_LOGGED_STATUS(PS_LOG_DFATAL, message_handler_, |
| SCANLINE_STATUS_INVOCATION_ERROR, |
| SCANLINE_PNGWRITER, |
| "not initialized or not all rows written"); |
| } |
| |
| net_instaweb::scoped_array<unsigned char*> row_pointers( |
| new unsigned char*[height_]); |
| for (size_t row = 0; row < height_; ++row) { |
| row_pointers[row] = pixel_buffer_.get() + row * bytes_per_row_; |
| } |
| png_set_rows(png_struct_->png_ptr(), png_struct_->info_ptr(), |
| row_pointers.get()); |
| png_write_png(png_struct_->png_ptr(), png_struct_->info_ptr(), |
| PNG_TRANSFORM_IDENTITY, NULL); |
| |
| if (try_best_compression_) { |
| if (!DoBestCompression()) { |
| Reset(); |
| return PS_LOGGED_STATUS(PS_LOG_ERROR, message_handler_, |
| SCANLINE_STATUS_INTERNAL_ERROR, |
| SCANLINE_PNGWRITER, |
| "Failed to do the best compression"); |
| } |
| } |
| return ScanlineStatus(SCANLINE_STATUS_SUCCESS); |
| } |
| |
| bool PngScanlineWriter::DoBestCompression() { |
| GoogleString* png_image = static_cast<GoogleString*>( |
| png_get_io_ptr(png_struct_->png_ptr())); |
| ScanlineStreamInput png_input(message_handler_); |
| png_input.Initialize(*png_image); |
| ScopedPngStruct png_read(ScopedPngStruct::READ, message_handler_); |
| |
| if (setjmp(png_jmpbuf(png_read.png_ptr())) != 0) { |
| // Jump to here if any error happens. |
| Reset(); |
| return false; |
| } |
| |
| png_set_read_fn(png_read.png_ptr(), &png_input, &ReadPngFromStream); |
| png_read_png(png_read.png_ptr(), png_read.info_ptr(), PNG_TRANSFORM_IDENTITY, |
| NULL); |
| opng_reduce_image(png_read.png_ptr(), png_read.info_ptr(), OPNG_REDUCE_ALL); |
| |
| int min_size = png_image->length(); |
| for (size_t i = 0; i < kParamCount; ++i) { |
| ScopedPngStruct png_write(ScopedPngStruct::WRITE, message_handler_); |
| PngOptimizer::CopyPngStructs(png_read, &png_write); |
| |
| if (setjmp(png_jmpbuf(png_write.png_ptr())) != 0) { |
| // Jump to here if any error happens. |
| Reset(); |
| return false; |
| } |
| |
| png_set_compression_level(png_write.png_ptr(), Z_BEST_COMPRESSION); |
| png_set_compression_mem_level(png_write.png_ptr(), 8); |
| png_set_compression_window_bits(png_write.png_ptr(), 15); |
| png_set_compression_strategy(png_write.png_ptr(), |
| kPngCompressionParams[i].compression_strategy); |
| png_set_filter(png_write.png_ptr(), PNG_FILTER_TYPE_BASE, |
| kPngCompressionParams[i].filter_level); |
| |
| GoogleString recompressed_image; |
| png_set_write_fn(png_write.png_ptr(), &recompressed_image, |
| &WritePngToString, &PngFlush); |
| png_write_png(png_write.png_ptr(), png_write.info_ptr(), |
| PNG_TRANSFORM_IDENTITY, NULL); |
| |
| int recompressed_length = recompressed_image.length(); |
| if (min_size > recompressed_length) { |
| min_size = recompressed_length; |
| swap(*png_image, recompressed_image); |
| } |
| } |
| |
| return true; |
| } |
| |
| } // namespace image_compression |
| |
| } // namespace pagespeed |