123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542 |
- // Copyright (c) 2012 The Chromium Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style license that can be
- // found in the LICENSE file.
- #include "ui/gfx/codec/png_codec.h"
- #include <stdint.h>
- #include "base/logging.h"
- #include "base/memory/raw_ptr.h"
- #include "base/notreached.h"
- #include "base/strings/string_util.h"
- #include "third_party/libpng/png.h"
- #include "third_party/skia/include/core/SkBitmap.h"
- #include "third_party/skia/include/core/SkColorPriv.h"
- #include "third_party/skia/include/core/SkUnPreMultiply.h"
- #include "third_party/skia/include/encode/SkPngEncoder.h"
- #include "third_party/zlib/zlib.h"
- #include "ui/gfx/codec/vector_wstream.h"
- #include "ui/gfx/geometry/size.h"
- namespace gfx {
- // Decoder --------------------------------------------------------------------
- //
- // This code is based on WebKit libpng interface (PNGImageDecoder), which is
- // in turn based on the Mozilla png decoder.
- namespace {
- // Gamma constants: We assume we're on Windows which uses a gamma of 2.2.
- const double kMaxGamma = 21474.83; // Maximum gamma accepted by png library.
- const double kDefaultGamma = 2.2;
- const double kInverseGamma = 1.0 / kDefaultGamma;
- class PngDecoderState {
- public:
- // Output is a vector<unsigned char>.
- PngDecoderState(PNGCodec::ColorFormat ofmt, std::vector<unsigned char>* o)
- : output_format(ofmt),
- output_channels(0),
- bitmap(nullptr),
- is_opaque(true),
- output(o),
- width(0),
- height(0),
- done(false) {}
- // Output is an SkBitmap.
- explicit PngDecoderState(SkBitmap* skbitmap)
- : output_format(PNGCodec::FORMAT_SkBitmap),
- output_channels(0),
- bitmap(skbitmap),
- is_opaque(true),
- output(nullptr),
- width(0),
- height(0),
- done(false) {}
- PngDecoderState(const PngDecoderState&) = delete;
- PngDecoderState& operator=(const PngDecoderState&) = delete;
- PNGCodec::ColorFormat output_format;
- int output_channels;
- // An incoming SkBitmap to write to. If NULL, we write to output instead.
- raw_ptr<SkBitmap> bitmap;
- // Used during the reading of an SkBitmap. Defaults to true until we see a
- // pixel with anything other than an alpha of 255.
- bool is_opaque;
- // The other way to decode output, where we write into an intermediary buffer
- // instead of directly to an SkBitmap.
- raw_ptr<std::vector<unsigned char>> output;
- // Size of the image, set in the info callback.
- int width;
- int height;
- // Set to true when we've found the end of the data.
- bool done;
- };
- // User transform (passed to libpng) which converts a row decoded by libpng to
- // Skia format. Expects the row to have 4 channels, otherwise there won't be
- // enough room in |data|.
- void ConvertRGBARowToSkia(png_structp png_ptr,
- png_row_infop row_info,
- png_bytep data) {
- const int channels = row_info->channels;
- DCHECK_EQ(channels, 4);
- PngDecoderState* state =
- static_cast<PngDecoderState*>(png_get_user_transform_ptr(png_ptr));
- DCHECK(state) << "LibPNG user transform pointer is NULL";
- unsigned char* const end = data + row_info->rowbytes;
- for (unsigned char* p = data; p < end; p += channels) {
- uint32_t* sk_pixel = reinterpret_cast<uint32_t*>(p);
- const unsigned char alpha = p[channels - 1];
- if (alpha != 255) {
- state->is_opaque = false;
- *sk_pixel = SkPreMultiplyARGB(alpha, p[0], p[1], p[2]);
- } else {
- *sk_pixel = SkPackARGB32(alpha, p[0], p[1], p[2]);
- }
- }
- }
- // Called when the png header has been read. This code is based on the WebKit
- // PNGImageDecoder
- void DecodeInfoCallback(png_struct* png_ptr, png_info* info_ptr) {
- PngDecoderState* state = static_cast<PngDecoderState*>(
- png_get_progressive_ptr(png_ptr));
- int bit_depth, color_type, interlace_type, compression_type;
- int filter_type;
- png_uint_32 w, h;
- png_get_IHDR(png_ptr, info_ptr, &w, &h, &bit_depth, &color_type,
- &interlace_type, &compression_type, &filter_type);
- // Bounds check. When the image is unreasonably big, we'll error out and
- // end up back at the setjmp call when we set up decoding. "Unreasonably big"
- // means "big enough that w * h * 32bpp might overflow an int"; we choose this
- // threshold to match WebKit and because a number of places in code assume
- // that an image's size (in bytes) fits in a (signed) int.
- unsigned long long total_size =
- static_cast<unsigned long long>(w) * static_cast<unsigned long long>(h);
- if (total_size > ((1 << 29) - 1))
- longjmp(png_jmpbuf(png_ptr), 1);
- state->width = static_cast<int>(w);
- state->height = static_cast<int>(h);
- // The following png_set_* calls have to be done in the order dictated by
- // the libpng docs. Please take care if you have to move any of them. This
- // is also why certain things are done outside of the switch, even though
- // they look like they belong there.
- // Expand to ensure we use 24-bit for RGB and 32-bit for RGBA.
- if (color_type == PNG_COLOR_TYPE_PALETTE ||
- (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8))
- png_set_expand(png_ptr);
- // The '!= 0' is for silencing a Windows compiler warning.
- bool input_has_alpha = ((color_type & PNG_COLOR_MASK_ALPHA) != 0);
- // Transparency for paletted images.
- if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
- png_set_expand(png_ptr);
- input_has_alpha = true;
- }
- // Convert 16-bit to 8-bit.
- if (bit_depth == 16)
- png_set_strip_16(png_ptr);
- // Pick our row format converter necessary for this data.
- if (!input_has_alpha) {
- switch (state->output_format) {
- case PNGCodec::FORMAT_RGBA:
- state->output_channels = 4;
- png_set_add_alpha(png_ptr, 0xFF, PNG_FILLER_AFTER);
- break;
- case PNGCodec::FORMAT_BGRA:
- state->output_channels = 4;
- png_set_bgr(png_ptr);
- png_set_add_alpha(png_ptr, 0xFF, PNG_FILLER_AFTER);
- break;
- case PNGCodec::FORMAT_SkBitmap:
- state->output_channels = 4;
- png_set_add_alpha(png_ptr, 0xFF, PNG_FILLER_AFTER);
- break;
- }
- } else {
- switch (state->output_format) {
- case PNGCodec::FORMAT_RGBA:
- state->output_channels = 4;
- break;
- case PNGCodec::FORMAT_BGRA:
- state->output_channels = 4;
- png_set_bgr(png_ptr);
- break;
- case PNGCodec::FORMAT_SkBitmap:
- state->output_channels = 4;
- break;
- }
- }
- // Expand grayscale to RGB.
- if (color_type == PNG_COLOR_TYPE_GRAY ||
- color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
- png_set_gray_to_rgb(png_ptr);
- // Deal with gamma and keep it under our control.
- double gamma;
- if (png_get_gAMA(png_ptr, info_ptr, &gamma)) {
- if (gamma <= 0.0 || gamma > kMaxGamma) {
- gamma = kInverseGamma;
- png_set_gAMA(png_ptr, info_ptr, gamma);
- }
- png_set_gamma(png_ptr, kDefaultGamma, gamma);
- } else {
- png_set_gamma(png_ptr, kDefaultGamma, kInverseGamma);
- }
- // Setting the user transforms here (as opposed to inside the switch above)
- // because all png_set_* calls need to be done in the specific order
- // mandated by libpng.
- if (state->output_format == PNGCodec::FORMAT_SkBitmap) {
- png_set_read_user_transform_fn(png_ptr, ConvertRGBARowToSkia);
- png_set_user_transform_info(png_ptr, state, 0, 0);
- }
- // Tell libpng to send us rows for interlaced pngs.
- if (interlace_type == PNG_INTERLACE_ADAM7)
- png_set_interlace_handling(png_ptr);
- png_read_update_info(png_ptr, info_ptr);
- if (state->bitmap) {
- if (!state->bitmap->tryAllocN32Pixels(state->width, state->height)) {
- png_error(png_ptr, "Could not allocate bitmap.");
- NOTREACHED() << "png_error should not return.";
- return;
- }
- } else if (state->output) {
- state->output->resize(
- state->width * state->output_channels * state->height);
- }
- }
- void DecodeRowCallback(png_struct* png_ptr, png_byte* new_row,
- png_uint_32 row_num, int pass) {
- if (!new_row)
- return; // Interlaced image; row didn't change this pass.
- PngDecoderState* state = static_cast<PngDecoderState*>(
- png_get_progressive_ptr(png_ptr));
- if (static_cast<int>(row_num) > state->height) {
- NOTREACHED() << "Invalid row";
- return;
- }
- unsigned char* base = NULL;
- if (state->bitmap)
- base = reinterpret_cast<unsigned char*>(state->bitmap->getAddr32(0, 0));
- else if (state->output)
- base = &state->output->front();
- unsigned char* dest = &base[state->width * state->output_channels * row_num];
- png_progressive_combine_row(png_ptr, dest, new_row);
- }
- void DecodeEndCallback(png_struct* png_ptr, png_info* info) {
- PngDecoderState* state = static_cast<PngDecoderState*>(
- png_get_progressive_ptr(png_ptr));
- // Mark the image as complete, this will tell the Decode function that we
- // have successfully found the end of the data.
- state->done = true;
- }
- // Holds png struct and info ensuring the proper destruction.
- class PngReadStructInfo {
- public:
- PngReadStructInfo(): png_ptr_(nullptr), info_ptr_(nullptr) {
- }
- PngReadStructInfo(const PngReadStructInfo&) = delete;
- PngReadStructInfo& operator=(const PngReadStructInfo&) = delete;
- ~PngReadStructInfo() {
- png_destroy_read_struct(&png_ptr_, &info_ptr_, NULL);
- }
- bool Build(const unsigned char* input, size_t input_size) {
- if (input_size < 8)
- return false; // Input data too small to be a png
- // Have libpng check the signature, it likes the first 8 bytes.
- if (png_sig_cmp(const_cast<unsigned char*>(input), 0, 8) != 0)
- return false;
- png_ptr_ = png_create_read_struct(
- PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
- if (!png_ptr_)
- return false;
- info_ptr_ = png_create_info_struct(png_ptr_);
- if (!info_ptr_) {
- return false;
- }
- return true;
- }
- png_struct* png_ptr_;
- png_info* info_ptr_;
- };
- // Holds png struct and info ensuring the proper destruction.
- class PngWriteStructInfo {
- public:
- PngWriteStructInfo() : png_ptr_(nullptr), info_ptr_(nullptr) {
- }
- PngWriteStructInfo(const PngWriteStructInfo&) = delete;
- PngWriteStructInfo& operator=(const PngWriteStructInfo&) = delete;
- ~PngWriteStructInfo() {
- png_destroy_write_struct(&png_ptr_, &info_ptr_);
- }
- png_struct* png_ptr_;
- png_info* info_ptr_;
- };
- // Libpng user error and warning functions which allows us to print libpng
- // errors and warnings using Chrome's logging facilities instead of stderr.
- void LogLibPNGDecodeError(png_structp png_ptr, png_const_charp error_msg) {
- DLOG(ERROR) << "libpng decode error: " << error_msg;
- longjmp(png_jmpbuf(png_ptr), 1);
- }
- void LogLibPNGDecodeWarning(png_structp png_ptr, png_const_charp warning_msg) {
- DLOG(ERROR) << "libpng decode warning: " << warning_msg;
- }
- } // namespace
- // static
- bool PNGCodec::Decode(const unsigned char* input, size_t input_size,
- ColorFormat format, std::vector<unsigned char>* output,
- int* w, int* h) {
- PngReadStructInfo si;
- if (!si.Build(input, input_size))
- return false;
- if (setjmp(png_jmpbuf(si.png_ptr_))) {
- // The destroyer will ensure that the structures are cleaned up in this
- // case, even though we may get here as a jump from random parts of the
- // PNG library called below.
- return false;
- }
- PngDecoderState state(format, output);
- png_set_error_fn(si.png_ptr_, NULL,
- LogLibPNGDecodeError, LogLibPNGDecodeWarning);
- png_set_progressive_read_fn(si.png_ptr_, &state, &DecodeInfoCallback,
- &DecodeRowCallback, &DecodeEndCallback);
- png_process_data(si.png_ptr_,
- si.info_ptr_,
- const_cast<unsigned char*>(input),
- input_size);
- if (!state.done) {
- // Fed it all the data but the library didn't think we got all the data, so
- // this file must be truncated.
- output->clear();
- return false;
- }
- *w = state.width;
- *h = state.height;
- return true;
- }
- // static
- bool PNGCodec::Decode(const unsigned char* input, size_t input_size,
- SkBitmap* bitmap) {
- DCHECK(bitmap);
- PngReadStructInfo si;
- if (!si.Build(input, input_size))
- return false;
- if (setjmp(png_jmpbuf(si.png_ptr_))) {
- // The destroyer will ensure that the structures are cleaned up in this
- // case, even though we may get here as a jump from random parts of the
- // PNG library called below.
- return false;
- }
- PngDecoderState state(bitmap);
- png_set_progressive_read_fn(si.png_ptr_, &state, &DecodeInfoCallback,
- &DecodeRowCallback, &DecodeEndCallback);
- png_process_data(si.png_ptr_,
- si.info_ptr_,
- const_cast<unsigned char*>(input),
- input_size);
- if (!state.done) {
- return false;
- }
- // Set the bitmap's opaqueness based on what we saw.
- bitmap->setAlphaType(state.is_opaque ?
- kOpaque_SkAlphaType : kPremul_SkAlphaType);
- return true;
- }
- // Encoder --------------------------------------------------------------------
- namespace {
- static void AddComments(SkPngEncoder::Options& options,
- const std::vector<PNGCodec::Comment>& comments) {
- std::vector<const char*> comment_pointers;
- std::vector<size_t> comment_sizes;
- for (const auto& comment : comments) {
- comment_pointers.push_back(comment.key.c_str());
- comment_pointers.push_back(comment.text.c_str());
- comment_sizes.push_back(comment.key.length() + 1);
- comment_sizes.push_back(comment.text.length() + 1);
- }
- options.fComments = SkDataTable::MakeCopyArrays(
- (void const* const*)comment_pointers.data(), comment_sizes.data(),
- static_cast<int>(comment_pointers.size()));
- }
- } // namespace
- static bool EncodeSkPixmap(const SkPixmap& src,
- const std::vector<PNGCodec::Comment>& comments,
- std::vector<unsigned char>* output,
- int zlib_level) {
- output->clear();
- VectorWStream dst(output);
- SkPngEncoder::Options options;
- AddComments(options, comments);
- options.fZLibLevel = zlib_level;
- return SkPngEncoder::Encode(&dst, src, options);
- }
- static bool EncodeSkPixmap(const SkPixmap& src,
- bool discard_transparency,
- const std::vector<PNGCodec::Comment>& comments,
- std::vector<unsigned char>* output,
- int zlib_level) {
- if (discard_transparency) {
- SkImageInfo opaque_info = src.info().makeAlphaType(kOpaque_SkAlphaType);
- SkBitmap copy;
- if (!copy.tryAllocPixels(opaque_info)) {
- return false;
- }
- SkPixmap opaque_pixmap;
- bool success = copy.peekPixels(&opaque_pixmap);
- DCHECK(success);
- // The following step does the unpremul as we set the dst alpha type to be
- // kUnpremul_SkAlphaType. Later, because opaque_pixmap has
- // kOpaque_SkAlphaType, we'll discard the transparency as required.
- success =
- src.readPixels(opaque_info.makeAlphaType(kUnpremul_SkAlphaType),
- opaque_pixmap.writable_addr(), opaque_pixmap.rowBytes());
- DCHECK(success);
- return EncodeSkPixmap(opaque_pixmap, comments, output, zlib_level);
- }
- return EncodeSkPixmap(src, comments, output, zlib_level);
- }
- // static
- bool PNGCodec::Encode(const unsigned char* input,
- ColorFormat format,
- const Size& size,
- int row_byte_width,
- bool discard_transparency,
- const std::vector<Comment>& comments,
- std::vector<unsigned char>* output) {
- // Initialization required for Windows although the switch covers all cases.
- SkColorType colorType = kN32_SkColorType;
- switch (format) {
- case FORMAT_RGBA:
- colorType = kRGBA_8888_SkColorType;
- break;
- case FORMAT_BGRA:
- colorType = kBGRA_8888_SkColorType;
- break;
- case FORMAT_SkBitmap:
- colorType = kN32_SkColorType;
- break;
- }
- auto alphaType =
- format == FORMAT_SkBitmap ? kPremul_SkAlphaType : kUnpremul_SkAlphaType;
- SkImageInfo info =
- SkImageInfo::Make(size.width(), size.height(), colorType, alphaType);
- SkPixmap src(info, input, row_byte_width);
- return EncodeSkPixmap(src, discard_transparency, comments, output,
- DEFAULT_ZLIB_COMPRESSION);
- }
- static bool EncodeSkBitmap(const SkBitmap& input,
- bool discard_transparency,
- std::vector<unsigned char>* output,
- int zlib_level) {
- SkPixmap src;
- if (!input.peekPixels(&src)) {
- return false;
- }
- return EncodeSkPixmap(src, discard_transparency,
- std::vector<PNGCodec::Comment>(), output, zlib_level);
- }
- // static
- bool PNGCodec::EncodeBGRASkBitmap(const SkBitmap& input,
- bool discard_transparency,
- std::vector<unsigned char>* output) {
- return EncodeSkBitmap(input, discard_transparency, output,
- DEFAULT_ZLIB_COMPRESSION);
- }
- // static
- bool PNGCodec::EncodeA8SkBitmap(const SkBitmap& input,
- std::vector<unsigned char>* output) {
- DCHECK_EQ(input.colorType(), kAlpha_8_SkColorType);
- auto info = input.info()
- .makeColorType(kGray_8_SkColorType)
- .makeAlphaType(kOpaque_SkAlphaType);
- SkPixmap src(info, input.getAddr(0, 0), input.rowBytes());
- return EncodeSkPixmap(src, std::vector<PNGCodec::Comment>(), output,
- DEFAULT_ZLIB_COMPRESSION);
- }
- // static
- bool PNGCodec::FastEncodeBGRASkBitmap(const SkBitmap& input,
- bool discard_transparency,
- std::vector<unsigned char>* output) {
- return EncodeSkBitmap(input, discard_transparency, output, Z_BEST_SPEED);
- }
- PNGCodec::Comment::Comment(const std::string& k, const std::string& t)
- : key(k), text(t) {
- }
- PNGCodec::Comment::~Comment() {
- }
- } // namespace gfx
|