michael@0: michael@0: /* michael@0: * Copyright 2006 The Android Open Source Project michael@0: * michael@0: * Use of this source code is governed by a BSD-style license that can be michael@0: * found in the LICENSE file. michael@0: */ michael@0: michael@0: michael@0: #include "SkImageDecoder.h" michael@0: #include "SkImageEncoder.h" michael@0: #include "SkColor.h" michael@0: #include "SkColorPriv.h" michael@0: #include "SkDither.h" michael@0: #include "SkMath.h" michael@0: #include "SkRTConf.h" michael@0: #include "SkScaledBitmapSampler.h" michael@0: #include "SkStream.h" michael@0: #include "SkTemplates.h" michael@0: #include "SkUtils.h" michael@0: #include "transform_scanline.h" michael@0: extern "C" { michael@0: #include "png.h" michael@0: } michael@0: michael@0: /* These were dropped in libpng >= 1.4 */ michael@0: #ifndef png_infopp_NULL michael@0: #define png_infopp_NULL NULL michael@0: #endif michael@0: michael@0: #ifndef png_bytepp_NULL michael@0: #define png_bytepp_NULL NULL michael@0: #endif michael@0: michael@0: #ifndef int_p_NULL michael@0: #define int_p_NULL NULL michael@0: #endif michael@0: michael@0: #ifndef png_flush_ptr_NULL michael@0: #define png_flush_ptr_NULL NULL michael@0: #endif michael@0: michael@0: #if defined(SK_DEBUG) michael@0: #define DEFAULT_FOR_SUPPRESS_PNG_IMAGE_DECODER_WARNINGS false michael@0: #else // !defined(SK_DEBUG) michael@0: #define DEFAULT_FOR_SUPPRESS_PNG_IMAGE_DECODER_WARNINGS true michael@0: #endif // defined(SK_DEBUG) michael@0: SK_CONF_DECLARE(bool, c_suppressPNGImageDecoderWarnings, michael@0: "images.png.suppressDecoderWarnings", michael@0: DEFAULT_FOR_SUPPRESS_PNG_IMAGE_DECODER_WARNINGS, michael@0: "Suppress most PNG warnings when calling image decode " michael@0: "functions."); michael@0: michael@0: michael@0: michael@0: class SkPNGImageIndex { michael@0: public: michael@0: SkPNGImageIndex(SkStreamRewindable* stream, png_structp png_ptr, png_infop info_ptr) michael@0: : fStream(stream) michael@0: , fPng_ptr(png_ptr) michael@0: , fInfo_ptr(info_ptr) michael@0: , fConfig(SkBitmap::kNo_Config) { michael@0: SkASSERT(stream != NULL); michael@0: stream->ref(); michael@0: } michael@0: ~SkPNGImageIndex() { michael@0: if (NULL != fPng_ptr) { michael@0: png_destroy_read_struct(&fPng_ptr, &fInfo_ptr, png_infopp_NULL); michael@0: } michael@0: } michael@0: michael@0: SkAutoTUnref fStream; michael@0: png_structp fPng_ptr; michael@0: png_infop fInfo_ptr; michael@0: SkBitmap::Config fConfig; michael@0: }; michael@0: michael@0: class SkPNGImageDecoder : public SkImageDecoder { michael@0: public: michael@0: SkPNGImageDecoder() { michael@0: fImageIndex = NULL; michael@0: } michael@0: virtual Format getFormat() const SK_OVERRIDE { michael@0: return kPNG_Format; michael@0: } michael@0: michael@0: virtual ~SkPNGImageDecoder() { michael@0: SkDELETE(fImageIndex); michael@0: } michael@0: michael@0: protected: michael@0: #ifdef SK_BUILD_FOR_ANDROID michael@0: virtual bool onBuildTileIndex(SkStreamRewindable *stream, int *width, int *height) SK_OVERRIDE; michael@0: virtual bool onDecodeSubset(SkBitmap* bitmap, const SkIRect& region) SK_OVERRIDE; michael@0: #endif michael@0: virtual bool onDecode(SkStream* stream, SkBitmap* bm, Mode) SK_OVERRIDE; michael@0: michael@0: private: michael@0: SkPNGImageIndex* fImageIndex; michael@0: michael@0: bool onDecodeInit(SkStream* stream, png_structp *png_ptrp, png_infop *info_ptrp); michael@0: bool decodePalette(png_structp png_ptr, png_infop info_ptr, michael@0: bool * SK_RESTRICT hasAlphap, bool *reallyHasAlphap, michael@0: SkColorTable **colorTablep); michael@0: bool getBitmapConfig(png_structp png_ptr, png_infop info_ptr, michael@0: SkBitmap::Config *config, bool *hasAlpha, michael@0: SkPMColor *theTranspColor); michael@0: michael@0: typedef SkImageDecoder INHERITED; michael@0: }; michael@0: michael@0: #ifndef png_jmpbuf michael@0: # define png_jmpbuf(png_ptr) ((png_ptr)->jmpbuf) michael@0: #endif michael@0: michael@0: #define PNG_BYTES_TO_CHECK 4 michael@0: michael@0: /* Automatically clean up after throwing an exception */ michael@0: struct PNGAutoClean { michael@0: PNGAutoClean(png_structp p, png_infop i): png_ptr(p), info_ptr(i) {} michael@0: ~PNGAutoClean() { michael@0: png_destroy_read_struct(&png_ptr, &info_ptr, png_infopp_NULL); michael@0: } michael@0: private: michael@0: png_structp png_ptr; michael@0: png_infop info_ptr; michael@0: }; michael@0: michael@0: static void sk_read_fn(png_structp png_ptr, png_bytep data, png_size_t length) { michael@0: SkStream* sk_stream = (SkStream*) png_get_io_ptr(png_ptr); michael@0: size_t bytes = sk_stream->read(data, length); michael@0: if (bytes != length) { michael@0: png_error(png_ptr, "Read Error!"); michael@0: } michael@0: } michael@0: michael@0: #ifdef SK_BUILD_FOR_ANDROID michael@0: static void sk_seek_fn(png_structp png_ptr, png_uint_32 offset) { michael@0: SkStreamRewindable* sk_stream = (SkStreamRewindable*) png_get_io_ptr(png_ptr); michael@0: if (!sk_stream->rewind()) { michael@0: png_error(png_ptr, "Failed to rewind stream!"); michael@0: } michael@0: (void)sk_stream->skip(offset); michael@0: } michael@0: #endif michael@0: michael@0: static int sk_read_user_chunk(png_structp png_ptr, png_unknown_chunkp chunk) { michael@0: SkImageDecoder::Peeker* peeker = michael@0: (SkImageDecoder::Peeker*)png_get_user_chunk_ptr(png_ptr); michael@0: // peek() returning true means continue decoding michael@0: return peeker->peek((const char*)chunk->name, chunk->data, chunk->size) ? michael@0: 1 : -1; michael@0: } michael@0: michael@0: static void sk_error_fn(png_structp png_ptr, png_const_charp msg) { michael@0: SkDEBUGF(("------ png error %s\n", msg)); michael@0: longjmp(png_jmpbuf(png_ptr), 1); michael@0: } michael@0: michael@0: static void skip_src_rows(png_structp png_ptr, uint8_t storage[], int count) { michael@0: for (int i = 0; i < count; i++) { michael@0: uint8_t* tmp = storage; michael@0: png_read_rows(png_ptr, &tmp, png_bytepp_NULL, 1); michael@0: } michael@0: } michael@0: michael@0: static bool pos_le(int value, int max) { michael@0: return value > 0 && value <= max; michael@0: } michael@0: michael@0: static bool substituteTranspColor(SkBitmap* bm, SkPMColor match) { michael@0: SkASSERT(bm->config() == SkBitmap::kARGB_8888_Config); michael@0: michael@0: bool reallyHasAlpha = false; michael@0: michael@0: for (int y = bm->height() - 1; y >= 0; --y) { michael@0: SkPMColor* p = bm->getAddr32(0, y); michael@0: for (int x = bm->width() - 1; x >= 0; --x) { michael@0: if (match == *p) { michael@0: *p = 0; michael@0: reallyHasAlpha = true; michael@0: } michael@0: p += 1; michael@0: } michael@0: } michael@0: return reallyHasAlpha; michael@0: } michael@0: michael@0: static bool canUpscalePaletteToConfig(SkBitmap::Config dstConfig, michael@0: bool srcHasAlpha) { michael@0: switch (dstConfig) { michael@0: case SkBitmap::kARGB_8888_Config: michael@0: case SkBitmap::kARGB_4444_Config: michael@0: return true; michael@0: case SkBitmap::kRGB_565_Config: michael@0: // only return true if the src is opaque (since 565 is opaque) michael@0: return !srcHasAlpha; michael@0: default: michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: // call only if color_type is PALETTE. Returns true if the ctable has alpha michael@0: static bool hasTransparencyInPalette(png_structp png_ptr, png_infop info_ptr) { michael@0: png_bytep trans; michael@0: int num_trans; michael@0: michael@0: if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) { michael@0: png_get_tRNS(png_ptr, info_ptr, &trans, &num_trans, NULL); michael@0: return num_trans > 0; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: void do_nothing_warning_fn(png_structp, png_const_charp) { michael@0: /* do nothing */ michael@0: } michael@0: michael@0: bool SkPNGImageDecoder::onDecodeInit(SkStream* sk_stream, png_structp *png_ptrp, michael@0: png_infop *info_ptrp) { michael@0: /* Create and initialize the png_struct with the desired error handler michael@0: * functions. If you want to use the default stderr and longjump method, michael@0: * you can supply NULL for the last three parameters. We also supply the michael@0: * the compiler header file version, so that we know if the application michael@0: * was compiled with a compatible version of the library. */ michael@0: michael@0: png_error_ptr user_warning_fn = michael@0: (c_suppressPNGImageDecoderWarnings) ? (&do_nothing_warning_fn) : NULL; michael@0: /* NULL means to leave as default library behavior. */ michael@0: /* c_suppressPNGImageDecoderWarnings default depends on SK_DEBUG. */ michael@0: /* To suppress warnings with a SK_DEBUG binary, set the michael@0: * environment variable "skia_images_png_suppressDecoderWarnings" michael@0: * to "true". Inside a program that links to skia: michael@0: * SK_CONF_SET("images.png.suppressDecoderWarnings", true); */ michael@0: michael@0: png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, michael@0: NULL, sk_error_fn, user_warning_fn); michael@0: // png_voidp user_error_ptr, user_error_fn, user_warning_fn); michael@0: if (png_ptr == NULL) { michael@0: return false; michael@0: } michael@0: michael@0: *png_ptrp = png_ptr; michael@0: michael@0: /* Allocate/initialize the memory for image information. */ michael@0: png_infop info_ptr = png_create_info_struct(png_ptr); michael@0: if (info_ptr == NULL) { michael@0: png_destroy_read_struct(&png_ptr, png_infopp_NULL, png_infopp_NULL); michael@0: return false; michael@0: } michael@0: *info_ptrp = info_ptr; michael@0: michael@0: /* Set error handling if you are using the setjmp/longjmp method (this is michael@0: * the normal method of doing things with libpng). REQUIRED unless you michael@0: * set up your own error handlers in the png_create_read_struct() earlier. michael@0: */ michael@0: if (setjmp(png_jmpbuf(png_ptr))) { michael@0: png_destroy_read_struct(&png_ptr, &info_ptr, png_infopp_NULL); michael@0: return false; michael@0: } michael@0: michael@0: /* If you are using replacement read functions, instead of calling michael@0: * png_init_io() here you would call: michael@0: */ michael@0: png_set_read_fn(png_ptr, (void *)sk_stream, sk_read_fn); michael@0: #ifdef SK_BUILD_FOR_ANDROID michael@0: png_set_seek_fn(png_ptr, sk_seek_fn); michael@0: #endif michael@0: /* where user_io_ptr is a structure you want available to the callbacks */ michael@0: /* If we have already read some of the signature */ michael@0: // png_set_sig_bytes(png_ptr, 0 /* sig_read */ ); michael@0: michael@0: // hookup our peeker so we can see any user-chunks the caller may be interested in michael@0: png_set_keep_unknown_chunks(png_ptr, PNG_HANDLE_CHUNK_ALWAYS, (png_byte*)"", 0); michael@0: if (this->getPeeker()) { michael@0: png_set_read_user_chunk_fn(png_ptr, (png_voidp)this->getPeeker(), sk_read_user_chunk); michael@0: } michael@0: michael@0: /* The call to png_read_info() gives us all of the information from the michael@0: * PNG file before the first IDAT (image data chunk). */ michael@0: png_read_info(png_ptr, info_ptr); michael@0: png_uint_32 origWidth, origHeight; michael@0: int bitDepth, colorType; michael@0: png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth, michael@0: &colorType, int_p_NULL, int_p_NULL, int_p_NULL); michael@0: michael@0: /* tell libpng to strip 16 bit/color files down to 8 bits/color */ michael@0: if (bitDepth == 16) { michael@0: png_set_strip_16(png_ptr); michael@0: } michael@0: /* Extract multiple pixels with bit depths of 1, 2, and 4 from a single michael@0: * byte into separate bytes (useful for paletted and grayscale images). */ michael@0: if (bitDepth < 8) { michael@0: png_set_packing(png_ptr); michael@0: } michael@0: /* Expand grayscale images to the full 8 bits from 1, 2, or 4 bits/pixel */ michael@0: if (colorType == PNG_COLOR_TYPE_GRAY && bitDepth < 8) { michael@0: png_set_expand_gray_1_2_4_to_8(png_ptr); michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool SkPNGImageDecoder::onDecode(SkStream* sk_stream, SkBitmap* decodedBitmap, michael@0: Mode mode) { michael@0: png_structp png_ptr; michael@0: png_infop info_ptr; michael@0: michael@0: if (!onDecodeInit(sk_stream, &png_ptr, &info_ptr)) { michael@0: return false; michael@0: } michael@0: michael@0: PNGAutoClean autoClean(png_ptr, info_ptr); michael@0: michael@0: if (setjmp(png_jmpbuf(png_ptr))) { michael@0: return false; michael@0: } michael@0: michael@0: png_uint_32 origWidth, origHeight; michael@0: int bitDepth, colorType, interlaceType; michael@0: png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth, michael@0: &colorType, &interlaceType, int_p_NULL, int_p_NULL); michael@0: michael@0: SkBitmap::Config config; michael@0: bool hasAlpha = false; michael@0: SkPMColor theTranspColor = 0; // 0 tells us not to try to match michael@0: michael@0: if (!this->getBitmapConfig(png_ptr, info_ptr, &config, &hasAlpha, &theTranspColor)) { michael@0: return false; michael@0: } michael@0: michael@0: const int sampleSize = this->getSampleSize(); michael@0: SkScaledBitmapSampler sampler(origWidth, origHeight, sampleSize); michael@0: decodedBitmap->setConfig(config, sampler.scaledWidth(), sampler.scaledHeight()); michael@0: michael@0: // we should communicate alphaType, even if we early-return in bounds-only-mode. michael@0: if (this->getRequireUnpremultipliedColors()) { michael@0: decodedBitmap->setAlphaType(kUnpremul_SkAlphaType); michael@0: } michael@0: michael@0: if (SkImageDecoder::kDecodeBounds_Mode == mode) { michael@0: return true; michael@0: } michael@0: michael@0: // from here down we are concerned with colortables and pixels michael@0: michael@0: // we track if we actually see a non-opaque pixels, since sometimes a PNG sets its colortype michael@0: // to |= PNG_COLOR_MASK_ALPHA, but all of its pixels are in fact opaque. We care, since we michael@0: // draw lots faster if we can flag the bitmap has being opaque michael@0: bool reallyHasAlpha = false; michael@0: SkColorTable* colorTable = NULL; michael@0: michael@0: if (colorType == PNG_COLOR_TYPE_PALETTE) { michael@0: decodePalette(png_ptr, info_ptr, &hasAlpha, &reallyHasAlpha, &colorTable); michael@0: } michael@0: michael@0: SkAutoUnref aur(colorTable); michael@0: michael@0: if (!this->allocPixelRef(decodedBitmap, michael@0: SkBitmap::kIndex8_Config == config ? colorTable : NULL)) { michael@0: return false; michael@0: } michael@0: michael@0: SkAutoLockPixels alp(*decodedBitmap); michael@0: michael@0: /* Turn on interlace handling. REQUIRED if you are not using michael@0: * png_read_image(). To see how to handle interlacing passes, michael@0: * see the png_read_row() method below: michael@0: */ michael@0: const int number_passes = (interlaceType != PNG_INTERLACE_NONE) ? michael@0: png_set_interlace_handling(png_ptr) : 1; michael@0: michael@0: /* Optional call to gamma correct and add the background to the palette michael@0: * and update info structure. REQUIRED if you are expecting libpng to michael@0: * update the palette for you (ie you selected such a transform above). michael@0: */ michael@0: png_read_update_info(png_ptr, info_ptr); michael@0: michael@0: if ((SkBitmap::kA8_Config == config || SkBitmap::kIndex8_Config == config) michael@0: && 1 == sampleSize) { michael@0: if (SkBitmap::kA8_Config == config) { michael@0: // For an A8 bitmap, we assume there is an alpha for speed. It is michael@0: // possible the bitmap is opaque, but that is an unlikely use case michael@0: // since it would not be very interesting. michael@0: reallyHasAlpha = true; michael@0: // A8 is only allowed if the original was GRAY. michael@0: SkASSERT(PNG_COLOR_TYPE_GRAY == colorType); michael@0: } michael@0: for (int i = 0; i < number_passes; i++) { michael@0: for (png_uint_32 y = 0; y < origHeight; y++) { michael@0: uint8_t* bmRow = decodedBitmap->getAddr8(0, y); michael@0: png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1); michael@0: } michael@0: } michael@0: } else { michael@0: SkScaledBitmapSampler::SrcConfig sc; michael@0: int srcBytesPerPixel = 4; michael@0: michael@0: if (colorTable != NULL) { michael@0: sc = SkScaledBitmapSampler::kIndex; michael@0: srcBytesPerPixel = 1; michael@0: } else if (SkBitmap::kA8_Config == config) { michael@0: // A8 is only allowed if the original was GRAY. michael@0: SkASSERT(PNG_COLOR_TYPE_GRAY == colorType); michael@0: sc = SkScaledBitmapSampler::kGray; michael@0: srcBytesPerPixel = 1; michael@0: } else if (hasAlpha) { michael@0: sc = SkScaledBitmapSampler::kRGBA; michael@0: } else { michael@0: sc = SkScaledBitmapSampler::kRGBX; michael@0: } michael@0: michael@0: /* We have to pass the colortable explicitly, since we may have one michael@0: even if our decodedBitmap doesn't, due to the request that we michael@0: upscale png's palette to a direct model michael@0: */ michael@0: SkAutoLockColors ctLock(colorTable); michael@0: if (!sampler.begin(decodedBitmap, sc, *this, ctLock.colors())) { michael@0: return false; michael@0: } michael@0: const int height = decodedBitmap->height(); michael@0: michael@0: if (number_passes > 1) { michael@0: SkAutoMalloc storage(origWidth * origHeight * srcBytesPerPixel); michael@0: uint8_t* base = (uint8_t*)storage.get(); michael@0: size_t rowBytes = origWidth * srcBytesPerPixel; michael@0: michael@0: for (int i = 0; i < number_passes; i++) { michael@0: uint8_t* row = base; michael@0: for (png_uint_32 y = 0; y < origHeight; y++) { michael@0: uint8_t* bmRow = row; michael@0: png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1); michael@0: row += rowBytes; michael@0: } michael@0: } michael@0: // now sample it michael@0: base += sampler.srcY0() * rowBytes; michael@0: for (int y = 0; y < height; y++) { michael@0: reallyHasAlpha |= sampler.next(base); michael@0: base += sampler.srcDY() * rowBytes; michael@0: } michael@0: } else { michael@0: SkAutoMalloc storage(origWidth * srcBytesPerPixel); michael@0: uint8_t* srcRow = (uint8_t*)storage.get(); michael@0: skip_src_rows(png_ptr, srcRow, sampler.srcY0()); michael@0: michael@0: for (int y = 0; y < height; y++) { michael@0: uint8_t* tmp = srcRow; michael@0: png_read_rows(png_ptr, &tmp, png_bytepp_NULL, 1); michael@0: reallyHasAlpha |= sampler.next(srcRow); michael@0: if (y < height - 1) { michael@0: skip_src_rows(png_ptr, srcRow, sampler.srcDY() - 1); michael@0: } michael@0: } michael@0: michael@0: // skip the rest of the rows (if any) michael@0: png_uint_32 read = (height - 1) * sampler.srcDY() + michael@0: sampler.srcY0() + 1; michael@0: SkASSERT(read <= origHeight); michael@0: skip_src_rows(png_ptr, srcRow, origHeight - read); michael@0: } michael@0: } michael@0: michael@0: /* read rest of file, and get additional chunks in info_ptr - REQUIRED */ michael@0: png_read_end(png_ptr, info_ptr); michael@0: michael@0: if (0 != theTranspColor) { michael@0: reallyHasAlpha |= substituteTranspColor(decodedBitmap, theTranspColor); michael@0: } michael@0: if (reallyHasAlpha && this->getRequireUnpremultipliedColors()) { michael@0: switch (decodedBitmap->config()) { michael@0: case SkBitmap::kIndex8_Config: michael@0: // Fall through. michael@0: case SkBitmap::kARGB_4444_Config: michael@0: // We have chosen not to support unpremul for these configs. michael@0: return false; michael@0: default: { michael@0: // Fall through to finish the decode. This config either michael@0: // supports unpremul or it is irrelevant because it has no michael@0: // alpha (or only alpha). michael@0: // These brackets prevent a warning. michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (!reallyHasAlpha) { michael@0: decodedBitmap->setAlphaType(kOpaque_SkAlphaType); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: michael@0: michael@0: bool SkPNGImageDecoder::getBitmapConfig(png_structp png_ptr, png_infop info_ptr, michael@0: SkBitmap::Config* SK_RESTRICT configp, michael@0: bool* SK_RESTRICT hasAlphap, michael@0: SkPMColor* SK_RESTRICT theTranspColorp) { michael@0: png_uint_32 origWidth, origHeight; michael@0: int bitDepth, colorType; michael@0: png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth, michael@0: &colorType, int_p_NULL, int_p_NULL, int_p_NULL); michael@0: michael@0: // check for sBIT chunk data, in case we should disable dithering because michael@0: // our data is not truely 8bits per component michael@0: png_color_8p sig_bit; michael@0: if (this->getDitherImage() && png_get_sBIT(png_ptr, info_ptr, &sig_bit)) { michael@0: #if 0 michael@0: SkDebugf("----- sBIT %d %d %d %d\n", sig_bit->red, sig_bit->green, michael@0: sig_bit->blue, sig_bit->alpha); michael@0: #endif michael@0: // 0 seems to indicate no information available michael@0: if (pos_le(sig_bit->red, SK_R16_BITS) && michael@0: pos_le(sig_bit->green, SK_G16_BITS) && michael@0: pos_le(sig_bit->blue, SK_B16_BITS)) { michael@0: this->setDitherImage(false); michael@0: } michael@0: } michael@0: michael@0: if (colorType == PNG_COLOR_TYPE_PALETTE) { michael@0: bool paletteHasAlpha = hasTransparencyInPalette(png_ptr, info_ptr); michael@0: *configp = this->getPrefConfig(kIndex_SrcDepth, paletteHasAlpha); michael@0: // now see if we can upscale to their requested config michael@0: if (!canUpscalePaletteToConfig(*configp, paletteHasAlpha)) { michael@0: *configp = SkBitmap::kIndex8_Config; michael@0: } michael@0: } else { michael@0: png_color_16p transpColor = NULL; michael@0: int numTransp = 0; michael@0: michael@0: png_get_tRNS(png_ptr, info_ptr, NULL, &numTransp, &transpColor); michael@0: michael@0: bool valid = png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS); michael@0: michael@0: if (valid && numTransp == 1 && transpColor != NULL) { michael@0: /* Compute our transparent color, which we'll match against later. michael@0: We don't really handle 16bit components properly here, since we michael@0: do our compare *after* the values have been knocked down to 8bit michael@0: which means we will find more matches than we should. The real michael@0: fix seems to be to see the actual 16bit components, do the michael@0: compare, and then knock it down to 8bits ourselves. michael@0: */ michael@0: if (colorType & PNG_COLOR_MASK_COLOR) { michael@0: if (16 == bitDepth) { michael@0: *theTranspColorp = SkPackARGB32(0xFF, transpColor->red >> 8, michael@0: transpColor->green >> 8, michael@0: transpColor->blue >> 8); michael@0: } else { michael@0: /* We apply the mask because in a very small michael@0: number of corrupt PNGs, (transpColor->red > 255) michael@0: and (bitDepth == 8), for certain versions of libpng. */ michael@0: *theTranspColorp = SkPackARGB32(0xFF, michael@0: 0xFF & (transpColor->red), michael@0: 0xFF & (transpColor->green), michael@0: 0xFF & (transpColor->blue)); michael@0: } michael@0: } else { // gray michael@0: if (16 == bitDepth) { michael@0: *theTranspColorp = SkPackARGB32(0xFF, transpColor->gray >> 8, michael@0: transpColor->gray >> 8, michael@0: transpColor->gray >> 8); michael@0: } else { michael@0: /* We apply the mask because in a very small michael@0: number of corrupt PNGs, (transpColor->red > michael@0: 255) and (bitDepth == 8), for certain versions michael@0: of libpng. For safety we assume the same could michael@0: happen with a grayscale PNG. */ michael@0: *theTranspColorp = SkPackARGB32(0xFF, michael@0: 0xFF & (transpColor->gray), michael@0: 0xFF & (transpColor->gray), michael@0: 0xFF & (transpColor->gray)); michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (valid || michael@0: PNG_COLOR_TYPE_RGB_ALPHA == colorType || michael@0: PNG_COLOR_TYPE_GRAY_ALPHA == colorType) { michael@0: *hasAlphap = true; michael@0: } michael@0: michael@0: SrcDepth srcDepth = k32Bit_SrcDepth; michael@0: if (PNG_COLOR_TYPE_GRAY == colorType) { michael@0: srcDepth = k8BitGray_SrcDepth; michael@0: // Remove this assert, which fails on desk_pokemonwiki.skp michael@0: //SkASSERT(!*hasAlphap); michael@0: } michael@0: michael@0: *configp = this->getPrefConfig(srcDepth, *hasAlphap); michael@0: // now match the request against our capabilities michael@0: if (*hasAlphap) { michael@0: if (*configp != SkBitmap::kARGB_4444_Config) { michael@0: *configp = SkBitmap::kARGB_8888_Config; michael@0: } michael@0: } else { michael@0: if (SkBitmap::kA8_Config == *configp) { michael@0: if (k8BitGray_SrcDepth != srcDepth) { michael@0: // Converting a non grayscale image to A8 is not currently supported. michael@0: *configp = SkBitmap::kARGB_8888_Config; michael@0: } michael@0: } else if (*configp != SkBitmap::kRGB_565_Config && michael@0: *configp != SkBitmap::kARGB_4444_Config) { michael@0: *configp = SkBitmap::kARGB_8888_Config; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // sanity check for size michael@0: { michael@0: int64_t size = sk_64_mul(origWidth, origHeight); michael@0: // now check that if we are 4-bytes per pixel, we also don't overflow michael@0: if (size < 0 || size > (0x7FFFFFFF >> 2)) { michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: if (!this->chooseFromOneChoice(*configp, origWidth, origHeight)) { michael@0: return false; michael@0: } michael@0: michael@0: // If the image has alpha and the decoder wants unpremultiplied michael@0: // colors, the only supported config is 8888. michael@0: if (this->getRequireUnpremultipliedColors() && *hasAlphap) { michael@0: *configp = SkBitmap::kARGB_8888_Config; michael@0: } michael@0: michael@0: if (fImageIndex != NULL) { michael@0: if (SkBitmap::kNo_Config == fImageIndex->fConfig) { michael@0: // This is the first time for this subset decode. From now on, michael@0: // all decodes must be in the same config. michael@0: fImageIndex->fConfig = *configp; michael@0: } else if (fImageIndex->fConfig != *configp) { michael@0: // Requesting a different config for a subsequent decode is not michael@0: // supported. Report failure before we make changes to png_ptr. michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: bool convertGrayToRGB = PNG_COLOR_TYPE_GRAY == colorType michael@0: && *configp != SkBitmap::kA8_Config; michael@0: michael@0: // Unless the user is requesting A8, convert a grayscale image into RGB. michael@0: // GRAY_ALPHA will always be converted to RGB michael@0: if (convertGrayToRGB || colorType == PNG_COLOR_TYPE_GRAY_ALPHA) { michael@0: png_set_gray_to_rgb(png_ptr); michael@0: } michael@0: michael@0: // Add filler (or alpha) byte (after each RGB triplet) if necessary. michael@0: if (colorType == PNG_COLOR_TYPE_RGB || convertGrayToRGB) { michael@0: png_set_filler(png_ptr, 0xff, PNG_FILLER_AFTER); michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: typedef uint32_t (*PackColorProc)(U8CPU a, U8CPU r, U8CPU g, U8CPU b); michael@0: michael@0: bool SkPNGImageDecoder::decodePalette(png_structp png_ptr, png_infop info_ptr, michael@0: bool *hasAlphap, bool *reallyHasAlphap, michael@0: SkColorTable **colorTablep) { michael@0: int numPalette; michael@0: png_colorp palette; michael@0: png_bytep trans; michael@0: int numTrans; michael@0: michael@0: png_get_PLTE(png_ptr, info_ptr, &palette, &numPalette); michael@0: michael@0: /* BUGGY IMAGE WORKAROUND michael@0: michael@0: We hit some images (e.g. fruit_.png) who contain bytes that are == colortable_count michael@0: which is a problem since we use the byte as an index. To work around this we grow michael@0: the colortable by 1 (if its < 256) and duplicate the last color into that slot. michael@0: */ michael@0: int colorCount = numPalette + (numPalette < 256); michael@0: SkPMColor colorStorage[256]; // worst-case storage michael@0: SkPMColor* colorPtr = colorStorage; michael@0: michael@0: if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) { michael@0: png_get_tRNS(png_ptr, info_ptr, &trans, &numTrans, NULL); michael@0: *hasAlphap = (numTrans > 0); michael@0: } else { michael@0: numTrans = 0; michael@0: } michael@0: michael@0: // check for bad images that might make us crash michael@0: if (numTrans > numPalette) { michael@0: numTrans = numPalette; michael@0: } michael@0: michael@0: int index = 0; michael@0: int transLessThanFF = 0; michael@0: michael@0: // Choose which function to use to create the color table. If the final destination's michael@0: // config is unpremultiplied, the color table will store unpremultiplied colors. michael@0: PackColorProc proc; michael@0: if (this->getRequireUnpremultipliedColors()) { michael@0: proc = &SkPackARGB32NoCheck; michael@0: } else { michael@0: proc = &SkPreMultiplyARGB; michael@0: } michael@0: for (; index < numTrans; index++) { michael@0: transLessThanFF |= (int)*trans - 0xFF; michael@0: *colorPtr++ = proc(*trans++, palette->red, palette->green, palette->blue); michael@0: palette++; michael@0: } michael@0: bool reallyHasAlpha = (transLessThanFF < 0); michael@0: michael@0: for (; index < numPalette; index++) { michael@0: *colorPtr++ = SkPackARGB32(0xFF, palette->red, palette->green, palette->blue); michael@0: palette++; michael@0: } michael@0: michael@0: // see BUGGY IMAGE WORKAROUND comment above michael@0: if (numPalette < 256) { michael@0: *colorPtr = colorPtr[-1]; michael@0: } michael@0: michael@0: SkAlphaType alphaType = kOpaque_SkAlphaType; michael@0: if (reallyHasAlpha) { michael@0: if (this->getRequireUnpremultipliedColors()) { michael@0: alphaType = kUnpremul_SkAlphaType; michael@0: } else { michael@0: alphaType = kPremul_SkAlphaType; michael@0: } michael@0: } michael@0: michael@0: *colorTablep = SkNEW_ARGS(SkColorTable, michael@0: (colorStorage, colorCount, alphaType)); michael@0: *reallyHasAlphap = reallyHasAlpha; michael@0: return true; michael@0: } michael@0: michael@0: #ifdef SK_BUILD_FOR_ANDROID michael@0: michael@0: bool SkPNGImageDecoder::onBuildTileIndex(SkStreamRewindable* sk_stream, int *width, int *height) { michael@0: png_structp png_ptr; michael@0: png_infop info_ptr; michael@0: michael@0: if (!onDecodeInit(sk_stream, &png_ptr, &info_ptr)) { michael@0: return false; michael@0: } michael@0: michael@0: if (setjmp(png_jmpbuf(png_ptr)) != 0) { michael@0: png_destroy_read_struct(&png_ptr, &info_ptr, png_infopp_NULL); michael@0: return false; michael@0: } michael@0: michael@0: png_uint_32 origWidth, origHeight; michael@0: int bitDepth, colorType; michael@0: png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth, michael@0: &colorType, int_p_NULL, int_p_NULL, int_p_NULL); michael@0: michael@0: *width = origWidth; michael@0: *height = origHeight; michael@0: michael@0: png_build_index(png_ptr); michael@0: michael@0: if (fImageIndex) { michael@0: SkDELETE(fImageIndex); michael@0: } michael@0: fImageIndex = SkNEW_ARGS(SkPNGImageIndex, (sk_stream, png_ptr, info_ptr)); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool SkPNGImageDecoder::onDecodeSubset(SkBitmap* bm, const SkIRect& region) { michael@0: if (NULL == fImageIndex) { michael@0: return false; michael@0: } michael@0: michael@0: png_structp png_ptr = fImageIndex->fPng_ptr; michael@0: png_infop info_ptr = fImageIndex->fInfo_ptr; michael@0: if (setjmp(png_jmpbuf(png_ptr))) { michael@0: return false; michael@0: } michael@0: michael@0: png_uint_32 origWidth, origHeight; michael@0: int bitDepth, colorType, interlaceType; michael@0: png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth, michael@0: &colorType, &interlaceType, int_p_NULL, int_p_NULL); michael@0: michael@0: SkIRect rect = SkIRect::MakeWH(origWidth, origHeight); michael@0: michael@0: if (!rect.intersect(region)) { michael@0: // If the requested region is entirely outside the image, just michael@0: // returns false michael@0: return false; michael@0: } michael@0: michael@0: SkBitmap::Config config; michael@0: bool hasAlpha = false; michael@0: SkPMColor theTranspColor = 0; // 0 tells us not to try to match michael@0: michael@0: if (!this->getBitmapConfig(png_ptr, info_ptr, &config, &hasAlpha, &theTranspColor)) { michael@0: return false; michael@0: } michael@0: michael@0: const int sampleSize = this->getSampleSize(); michael@0: SkScaledBitmapSampler sampler(origWidth, rect.height(), sampleSize); michael@0: michael@0: SkBitmap decodedBitmap; michael@0: decodedBitmap.setConfig(config, sampler.scaledWidth(), sampler.scaledHeight()); michael@0: michael@0: // from here down we are concerned with colortables and pixels michael@0: michael@0: // we track if we actually see a non-opaque pixels, since sometimes a PNG sets its colortype michael@0: // to |= PNG_COLOR_MASK_ALPHA, but all of its pixels are in fact opaque. We care, since we michael@0: // draw lots faster if we can flag the bitmap has being opaque michael@0: bool reallyHasAlpha = false; michael@0: SkColorTable* colorTable = NULL; michael@0: michael@0: if (colorType == PNG_COLOR_TYPE_PALETTE) { michael@0: decodePalette(png_ptr, info_ptr, &hasAlpha, &reallyHasAlpha, &colorTable); michael@0: } michael@0: michael@0: SkAutoUnref aur(colorTable); michael@0: michael@0: // Check ahead of time if the swap(dest, src) is possible. michael@0: // If yes, then we will stick to AllocPixelRef since it's cheaper with the swap happening. michael@0: // If no, then we will use alloc to allocate pixels to prevent garbage collection. michael@0: int w = rect.width() / sampleSize; michael@0: int h = rect.height() / sampleSize; michael@0: const bool swapOnly = (rect == region) && (w == decodedBitmap.width()) && michael@0: (h == decodedBitmap.height()) && bm->isNull(); michael@0: const bool needColorTable = SkBitmap::kIndex8_Config == config; michael@0: if (swapOnly) { michael@0: if (!this->allocPixelRef(&decodedBitmap, needColorTable ? colorTable : NULL)) { michael@0: return false; michael@0: } michael@0: } else { michael@0: if (!decodedBitmap.allocPixels(NULL, needColorTable ? colorTable : NULL)) { michael@0: return false; michael@0: } michael@0: } michael@0: SkAutoLockPixels alp(decodedBitmap); michael@0: michael@0: /* Turn on interlace handling. REQUIRED if you are not using michael@0: * png_read_image(). To see how to handle interlacing passes, michael@0: * see the png_read_row() method below: michael@0: */ michael@0: const int number_passes = (interlaceType != PNG_INTERLACE_NONE) ? michael@0: png_set_interlace_handling(png_ptr) : 1; michael@0: michael@0: /* Optional call to gamma correct and add the background to the palette michael@0: * and update info structure. REQUIRED if you are expecting libpng to michael@0: * update the palette for you (ie you selected such a transform above). michael@0: */ michael@0: michael@0: // Direct access to png_ptr fields is deprecated in libpng > 1.2. michael@0: #if defined(PNG_1_0_X) || defined (PNG_1_2_X) michael@0: png_ptr->pass = 0; michael@0: #else michael@0: // FIXME: This sets pass as desired, but also sets iwidth. Is that ok? michael@0: png_set_interlaced_pass(png_ptr, 0); michael@0: #endif michael@0: png_read_update_info(png_ptr, info_ptr); michael@0: michael@0: int actualTop = rect.fTop; michael@0: michael@0: if ((SkBitmap::kA8_Config == config || SkBitmap::kIndex8_Config == config) michael@0: && 1 == sampleSize) { michael@0: if (SkBitmap::kA8_Config == config) { michael@0: // For an A8 bitmap, we assume there is an alpha for speed. It is michael@0: // possible the bitmap is opaque, but that is an unlikely use case michael@0: // since it would not be very interesting. michael@0: reallyHasAlpha = true; michael@0: // A8 is only allowed if the original was GRAY. michael@0: SkASSERT(PNG_COLOR_TYPE_GRAY == colorType); michael@0: } michael@0: michael@0: for (int i = 0; i < number_passes; i++) { michael@0: png_configure_decoder(png_ptr, &actualTop, i); michael@0: for (int j = 0; j < rect.fTop - actualTop; j++) { michael@0: uint8_t* bmRow = decodedBitmap.getAddr8(0, 0); michael@0: png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1); michael@0: } michael@0: png_uint_32 bitmapHeight = (png_uint_32) decodedBitmap.height(); michael@0: for (png_uint_32 y = 0; y < bitmapHeight; y++) { michael@0: uint8_t* bmRow = decodedBitmap.getAddr8(0, y); michael@0: png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1); michael@0: } michael@0: } michael@0: } else { michael@0: SkScaledBitmapSampler::SrcConfig sc; michael@0: int srcBytesPerPixel = 4; michael@0: michael@0: if (colorTable != NULL) { michael@0: sc = SkScaledBitmapSampler::kIndex; michael@0: srcBytesPerPixel = 1; michael@0: } else if (SkBitmap::kA8_Config == config) { michael@0: // A8 is only allowed if the original was GRAY. michael@0: SkASSERT(PNG_COLOR_TYPE_GRAY == colorType); michael@0: sc = SkScaledBitmapSampler::kGray; michael@0: srcBytesPerPixel = 1; michael@0: } else if (hasAlpha) { michael@0: sc = SkScaledBitmapSampler::kRGBA; michael@0: } else { michael@0: sc = SkScaledBitmapSampler::kRGBX; michael@0: } michael@0: michael@0: /* We have to pass the colortable explicitly, since we may have one michael@0: even if our decodedBitmap doesn't, due to the request that we michael@0: upscale png's palette to a direct model michael@0: */ michael@0: SkAutoLockColors ctLock(colorTable); michael@0: if (!sampler.begin(&decodedBitmap, sc, *this, ctLock.colors())) { michael@0: return false; michael@0: } michael@0: const int height = decodedBitmap.height(); michael@0: michael@0: if (number_passes > 1) { michael@0: SkAutoMalloc storage(origWidth * origHeight * srcBytesPerPixel); michael@0: uint8_t* base = (uint8_t*)storage.get(); michael@0: size_t rb = origWidth * srcBytesPerPixel; michael@0: michael@0: for (int i = 0; i < number_passes; i++) { michael@0: png_configure_decoder(png_ptr, &actualTop, i); michael@0: for (int j = 0; j < rect.fTop - actualTop; j++) { michael@0: uint8_t* bmRow = (uint8_t*)decodedBitmap.getPixels(); michael@0: png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1); michael@0: } michael@0: uint8_t* row = base; michael@0: for (int32_t y = 0; y < rect.height(); y++) { michael@0: uint8_t* bmRow = row; michael@0: png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1); michael@0: row += rb; michael@0: } michael@0: } michael@0: // now sample it michael@0: base += sampler.srcY0() * rb; michael@0: for (int y = 0; y < height; y++) { michael@0: reallyHasAlpha |= sampler.next(base); michael@0: base += sampler.srcDY() * rb; michael@0: } michael@0: } else { michael@0: SkAutoMalloc storage(origWidth * srcBytesPerPixel); michael@0: uint8_t* srcRow = (uint8_t*)storage.get(); michael@0: michael@0: png_configure_decoder(png_ptr, &actualTop, 0); michael@0: skip_src_rows(png_ptr, srcRow, sampler.srcY0()); michael@0: michael@0: for (int i = 0; i < rect.fTop - actualTop; i++) { michael@0: uint8_t* bmRow = (uint8_t*)decodedBitmap.getPixels(); michael@0: png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1); michael@0: } michael@0: for (int y = 0; y < height; y++) { michael@0: uint8_t* tmp = srcRow; michael@0: png_read_rows(png_ptr, &tmp, png_bytepp_NULL, 1); michael@0: reallyHasAlpha |= sampler.next(srcRow); michael@0: if (y < height - 1) { michael@0: skip_src_rows(png_ptr, srcRow, sampler.srcDY() - 1); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (0 != theTranspColor) { michael@0: reallyHasAlpha |= substituteTranspColor(&decodedBitmap, theTranspColor); michael@0: } michael@0: if (reallyHasAlpha && this->getRequireUnpremultipliedColors()) { michael@0: switch (decodedBitmap.config()) { michael@0: case SkBitmap::kIndex8_Config: michael@0: // Fall through. michael@0: case SkBitmap::kARGB_4444_Config: michael@0: // We have chosen not to support unpremul for these configs. michael@0: return false; michael@0: default: { michael@0: // Fall through to finish the decode. This config either michael@0: // supports unpremul or it is irrelevant because it has no michael@0: // alpha (or only alpha). michael@0: // These brackets prevent a warning. michael@0: } michael@0: } michael@0: } michael@0: SkAlphaType alphaType = kOpaque_SkAlphaType; michael@0: if (reallyHasAlpha) { michael@0: if (this->getRequireUnpremultipliedColors()) { michael@0: alphaType = kUnpremul_SkAlphaType; michael@0: } else { michael@0: alphaType = kPremul_SkAlphaType; michael@0: } michael@0: } michael@0: decodedBitmap.setAlphaType(alphaType); michael@0: michael@0: if (swapOnly) { michael@0: bm->swap(decodedBitmap); michael@0: return true; michael@0: } michael@0: return this->cropBitmap(bm, &decodedBitmap, sampleSize, region.x(), region.y(), michael@0: region.width(), region.height(), 0, rect.y()); michael@0: } michael@0: #endif michael@0: michael@0: /////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: #include "SkColorPriv.h" michael@0: #include "SkUnPreMultiply.h" michael@0: michael@0: static void sk_write_fn(png_structp png_ptr, png_bytep data, png_size_t len) { michael@0: SkWStream* sk_stream = (SkWStream*)png_get_io_ptr(png_ptr); michael@0: if (!sk_stream->write(data, len)) { michael@0: png_error(png_ptr, "sk_write_fn Error!"); michael@0: } michael@0: } michael@0: michael@0: static transform_scanline_proc choose_proc(SkBitmap::Config config, michael@0: bool hasAlpha) { michael@0: // we don't care about search on alpha if we're kIndex8, since only the michael@0: // colortable packing cares about that distinction, not the pixels michael@0: if (SkBitmap::kIndex8_Config == config) { michael@0: hasAlpha = false; // we store false in the table entries for kIndex8 michael@0: } michael@0: michael@0: static const struct { michael@0: SkBitmap::Config fConfig; michael@0: bool fHasAlpha; michael@0: transform_scanline_proc fProc; michael@0: } gMap[] = { michael@0: { SkBitmap::kRGB_565_Config, false, transform_scanline_565 }, michael@0: { SkBitmap::kARGB_8888_Config, false, transform_scanline_888 }, michael@0: { SkBitmap::kARGB_8888_Config, true, transform_scanline_8888 }, michael@0: { SkBitmap::kARGB_4444_Config, false, transform_scanline_444 }, michael@0: { SkBitmap::kARGB_4444_Config, true, transform_scanline_4444 }, michael@0: { SkBitmap::kIndex8_Config, false, transform_scanline_memcpy }, michael@0: }; michael@0: michael@0: for (int i = SK_ARRAY_COUNT(gMap) - 1; i >= 0; --i) { michael@0: if (gMap[i].fConfig == config && gMap[i].fHasAlpha == hasAlpha) { michael@0: return gMap[i].fProc; michael@0: } michael@0: } michael@0: sk_throw(); michael@0: return NULL; michael@0: } michael@0: michael@0: // return the minimum legal bitdepth (by png standards) for this many colortable michael@0: // entries. SkBitmap always stores in 8bits per pixel, but for colorcount <= 16, michael@0: // we can use fewer bits per in png michael@0: static int computeBitDepth(int colorCount) { michael@0: #if 0 michael@0: int bits = SkNextLog2(colorCount); michael@0: SkASSERT(bits >= 1 && bits <= 8); michael@0: // now we need bits itself to be a power of 2 (e.g. 1, 2, 4, 8) michael@0: return SkNextPow2(bits); michael@0: #else michael@0: // for the moment, we don't know how to pack bitdepth < 8 michael@0: return 8; michael@0: #endif michael@0: } michael@0: michael@0: /* Pack palette[] with the corresponding colors, and if hasAlpha is true, also michael@0: pack trans[] and return the number of trans[] entries written. If hasAlpha michael@0: is false, the return value will always be 0. michael@0: michael@0: Note: this routine takes care of unpremultiplying the RGB values when we michael@0: have alpha in the colortable, since png doesn't support premul colors michael@0: */ michael@0: static inline int pack_palette(SkColorTable* ctable, michael@0: png_color* SK_RESTRICT palette, michael@0: png_byte* SK_RESTRICT trans, bool hasAlpha) { michael@0: SkAutoLockColors alc(ctable); michael@0: const SkPMColor* SK_RESTRICT colors = alc.colors(); michael@0: const int ctCount = ctable->count(); michael@0: int i, num_trans = 0; michael@0: michael@0: if (hasAlpha) { michael@0: /* first see if we have some number of fully opaque at the end of the michael@0: ctable. PNG allows num_trans < num_palette, but all of the trans michael@0: entries must come first in the palette. If I was smarter, I'd michael@0: reorder the indices and ctable so that all non-opaque colors came michael@0: first in the palette. But, since that would slow down the encode, michael@0: I'm leaving the indices and ctable order as is, and just looking michael@0: at the tail of the ctable for opaqueness. michael@0: */ michael@0: num_trans = ctCount; michael@0: for (i = ctCount - 1; i >= 0; --i) { michael@0: if (SkGetPackedA32(colors[i]) != 0xFF) { michael@0: break; michael@0: } michael@0: num_trans -= 1; michael@0: } michael@0: michael@0: const SkUnPreMultiply::Scale* SK_RESTRICT table = michael@0: SkUnPreMultiply::GetScaleTable(); michael@0: michael@0: for (i = 0; i < num_trans; i++) { michael@0: const SkPMColor c = *colors++; michael@0: const unsigned a = SkGetPackedA32(c); michael@0: const SkUnPreMultiply::Scale s = table[a]; michael@0: trans[i] = a; michael@0: palette[i].red = SkUnPreMultiply::ApplyScale(s, SkGetPackedR32(c)); michael@0: palette[i].green = SkUnPreMultiply::ApplyScale(s,SkGetPackedG32(c)); michael@0: palette[i].blue = SkUnPreMultiply::ApplyScale(s, SkGetPackedB32(c)); michael@0: } michael@0: // now fall out of this if-block to use common code for the trailing michael@0: // opaque entries michael@0: } michael@0: michael@0: // these (remaining) entries are opaque michael@0: for (i = num_trans; i < ctCount; i++) { michael@0: SkPMColor c = *colors++; michael@0: palette[i].red = SkGetPackedR32(c); michael@0: palette[i].green = SkGetPackedG32(c); michael@0: palette[i].blue = SkGetPackedB32(c); michael@0: } michael@0: return num_trans; michael@0: } michael@0: michael@0: class SkPNGImageEncoder : public SkImageEncoder { michael@0: protected: michael@0: virtual bool onEncode(SkWStream* stream, const SkBitmap& bm, int quality) SK_OVERRIDE; michael@0: private: michael@0: bool doEncode(SkWStream* stream, const SkBitmap& bm, michael@0: const bool& hasAlpha, int colorType, michael@0: int bitDepth, SkBitmap::Config config, michael@0: png_color_8& sig_bit); michael@0: michael@0: typedef SkImageEncoder INHERITED; michael@0: }; michael@0: michael@0: bool SkPNGImageEncoder::onEncode(SkWStream* stream, const SkBitmap& bitmap, michael@0: int /*quality*/) { michael@0: SkBitmap::Config config = bitmap.config(); michael@0: michael@0: const bool hasAlpha = !bitmap.isOpaque(); michael@0: int colorType = PNG_COLOR_MASK_COLOR; michael@0: int bitDepth = 8; // default for color michael@0: png_color_8 sig_bit; michael@0: michael@0: switch (config) { michael@0: case SkBitmap::kIndex8_Config: michael@0: colorType |= PNG_COLOR_MASK_PALETTE; michael@0: // fall through to the ARGB_8888 case michael@0: case SkBitmap::kARGB_8888_Config: michael@0: sig_bit.red = 8; michael@0: sig_bit.green = 8; michael@0: sig_bit.blue = 8; michael@0: sig_bit.alpha = 8; michael@0: break; michael@0: case SkBitmap::kARGB_4444_Config: michael@0: sig_bit.red = 4; michael@0: sig_bit.green = 4; michael@0: sig_bit.blue = 4; michael@0: sig_bit.alpha = 4; michael@0: break; michael@0: case SkBitmap::kRGB_565_Config: michael@0: sig_bit.red = 5; michael@0: sig_bit.green = 6; michael@0: sig_bit.blue = 5; michael@0: sig_bit.alpha = 0; michael@0: break; michael@0: default: michael@0: return false; michael@0: } michael@0: michael@0: if (hasAlpha) { michael@0: // don't specify alpha if we're a palette, even if our ctable has alpha michael@0: if (!(colorType & PNG_COLOR_MASK_PALETTE)) { michael@0: colorType |= PNG_COLOR_MASK_ALPHA; michael@0: } michael@0: } else { michael@0: sig_bit.alpha = 0; michael@0: } michael@0: michael@0: SkAutoLockPixels alp(bitmap); michael@0: // readyToDraw checks for pixels (and colortable if that is required) michael@0: if (!bitmap.readyToDraw()) { michael@0: return false; michael@0: } michael@0: michael@0: // we must do this after we have locked the pixels michael@0: SkColorTable* ctable = bitmap.getColorTable(); michael@0: if (NULL != ctable) { michael@0: if (ctable->count() == 0) { michael@0: return false; michael@0: } michael@0: // check if we can store in fewer than 8 bits michael@0: bitDepth = computeBitDepth(ctable->count()); michael@0: } michael@0: michael@0: return doEncode(stream, bitmap, hasAlpha, colorType, michael@0: bitDepth, config, sig_bit); michael@0: } michael@0: michael@0: bool SkPNGImageEncoder::doEncode(SkWStream* stream, const SkBitmap& bitmap, michael@0: const bool& hasAlpha, int colorType, michael@0: int bitDepth, SkBitmap::Config config, michael@0: png_color_8& sig_bit) { michael@0: michael@0: png_structp png_ptr; michael@0: png_infop info_ptr; michael@0: michael@0: png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, sk_error_fn, michael@0: NULL); michael@0: if (NULL == png_ptr) { michael@0: return false; michael@0: } michael@0: michael@0: info_ptr = png_create_info_struct(png_ptr); michael@0: if (NULL == info_ptr) { michael@0: png_destroy_write_struct(&png_ptr, png_infopp_NULL); michael@0: return false; michael@0: } michael@0: michael@0: /* Set error handling. REQUIRED if you aren't supplying your own michael@0: * error handling functions in the png_create_write_struct() call. michael@0: */ michael@0: if (setjmp(png_jmpbuf(png_ptr))) { michael@0: png_destroy_write_struct(&png_ptr, &info_ptr); michael@0: return false; michael@0: } michael@0: michael@0: png_set_write_fn(png_ptr, (void*)stream, sk_write_fn, png_flush_ptr_NULL); michael@0: michael@0: /* Set the image information here. Width and height are up to 2^31, michael@0: * bit_depth is one of 1, 2, 4, 8, or 16, but valid values also depend on michael@0: * the color_type selected. color_type is one of PNG_COLOR_TYPE_GRAY, michael@0: * PNG_COLOR_TYPE_GRAY_ALPHA, PNG_COLOR_TYPE_PALETTE, PNG_COLOR_TYPE_RGB, michael@0: * or PNG_COLOR_TYPE_RGB_ALPHA. interlace is either PNG_INTERLACE_NONE or michael@0: * PNG_INTERLACE_ADAM7, and the compression_type and filter_type MUST michael@0: * currently be PNG_COMPRESSION_TYPE_BASE and PNG_FILTER_TYPE_BASE. REQUIRED michael@0: */ michael@0: michael@0: png_set_IHDR(png_ptr, info_ptr, bitmap.width(), bitmap.height(), michael@0: bitDepth, colorType, michael@0: PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, michael@0: PNG_FILTER_TYPE_BASE); michael@0: michael@0: // set our colortable/trans arrays if needed michael@0: png_color paletteColors[256]; michael@0: png_byte trans[256]; michael@0: if (SkBitmap::kIndex8_Config == config) { michael@0: SkColorTable* ct = bitmap.getColorTable(); michael@0: int numTrans = pack_palette(ct, paletteColors, trans, hasAlpha); michael@0: png_set_PLTE(png_ptr, info_ptr, paletteColors, ct->count()); michael@0: if (numTrans > 0) { michael@0: png_set_tRNS(png_ptr, info_ptr, trans, numTrans, NULL); michael@0: } michael@0: } michael@0: michael@0: png_set_sBIT(png_ptr, info_ptr, &sig_bit); michael@0: png_write_info(png_ptr, info_ptr); michael@0: michael@0: const char* srcImage = (const char*)bitmap.getPixels(); michael@0: SkAutoSMalloc<1024> rowStorage(bitmap.width() << 2); michael@0: char* storage = (char*)rowStorage.get(); michael@0: transform_scanline_proc proc = choose_proc(config, hasAlpha); michael@0: michael@0: for (int y = 0; y < bitmap.height(); y++) { michael@0: png_bytep row_ptr = (png_bytep)storage; michael@0: proc(srcImage, bitmap.width(), storage); michael@0: png_write_rows(png_ptr, &row_ptr, 1); michael@0: srcImage += bitmap.rowBytes(); michael@0: } michael@0: michael@0: png_write_end(png_ptr, info_ptr); michael@0: michael@0: /* clean up after the write, and free any memory allocated */ michael@0: png_destroy_write_struct(&png_ptr, &info_ptr); michael@0: return true; michael@0: } michael@0: michael@0: /////////////////////////////////////////////////////////////////////////////// michael@0: DEFINE_DECODER_CREATOR(PNGImageDecoder); michael@0: DEFINE_ENCODER_CREATOR(PNGImageEncoder); michael@0: /////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: static bool is_png(SkStreamRewindable* stream) { michael@0: char buf[PNG_BYTES_TO_CHECK]; michael@0: if (stream->read(buf, PNG_BYTES_TO_CHECK) == PNG_BYTES_TO_CHECK && michael@0: !png_sig_cmp((png_bytep) buf, (png_size_t)0, PNG_BYTES_TO_CHECK)) { michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: SkImageDecoder* sk_libpng_dfactory(SkStreamRewindable* stream) { michael@0: if (is_png(stream)) { michael@0: return SkNEW(SkPNGImageDecoder); michael@0: } michael@0: return NULL; michael@0: } michael@0: michael@0: static SkImageDecoder::Format get_format_png(SkStreamRewindable* stream) { michael@0: if (is_png(stream)) { michael@0: return SkImageDecoder::kPNG_Format; michael@0: } michael@0: return SkImageDecoder::kUnknown_Format; michael@0: } michael@0: michael@0: SkImageEncoder* sk_libpng_efactory(SkImageEncoder::Type t) { michael@0: return (SkImageEncoder::kPNG_Type == t) ? SkNEW(SkPNGImageEncoder) : NULL; michael@0: } michael@0: michael@0: static SkImageDecoder_DecodeReg gDReg(sk_libpng_dfactory); michael@0: static SkImageDecoder_FormatReg gFormatReg(get_format_png); michael@0: static SkImageEncoder_EncodeReg gEReg(sk_libpng_efactory);