diff options
Diffstat (limited to 'skia/images/SkImageDecoder_libpng.cpp')
-rw-r--r-- | skia/images/SkImageDecoder_libpng.cpp | 242 |
1 files changed, 197 insertions, 45 deletions
diff --git a/skia/images/SkImageDecoder_libpng.cpp b/skia/images/SkImageDecoder_libpng.cpp index 4378ca9..862ebf1 100644 --- a/skia/images/SkImageDecoder_libpng.cpp +++ b/skia/images/SkImageDecoder_libpng.cpp @@ -1,6 +1,6 @@ /* libs/graphics/images/SkImageDecoder_libpng.cpp ** -** Copyright 2006, Google Inc. +** Copyright 2006, The Android Open Source Project ** ** Licensed under the Apache License, Version 2.0 (the "License"); ** you may not use this file except in compliance with the License. @@ -83,7 +83,9 @@ static int sk_read_user_chunk(png_structp png_ptr, png_unknown_chunkp chunk) { } static void sk_error_fn(png_structp png_ptr, png_const_charp msg) { +#if 0 SkDebugf("------ png error %s\n", msg); +#endif longjmp(png_jmpbuf(png_ptr), 1); } @@ -98,6 +100,24 @@ static bool pos_le(int value, int max) { return value > 0 && value <= max; } +static bool substituteTranspColor(SkBitmap* bm, SkPMColor match) { + SkASSERT(bm->config() == SkBitmap::kARGB_8888_Config); + + bool reallyHasAlpha = false; + + for (int y = bm->height() - 1; y >= 0; --y) { + SkPMColor* p = bm->getAddr32(0, y); + for (int x = bm->width() - 1; x >= 0; --x) { + if (match == *p) { + *p = 0; + reallyHasAlpha = true; + } + p += 1; + } + } + return reallyHasAlpha; +} + bool SkPNGImageDecoder::onDecode(SkStream* sk_stream, SkBitmap* decodedBitmap, SkBitmap::Config prefConfig, Mode mode) { // SkAutoTrace apr("SkPNGImageDecoder::onDecode"); @@ -153,9 +173,30 @@ bool SkPNGImageDecoder::onDecode(SkStream* sk_stream, SkBitmap* decodedBitmap, png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bit_depth, &color_type, &interlace_type, int_p_NULL, int_p_NULL); + /* tell libpng to strip 16 bit/color files down to 8 bits/color */ + if (bit_depth == 16) { + png_set_strip_16(png_ptr); + } + /* Extract multiple pixels with bit depths of 1, 2, and 4 from a single + * byte into separate bytes (useful for paletted and grayscale images). */ + if (bit_depth < 8) { + png_set_packing(png_ptr); + } + /* Expand grayscale images to the full 8 bits from 1, 2, or 4 bits/pixel */ + if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) { + png_set_gray_1_2_4_to_8(png_ptr); + } + + /* Make a grayscale image into RGB. */ + if (color_type == PNG_COLOR_TYPE_GRAY || + color_type == PNG_COLOR_TYPE_GRAY_ALPHA) { + png_set_gray_to_rgb(png_ptr); + } + SkBitmap::Config config; bool hasAlpha = false; bool doDither = this->getDitherImage(); + SkPMColor theTranspColor = 0; // 0 tells us not to try to match // check for sBIT chunk data, in case we should disable dithering because // our data is not truely 8bits per component @@ -176,11 +217,41 @@ bool SkPNGImageDecoder::onDecode(SkStream* sk_stream, SkBitmap* decodedBitmap, if (color_type == PNG_COLOR_TYPE_PALETTE) { config = SkBitmap::kIndex8_Config; // defer sniffing for hasAlpha } else { - png_color_16p transColor; + png_color_16p transpColor = NULL; + int numTransp = 0; + + png_get_tRNS(png_ptr, info_ptr, NULL, &numTransp, &transpColor); - png_get_tRNS(png_ptr, info_ptr, NULL, NULL, &transColor); + bool valid = png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS); - if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS) || + if (valid && numTransp == 1 && transpColor != NULL) { + /* Compute our transparent color, which we'll match against later. + We don't really handle 16bit components properly here, since we + do our compare *after* the values have been knocked down to 8bit + which means we will find more matches than we should. The real + fix seems to be to see the actual 16bit components, do the + compare, and then knock it down to 8bits ourselves. + */ + if (color_type & PNG_COLOR_MASK_COLOR) { + if (16 == bit_depth) { + theTranspColor = SkPackARGB32(0xFF, transpColor->red >> 8, + transpColor->green >> 8, transpColor->blue >> 8); + } else { + theTranspColor = SkPackARGB32(0xFF, transpColor->red, + transpColor->green, transpColor->blue); + } + } else { // gray + if (16 == bit_depth) { + theTranspColor = SkPackARGB32(0xFF, transpColor->gray >> 8, + transpColor->gray >> 8, transpColor->gray >> 8); + } else { + theTranspColor = SkPackARGB32(0xFF, transpColor->gray, + transpColor->gray, transpColor->gray); + } + } + } + + if (valid || PNG_COLOR_TYPE_RGB_ALPHA == color_type || PNG_COLOR_TYPE_GRAY_ALPHA == color_type) { hasAlpha = true; @@ -212,26 +283,6 @@ bool SkPNGImageDecoder::onDecode(SkStream* sk_stream, SkBitmap* decodedBitmap, // from here down we are concerned with colortables and pixels - /* tell libpng to strip 16 bit/color files down to 8 bits/color */ - if (bit_depth == 16) { - png_set_strip_16(png_ptr); - } - /* Extract multiple pixels with bit depths of 1, 2, and 4 from a single - * byte into separate bytes (useful for paletted and grayscale images). */ - if (bit_depth < 8) { - png_set_packing(png_ptr); - } - /* Expand grayscale images to the full 8 bits from 1, 2, or 4 bits/pixel */ - if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) { - png_set_gray_1_2_4_to_8(png_ptr); - } - - /* Make a grayscale image into RGB. */ - if (color_type == PNG_COLOR_TYPE_GRAY || - color_type == PNG_COLOR_TYPE_GRAY_ALPHA) { - png_set_gray_to_rgb(png_ptr); - } - // we track if we actually see a non-opaque pixels, since sometimes a PNG sets its colortype // to |= PNG_COLOR_MASK_ALPHA, but all of its pixels are in fact opaque. We care, since we // draw lots faster if we can flag the bitmap has being opaque @@ -295,7 +346,6 @@ bool SkPNGImageDecoder::onDecode(SkStream* sk_stream, SkBitmap* decodedBitmap, SkAutoUnref aur(colorTable); if (!this->allocPixelRef(decodedBitmap, colorTable)) { - delete colorTable; return false; } @@ -374,14 +424,19 @@ bool SkPNGImageDecoder::onDecode(SkStream* sk_stream, SkBitmap* decodedBitmap, } if (hasAlpha && !reallyHasAlpha) { +#if 0 SkDEBUGF(("Image doesn't really have alpha [%d %d]\n", origWidth, origHeight)); +#endif } } /* read rest of file, and get additional chunks in info_ptr - REQUIRED */ png_read_end(png_ptr, info_ptr); + if (0 != theTranspColor) { + reallyHasAlpha |= substituteTranspColor(decodedBitmap, theTranspColor); + } decodedBitmap->setIsOpaque(!reallyHasAlpha); return true; } @@ -488,8 +543,19 @@ static void transform_scanline_4444(const char* SK_RESTRICT src, int width, } } +static void transform_scanline_index8(const char* SK_RESTRICT src, int width, + char* SK_RESTRICT dst) { + memcpy(dst, src, width); +} + static transform_scanline_proc choose_proc(SkBitmap::Config config, bool hasAlpha) { + // we don't care about search on alpha if we're kIndex8, since only the + // colortable packing cares about that distinction, not the pixels + if (SkBitmap::kIndex8_Config == config) { + hasAlpha = false; // we store false in the table entries for kIndex8 + } + static const struct { SkBitmap::Config fConfig; bool fHasAlpha; @@ -500,6 +566,7 @@ static transform_scanline_proc choose_proc(SkBitmap::Config config, { SkBitmap::kARGB_8888_Config, true, transform_scanline_8888 }, { SkBitmap::kARGB_4444_Config, false, transform_scanline_444 }, { SkBitmap::kARGB_4444_Config, true, transform_scanline_4444 }, + { SkBitmap::kIndex8_Config, false, transform_scanline_index8 }, }; for (int i = SK_ARRAY_COUNT(gMap) - 1; i >= 0; --i) { @@ -511,6 +578,78 @@ static transform_scanline_proc choose_proc(SkBitmap::Config config, return NULL; } +// return the minimum legal bitdepth (by png standards) for this many colortable +// entries. SkBitmap always stores in 8bits per pixel, but for colorcount <= 16, +// we can use fewer bits per in png +static int computeBitDepth(int colorCount) { +#if 0 + int bits = SkNextLog2(colorCount); + SkASSERT(bits >= 1 && bits <= 8); + // now we need bits itself to be a power of 2 (e.g. 1, 2, 4, 8) + return SkNextPow2(bits); +#else + // for the moment, we don't know how to pack bitdepth < 8 + return 8; +#endif +} + +/* Pack palette[] with the corresponding colors, and if hasAlpha is true, also + pack trans[] and return the number of trans[] entries written. If hasAlpha + is false, the return value will always be 0. + + Note: this routine takes care of unpremultiplying the RGB values when we + have alpha in the colortable, since png doesn't support premul colors +*/ +static int pack_palette(SkColorTable* ctable, png_color* SK_RESTRICT palette, + png_byte* SK_RESTRICT trans, bool hasAlpha) { + SkAutoLockColors alc(ctable); + const SkPMColor* SK_RESTRICT colors = alc.colors(); + const int ctCount = ctable->count(); + int i, num_trans = 0; + + if (hasAlpha) { + /* first see if we have some number of fully opaque at the end of the + ctable. PNG allows num_trans < num_palette, but all of the trans + entries must come first in the palette. If I was smarter, I'd + reorder the indices and ctable so that all non-opaque colors came + first in the palette. But, since that would slow down the encode, + I'm leaving the indices and ctable order as is, and just looking + at the tail of the ctable for opaqueness. + */ + num_trans = ctCount; + for (i = ctCount - 1; i >= 0; --i) { + if (SkGetPackedA32(colors[i]) != 0xFF) { + break; + } + num_trans -= 1; + } + + const SkUnPreMultiply::Scale* SK_RESTRICT table = + SkUnPreMultiply::GetScaleTable(); + + for (i = 0; i < num_trans; i++) { + const SkPMColor c = *colors++; + const unsigned a = SkGetPackedA32(c); + const SkUnPreMultiply::Scale s = table[a]; + trans[i] = a; + palette[i].red = SkUnPreMultiply::ApplyScale(s, SkGetPackedR32(c)); + palette[i].green = SkUnPreMultiply::ApplyScale(s,SkGetPackedG32(c)); + palette[i].blue = SkUnPreMultiply::ApplyScale(s, SkGetPackedB32(c)); + } + // now fall out of this if-block to use common code for the trailing + // opaque entries + } + + // these (remaining) entries are opaque + for (i = num_trans; i < ctCount; i++) { + SkPMColor c = *colors++; + palette[i].red = SkGetPackedR32(c); + palette[i].green = SkGetPackedG32(c); + palette[i].blue = SkGetPackedB32(c); + } + return num_trans; +} + class SkPNGImageEncoder : public SkImageEncoder { protected: virtual bool onEncode(SkWStream* stream, const SkBitmap& bm, int quality); @@ -521,20 +660,25 @@ bool SkPNGImageEncoder::onEncode(SkWStream* stream, const SkBitmap& bitmap, SkBitmap::Config config = bitmap.getConfig(); const bool hasAlpha = !bitmap.isOpaque(); + int colorType = PNG_COLOR_MASK_COLOR; + int bitDepth = 8; // default for color png_color_8 sig_bit; switch (config) { + case SkBitmap::kIndex8_Config: + colorType |= PNG_COLOR_MASK_PALETTE; + // fall through to the ARGB_8888 case case SkBitmap::kARGB_8888_Config: sig_bit.red = 8; sig_bit.green = 8; sig_bit.blue = 8; - sig_bit.alpha = hasAlpha ? 8 : 0; + sig_bit.alpha = 8; break; case SkBitmap::kARGB_4444_Config: sig_bit.red = 4; sig_bit.green = 4; sig_bit.blue = 4; - sig_bit.alpha = hasAlpha ? 4 : 0; + sig_bit.alpha = 4; break; case SkBitmap::kRGB_565_Config: sig_bit.red = 5; @@ -543,16 +687,34 @@ bool SkPNGImageEncoder::onEncode(SkWStream* stream, const SkBitmap& bitmap, sig_bit.alpha = 0; break; default: - SkDEBUGF(("SkPNGImageEncoder::onEncode can't encode %d config\n", - config)); return false; } - + + if (hasAlpha) { + // don't specify alpha if we're a palette, even if our ctable has alpha + if (!(colorType & PNG_COLOR_MASK_PALETTE)) { + colorType |= PNG_COLOR_MASK_ALPHA; + } + } else { + sig_bit.alpha = 0; + } + SkAutoLockPixels alp(bitmap); - if (NULL == bitmap.getPixels()) { + // readyToDraw checks for pixels (and colortable if that is required) + if (!bitmap.readyToDraw()) { return false; } - + + // we must do this after we have locked the pixels + SkColorTable* ctable = bitmap.getColorTable(); + if (NULL != ctable) { + if (ctable->count() == 0) { + return false; + } + // check if we can store in fewer than 8 bits + bitDepth = computeBitDepth(ctable->count()); + } + png_structp png_ptr; png_infop info_ptr; @@ -587,12 +749,12 @@ bool SkPNGImageEncoder::onEncode(SkWStream* stream, const SkBitmap& bitmap, * currently be PNG_COMPRESSION_TYPE_BASE and PNG_FILTER_TYPE_BASE. REQUIRED */ - png_set_IHDR(png_ptr, info_ptr, bitmap.width(), bitmap.height(), 8, - hasAlpha ? PNG_COLOR_TYPE_RGB_ALPHA : PNG_COLOR_TYPE_RGB, + png_set_IHDR(png_ptr, info_ptr, bitmap.width(), bitmap.height(), + bitDepth, colorType, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); -#if 0 // need to support this some day <reed> +#if 0 // need to support this some day /* set the palette if there is one. REQUIRED for indexed-color images */ palette = (png_colorp)png_malloc(png_ptr, PNG_MAX_PALETTE_LENGTH * png_sizeof (png_color)); @@ -620,15 +782,6 @@ bool SkPNGImageEncoder::onEncode(SkWStream* stream, const SkBitmap& bitmap, png_write_end(png_ptr, info_ptr); -#if 0 - /* If you png_malloced a palette, free it here (don't free info_ptr->palette, - as recommended in versions 1.0.5m and earlier of this example; if - libpng mallocs info_ptr->palette, libpng will free it). If you - allocated it with malloc() instead of png_malloc(), use free() instead - of png_free(). */ - png_free(png_ptr, palette); -#endif - /* clean up after the write, and free any memory allocated */ png_destroy_write_struct(&png_ptr, &info_ptr); return true; @@ -640,4 +793,3 @@ SkImageEncoder* SkImageEncoder_PNG_Factory() { } #endif /* SK_SUPPORT_IMAGE_ENCODE */ - |