blob: 3cc41e3f5983c81561b00dfb440c81f73ba0a459 [file] [log] [blame]
epoger@google.comec3ed6a2011-07-28 14:26:00 +00001
2/*
3 * Copyright 2006 The Android Open Source Project
4 *
5 * Use of this source code is governed by a BSD-style license that can be
6 * found in the LICENSE file.
7 */
8
reed@android.com8a1c16f2008-12-17 15:59:43 +00009
10#include "SkImageDecoder.h"
reed@android.comb08eb2b2009-01-06 20:16:26 +000011#include "SkImageEncoder.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000012#include "SkColor.h"
13#include "SkColorPriv.h"
14#include "SkDither.h"
15#include "SkMath.h"
halcanary@google.com2a103182013-10-14 12:49:15 +000016#include "SkRTConf.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000017#include "SkScaledBitmapSampler.h"
18#include "SkStream.h"
19#include "SkTemplates.h"
20#include "SkUtils.h"
epoger@google.com4ce738b2012-11-16 18:44:18 +000021#include "transform_scanline.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000022extern "C" {
23#include "png.h"
24}
25
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +000026/* These were dropped in libpng >= 1.4 */
27#ifndef png_infopp_NULL
28#define png_infopp_NULL NULL
29#endif
30
31#ifndef png_bytepp_NULL
32#define png_bytepp_NULL NULL
33#endif
34
35#ifndef int_p_NULL
36#define int_p_NULL NULL
37#endif
38
39#ifndef png_flush_ptr_NULL
40#define png_flush_ptr_NULL NULL
41#endif
42
halcanary@google.comfed30372013-10-04 12:46:45 +000043#if defined(SK_DEBUG)
halcanary@google.com2a103182013-10-14 12:49:15 +000044#define DEFAULT_FOR_SUPPRESS_PNG_IMAGE_DECODER_WARNINGS false
45#else // !defined(SK_DEBUG)
46#define DEFAULT_FOR_SUPPRESS_PNG_IMAGE_DECODER_WARNINGS true
halcanary@google.comfed30372013-10-04 12:46:45 +000047#endif // defined(SK_DEBUG)
halcanary@google.com2a103182013-10-14 12:49:15 +000048SK_CONF_DECLARE(bool, c_suppressPNGImageDecoderWarnings,
49 "images.png.suppressDecoderWarnings",
50 DEFAULT_FOR_SUPPRESS_PNG_IMAGE_DECODER_WARNINGS,
51 "Suppress most PNG warnings when calling image decode "
52 "functions.");
53
halcanary@google.comfed30372013-10-04 12:46:45 +000054
55
commit-bot@chromium.orga936e372013-03-14 14:42:18 +000056class SkPNGImageIndex {
57public:
scroggo@google.comb5571b32013-09-25 21:34:24 +000058 SkPNGImageIndex(SkStreamRewindable* stream, png_structp png_ptr, png_infop info_ptr)
scroggo@google.comc70a3aa2013-07-18 20:03:15 +000059 : fStream(stream)
60 , fPng_ptr(png_ptr)
61 , fInfo_ptr(info_ptr)
62 , fConfig(SkBitmap::kNo_Config) {
63 SkASSERT(stream != NULL);
64 stream->ref();
commit-bot@chromium.orga936e372013-03-14 14:42:18 +000065 }
66 ~SkPNGImageIndex() {
scroggo@google.comc70a3aa2013-07-18 20:03:15 +000067 if (NULL != fPng_ptr) {
68 png_destroy_read_struct(&fPng_ptr, &fInfo_ptr, png_infopp_NULL);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +000069 }
70 }
71
scroggo@google.comb5571b32013-09-25 21:34:24 +000072 SkAutoTUnref<SkStreamRewindable> fStream;
73 png_structp fPng_ptr;
74 png_infop fInfo_ptr;
75 SkBitmap::Config fConfig;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +000076};
77
reed@android.com8a1c16f2008-12-17 15:59:43 +000078class SkPNGImageDecoder : public SkImageDecoder {
79public:
commit-bot@chromium.orga936e372013-03-14 14:42:18 +000080 SkPNGImageDecoder() {
81 fImageIndex = NULL;
82 }
83 virtual Format getFormat() const SK_OVERRIDE {
reed@android.com8a1c16f2008-12-17 15:59:43 +000084 return kPNG_Format;
85 }
scroggo@google.com39edf4c2013-04-25 17:33:51 +000086
commit-bot@chromium.orga936e372013-03-14 14:42:18 +000087 virtual ~SkPNGImageDecoder() {
88 SkDELETE(fImageIndex);
89 }
rmistry@google.comd6176b02012-08-23 18:14:13 +000090
reed@android.com8a1c16f2008-12-17 15:59:43 +000091protected:
commit-bot@chromium.orga936e372013-03-14 14:42:18 +000092#ifdef SK_BUILD_FOR_ANDROID
scroggo@google.comb5571b32013-09-25 21:34:24 +000093 virtual bool onBuildTileIndex(SkStreamRewindable *stream, int *width, int *height) SK_OVERRIDE;
scroggo@google.com7e6fcee2013-05-03 20:14:28 +000094 virtual bool onDecodeSubset(SkBitmap* bitmap, const SkIRect& region) SK_OVERRIDE;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +000095#endif
96 virtual bool onDecode(SkStream* stream, SkBitmap* bm, Mode) SK_OVERRIDE;
97
98private:
99 SkPNGImageIndex* fImageIndex;
100
101 bool onDecodeInit(SkStream* stream, png_structp *png_ptrp, png_infop *info_ptrp);
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000102 bool decodePalette(png_structp png_ptr, png_infop info_ptr,
103 bool * SK_RESTRICT hasAlphap, bool *reallyHasAlphap,
104 SkColorTable **colorTablep);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000105 bool getBitmapConfig(png_structp png_ptr, png_infop info_ptr,
106 SkBitmap::Config *config, bool *hasAlpha,
scroggo@google.com8d239242013-10-01 17:27:15 +0000107 SkPMColor *theTranspColor);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000108
109 typedef SkImageDecoder INHERITED;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000110};
111
112#ifndef png_jmpbuf
113# define png_jmpbuf(png_ptr) ((png_ptr)->jmpbuf)
114#endif
115
116#define PNG_BYTES_TO_CHECK 4
117
118/* Automatically clean up after throwing an exception */
119struct PNGAutoClean {
120 PNGAutoClean(png_structp p, png_infop i): png_ptr(p), info_ptr(i) {}
121 ~PNGAutoClean() {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000122 png_destroy_read_struct(&png_ptr, &info_ptr, png_infopp_NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000123 }
124private:
125 png_structp png_ptr;
126 png_infop info_ptr;
127};
128
reed@android.com8a1c16f2008-12-17 15:59:43 +0000129static void sk_read_fn(png_structp png_ptr, png_bytep data, png_size_t length) {
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000130 SkStream* sk_stream = (SkStream*) png_get_io_ptr(png_ptr);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000131 size_t bytes = sk_stream->read(data, length);
132 if (bytes != length) {
133 png_error(png_ptr, "Read Error!");
134 }
135}
136
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000137#ifdef SK_BUILD_FOR_ANDROID
138static void sk_seek_fn(png_structp png_ptr, png_uint_32 offset) {
scroggo@google.comb5571b32013-09-25 21:34:24 +0000139 SkStreamRewindable* sk_stream = (SkStreamRewindable*) png_get_io_ptr(png_ptr);
scroggo@google.com4d213ab2013-08-28 13:08:54 +0000140 if (!sk_stream->rewind()) {
141 png_error(png_ptr, "Failed to rewind stream!");
142 }
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000143 (void)sk_stream->skip(offset);
144}
145#endif
146
reed@android.com8a1c16f2008-12-17 15:59:43 +0000147static int sk_read_user_chunk(png_structp png_ptr, png_unknown_chunkp chunk) {
148 SkImageDecoder::Peeker* peeker =
149 (SkImageDecoder::Peeker*)png_get_user_chunk_ptr(png_ptr);
150 // peek() returning true means continue decoding
151 return peeker->peek((const char*)chunk->name, chunk->data, chunk->size) ?
152 1 : -1;
153}
154
155static void sk_error_fn(png_structp png_ptr, png_const_charp msg) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000156 SkDEBUGF(("------ png error %s\n", msg));
reed@android.com8a1c16f2008-12-17 15:59:43 +0000157 longjmp(png_jmpbuf(png_ptr), 1);
158}
159
160static void skip_src_rows(png_structp png_ptr, uint8_t storage[], int count) {
161 for (int i = 0; i < count; i++) {
162 uint8_t* tmp = storage;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000163 png_read_rows(png_ptr, &tmp, png_bytepp_NULL, 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000164 }
165}
166
167static bool pos_le(int value, int max) {
168 return value > 0 && value <= max;
169}
170
171static bool substituteTranspColor(SkBitmap* bm, SkPMColor match) {
172 SkASSERT(bm->config() == SkBitmap::kARGB_8888_Config);
rmistry@google.comd6176b02012-08-23 18:14:13 +0000173
reed@android.com8a1c16f2008-12-17 15:59:43 +0000174 bool reallyHasAlpha = false;
175
176 for (int y = bm->height() - 1; y >= 0; --y) {
177 SkPMColor* p = bm->getAddr32(0, y);
178 for (int x = bm->width() - 1; x >= 0; --x) {
179 if (match == *p) {
180 *p = 0;
181 reallyHasAlpha = true;
182 }
183 p += 1;
184 }
185 }
186 return reallyHasAlpha;
187}
188
reed@android.com3f1f06a2010-03-03 21:04:12 +0000189static bool canUpscalePaletteToConfig(SkBitmap::Config dstConfig,
reed@android.comb6137c32009-07-29 20:56:52 +0000190 bool srcHasAlpha) {
reed@android.com3f1f06a2010-03-03 21:04:12 +0000191 switch (dstConfig) {
reed@android.comb6137c32009-07-29 20:56:52 +0000192 case SkBitmap::kARGB_8888_Config:
193 case SkBitmap::kARGB_4444_Config:
194 return true;
195 case SkBitmap::kRGB_565_Config:
196 // only return true if the src is opaque (since 565 is opaque)
197 return !srcHasAlpha;
198 default:
199 return false;
200 }
201}
202
203// call only if color_type is PALETTE. Returns true if the ctable has alpha
204static bool hasTransparencyInPalette(png_structp png_ptr, png_infop info_ptr) {
205 png_bytep trans;
206 int num_trans;
207
208 if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
209 png_get_tRNS(png_ptr, info_ptr, &trans, &num_trans, NULL);
210 return num_trans > 0;
211 }
212 return false;
reed@android.com11344262009-07-08 20:09:23 +0000213}
214
halcanary@google.comfed30372013-10-04 12:46:45 +0000215void do_nothing_warning_fn(png_structp, png_const_charp) {
216 /* do nothing */
217}
218
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000219bool SkPNGImageDecoder::onDecodeInit(SkStream* sk_stream, png_structp *png_ptrp,
220 png_infop *info_ptrp) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000221 /* Create and initialize the png_struct with the desired error handler
222 * functions. If you want to use the default stderr and longjump method,
223 * you can supply NULL for the last three parameters. We also supply the
224 * the compiler header file version, so that we know if the application
225 * was compiled with a compatible version of the library. */
halcanary@google.comfed30372013-10-04 12:46:45 +0000226
halcanary@google.comfed30372013-10-04 12:46:45 +0000227 png_error_ptr user_warning_fn =
228 (c_suppressPNGImageDecoderWarnings) ? (&do_nothing_warning_fn) : NULL;
229 /* NULL means to leave as default library behavior. */
halcanary@google.com2a103182013-10-14 12:49:15 +0000230 /* c_suppressPNGImageDecoderWarnings default depends on SK_DEBUG. */
halcanary@google.comfed30372013-10-04 12:46:45 +0000231 /* To suppress warnings with a SK_DEBUG binary, set the
232 * environment variable "skia_images_png_suppressDecoderWarnings"
233 * to "true". Inside a program that links to skia:
234 * SK_CONF_SET("images.png.suppressDecoderWarnings", true); */
halcanary@google.comfed30372013-10-04 12:46:45 +0000235
reed@android.com8a1c16f2008-12-17 15:59:43 +0000236 png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
halcanary@google.comfed30372013-10-04 12:46:45 +0000237 NULL, sk_error_fn, user_warning_fn);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000238 // png_voidp user_error_ptr, user_error_fn, user_warning_fn);
239 if (png_ptr == NULL) {
240 return false;
241 }
halcanary@google.comfed30372013-10-04 12:46:45 +0000242
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000243 *png_ptrp = png_ptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000244
245 /* Allocate/initialize the memory for image information. */
246 png_infop info_ptr = png_create_info_struct(png_ptr);
247 if (info_ptr == NULL) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000248 png_destroy_read_struct(&png_ptr, png_infopp_NULL, png_infopp_NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000249 return false;
250 }
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000251 *info_ptrp = info_ptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000252
253 /* Set error handling if you are using the setjmp/longjmp method (this is
254 * the normal method of doing things with libpng). REQUIRED unless you
255 * set up your own error handlers in the png_create_read_struct() earlier.
256 */
257 if (setjmp(png_jmpbuf(png_ptr))) {
scroggo@google.com5401cd02013-11-12 15:30:06 +0000258 png_destroy_read_struct(&png_ptr, &info_ptr, png_infopp_NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000259 return false;
260 }
261
262 /* If you are using replacement read functions, instead of calling
263 * png_init_io() here you would call:
264 */
265 png_set_read_fn(png_ptr, (void *)sk_stream, sk_read_fn);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000266#ifdef SK_BUILD_FOR_ANDROID
267 png_set_seek_fn(png_ptr, sk_seek_fn);
268#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000269 /* where user_io_ptr is a structure you want available to the callbacks */
270 /* If we have already read some of the signature */
271// png_set_sig_bytes(png_ptr, 0 /* sig_read */ );
272
273 // hookup our peeker so we can see any user-chunks the caller may be interested in
274 png_set_keep_unknown_chunks(png_ptr, PNG_HANDLE_CHUNK_ALWAYS, (png_byte*)"", 0);
275 if (this->getPeeker()) {
276 png_set_read_user_chunk_fn(png_ptr, (png_voidp)this->getPeeker(), sk_read_user_chunk);
277 }
278
279 /* The call to png_read_info() gives us all of the information from the
280 * PNG file before the first IDAT (image data chunk). */
281 png_read_info(png_ptr, info_ptr);
282 png_uint_32 origWidth, origHeight;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000283 int bitDepth, colorType;
284 png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
285 &colorType, int_p_NULL, int_p_NULL, int_p_NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000286
287 /* tell libpng to strip 16 bit/color files down to 8 bits/color */
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000288 if (bitDepth == 16) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000289 png_set_strip_16(png_ptr);
290 }
291 /* Extract multiple pixels with bit depths of 1, 2, and 4 from a single
292 * byte into separate bytes (useful for paletted and grayscale images). */
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000293 if (bitDepth < 8) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000294 png_set_packing(png_ptr);
295 }
296 /* Expand grayscale images to the full 8 bits from 1, 2, or 4 bits/pixel */
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000297 if (colorType == PNG_COLOR_TYPE_GRAY && bitDepth < 8) {
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000298 png_set_expand_gray_1_2_4_to_8(png_ptr);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000299 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000300
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000301 return true;
302}
303
304bool SkPNGImageDecoder::onDecode(SkStream* sk_stream, SkBitmap* decodedBitmap,
305 Mode mode) {
306 png_structp png_ptr;
307 png_infop info_ptr;
308
309 if (!onDecodeInit(sk_stream, &png_ptr, &info_ptr)) {
310 return false;
311 }
312
scroggo@google.comfeeca3c2013-11-12 14:38:41 +0000313 PNGAutoClean autoClean(png_ptr, info_ptr);
314
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000315 if (setjmp(png_jmpbuf(png_ptr))) {
316 return false;
317 }
318
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000319 png_uint_32 origWidth, origHeight;
320 int bitDepth, colorType, interlaceType;
321 png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
322 &colorType, &interlaceType, int_p_NULL, int_p_NULL);
rmistry@google.comd6176b02012-08-23 18:14:13 +0000323
reed@android.com8a1c16f2008-12-17 15:59:43 +0000324 SkBitmap::Config config;
325 bool hasAlpha = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000326 SkPMColor theTranspColor = 0; // 0 tells us not to try to match
rmistry@google.comd6176b02012-08-23 18:14:13 +0000327
scroggo@google.com8d239242013-10-01 17:27:15 +0000328 if (!this->getBitmapConfig(png_ptr, info_ptr, &config, &hasAlpha, &theTranspColor)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000329 return false;
330 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000331
reed@android.com8a1c16f2008-12-17 15:59:43 +0000332 const int sampleSize = this->getSampleSize();
333 SkScaledBitmapSampler sampler(origWidth, origHeight, sampleSize);
scroggo@google.combc69ce92013-07-09 15:45:14 +0000334 decodedBitmap->setConfig(config, sampler.scaledWidth(), sampler.scaledHeight());
reed@android.com8a1c16f2008-12-17 15:59:43 +0000335
reed@android.com8a1c16f2008-12-17 15:59:43 +0000336 if (SkImageDecoder::kDecodeBounds_Mode == mode) {
337 return true;
338 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000339
reed@android.com8a1c16f2008-12-17 15:59:43 +0000340 // from here down we are concerned with colortables and pixels
341
342 // we track if we actually see a non-opaque pixels, since sometimes a PNG sets its colortype
343 // to |= PNG_COLOR_MASK_ALPHA, but all of its pixels are in fact opaque. We care, since we
344 // draw lots faster if we can flag the bitmap has being opaque
345 bool reallyHasAlpha = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000346 SkColorTable* colorTable = NULL;
347
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000348 if (colorType == PNG_COLOR_TYPE_PALETTE) {
349 decodePalette(png_ptr, info_ptr, &hasAlpha, &reallyHasAlpha, &colorTable);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000350 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000351
reed@android.com8a1c16f2008-12-17 15:59:43 +0000352 SkAutoUnref aur(colorTable);
353
scroggo@google.combc69ce92013-07-09 15:45:14 +0000354 if (!this->allocPixelRef(decodedBitmap,
355 SkBitmap::kIndex8_Config == config ? colorTable : NULL)) {
356 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000357 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000358
reed@android.com8a1c16f2008-12-17 15:59:43 +0000359 SkAutoLockPixels alp(*decodedBitmap);
360
reed@android.com8a1c16f2008-12-17 15:59:43 +0000361 /* Turn on interlace handling. REQUIRED if you are not using
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000362 * png_read_image(). To see how to handle interlacing passes,
363 * see the png_read_row() method below:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000364 */
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000365 const int number_passes = (interlaceType != PNG_INTERLACE_NONE) ?
366 png_set_interlace_handling(png_ptr) : 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000367
368 /* Optional call to gamma correct and add the background to the palette
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000369 * and update info structure. REQUIRED if you are expecting libpng to
370 * update the palette for you (ie you selected such a transform above).
reed@android.com8a1c16f2008-12-17 15:59:43 +0000371 */
372 png_read_update_info(png_ptr, info_ptr);
373
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000374 if ((SkBitmap::kA8_Config == config || SkBitmap::kIndex8_Config == config)
375 && 1 == sampleSize) {
scroggo@google.com5ee18dd2013-10-21 20:47:31 +0000376 if (SkBitmap::kA8_Config == config) {
377 // For an A8 bitmap, we assume there is an alpha for speed. It is
378 // possible the bitmap is opaque, but that is an unlikely use case
379 // since it would not be very interesting.
380 reallyHasAlpha = true;
381 // A8 is only allowed if the original was GRAY.
382 SkASSERT(PNG_COLOR_TYPE_GRAY == colorType);
383 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000384 for (int i = 0; i < number_passes; i++) {
385 for (png_uint_32 y = 0; y < origHeight; y++) {
386 uint8_t* bmRow = decodedBitmap->getAddr8(0, y);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000387 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000388 }
389 }
390 } else {
391 SkScaledBitmapSampler::SrcConfig sc;
392 int srcBytesPerPixel = 4;
rmistry@google.comd6176b02012-08-23 18:14:13 +0000393
reed@android.com11344262009-07-08 20:09:23 +0000394 if (colorTable != NULL) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000395 sc = SkScaledBitmapSampler::kIndex;
396 srcBytesPerPixel = 1;
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000397 } else if (SkBitmap::kA8_Config == config) {
398 // A8 is only allowed if the original was GRAY.
399 SkASSERT(PNG_COLOR_TYPE_GRAY == colorType);
400 sc = SkScaledBitmapSampler::kGray;
401 srcBytesPerPixel = 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000402 } else if (hasAlpha) {
403 sc = SkScaledBitmapSampler::kRGBA;
404 } else {
405 sc = SkScaledBitmapSampler::kRGBX;
406 }
reed@android.com11344262009-07-08 20:09:23 +0000407
408 /* We have to pass the colortable explicitly, since we may have one
409 even if our decodedBitmap doesn't, due to the request that we
410 upscale png's palette to a direct model
411 */
412 SkAutoLockColors ctLock(colorTable);
scroggo@google.com8d239242013-10-01 17:27:15 +0000413 if (!sampler.begin(decodedBitmap, sc, *this, ctLock.colors())) {
reed@android.com862e91b2009-04-28 15:27:07 +0000414 return false;
415 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000416 const int height = decodedBitmap->height();
417
reed@android.com862e91b2009-04-28 15:27:07 +0000418 if (number_passes > 1) {
419 SkAutoMalloc storage(origWidth * origHeight * srcBytesPerPixel);
420 uint8_t* base = (uint8_t*)storage.get();
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000421 size_t rowBytes = origWidth * srcBytesPerPixel;
reed@android.coma8a8b8b2009-05-04 15:00:11 +0000422
reed@android.com862e91b2009-04-28 15:27:07 +0000423 for (int i = 0; i < number_passes; i++) {
424 uint8_t* row = base;
425 for (png_uint_32 y = 0; y < origHeight; y++) {
426 uint8_t* bmRow = row;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000427 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
428 row += rowBytes;
reed@android.com862e91b2009-04-28 15:27:07 +0000429 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000430 }
reed@android.com862e91b2009-04-28 15:27:07 +0000431 // now sample it
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000432 base += sampler.srcY0() * rowBytes;
reed@android.com862e91b2009-04-28 15:27:07 +0000433 for (int y = 0; y < height; y++) {
434 reallyHasAlpha |= sampler.next(base);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000435 base += sampler.srcDY() * rowBytes;
reed@android.com862e91b2009-04-28 15:27:07 +0000436 }
437 } else {
438 SkAutoMalloc storage(origWidth * srcBytesPerPixel);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000439 uint8_t* srcRow = (uint8_t*)storage.get();
440 skip_src_rows(png_ptr, srcRow, sampler.srcY0());
441
442 for (int y = 0; y < height; y++) {
443 uint8_t* tmp = srcRow;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000444 png_read_rows(png_ptr, &tmp, png_bytepp_NULL, 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000445 reallyHasAlpha |= sampler.next(srcRow);
446 if (y < height - 1) {
447 skip_src_rows(png_ptr, srcRow, sampler.srcDY() - 1);
448 }
449 }
reed@android.com862e91b2009-04-28 15:27:07 +0000450
reed@android.com8a1c16f2008-12-17 15:59:43 +0000451 // skip the rest of the rows (if any)
452 png_uint_32 read = (height - 1) * sampler.srcDY() +
453 sampler.srcY0() + 1;
454 SkASSERT(read <= origHeight);
455 skip_src_rows(png_ptr, srcRow, origHeight - read);
456 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000457 }
458
459 /* read rest of file, and get additional chunks in info_ptr - REQUIRED */
460 png_read_end(png_ptr, info_ptr);
461
462 if (0 != theTranspColor) {
463 reallyHasAlpha |= substituteTranspColor(decodedBitmap, theTranspColor);
464 }
scroggo@google.com5ee18dd2013-10-21 20:47:31 +0000465 if (reallyHasAlpha && this->getRequireUnpremultipliedColors()) {
466 switch (decodedBitmap->config()) {
467 case SkBitmap::kIndex8_Config:
468 // Fall through.
469 case SkBitmap::kARGB_4444_Config:
470 // We have chosen not to support unpremul for these configs.
471 return false;
472 default: {
473 // Fall through to finish the decode. This config either
474 // supports unpremul or it is irrelevant because it has no
475 // alpha (or only alpha).
476 // These brackets prevent a warning.
477 }
478 }
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000479 }
scroggo@google.com5ee18dd2013-10-21 20:47:31 +0000480
reed@google.com383a6972013-10-21 14:00:07 +0000481 SkAlphaType alphaType = kOpaque_SkAlphaType;
482 if (reallyHasAlpha) {
483 if (this->getRequireUnpremultipliedColors()) {
484 alphaType = kUnpremul_SkAlphaType;
485 } else {
486 alphaType = kPremul_SkAlphaType;
487 }
488 }
489 decodedBitmap->setAlphaType(alphaType);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000490 return true;
491}
492
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000493
494
495bool SkPNGImageDecoder::getBitmapConfig(png_structp png_ptr, png_infop info_ptr,
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000496 SkBitmap::Config* SK_RESTRICT configp,
497 bool* SK_RESTRICT hasAlphap,
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000498 SkPMColor* SK_RESTRICT theTranspColorp) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000499 png_uint_32 origWidth, origHeight;
500 int bitDepth, colorType;
501 png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
502 &colorType, int_p_NULL, int_p_NULL, int_p_NULL);
503
504 // check for sBIT chunk data, in case we should disable dithering because
505 // our data is not truely 8bits per component
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000506 png_color_8p sig_bit;
scroggo@google.com8d239242013-10-01 17:27:15 +0000507 if (this->getDitherImage() && png_get_sBIT(png_ptr, info_ptr, &sig_bit)) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000508#if 0
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000509 SkDebugf("----- sBIT %d %d %d %d\n", sig_bit->red, sig_bit->green,
510 sig_bit->blue, sig_bit->alpha);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000511#endif
512 // 0 seems to indicate no information available
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000513 if (pos_le(sig_bit->red, SK_R16_BITS) &&
514 pos_le(sig_bit->green, SK_G16_BITS) &&
515 pos_le(sig_bit->blue, SK_B16_BITS)) {
scroggo@google.com8d239242013-10-01 17:27:15 +0000516 this->setDitherImage(false);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000517 }
518 }
519
520 if (colorType == PNG_COLOR_TYPE_PALETTE) {
521 bool paletteHasAlpha = hasTransparencyInPalette(png_ptr, info_ptr);
522 *configp = this->getPrefConfig(kIndex_SrcDepth, paletteHasAlpha);
523 // now see if we can upscale to their requested config
524 if (!canUpscalePaletteToConfig(*configp, paletteHasAlpha)) {
525 *configp = SkBitmap::kIndex8_Config;
526 }
527 } else {
528 png_color_16p transpColor = NULL;
529 int numTransp = 0;
530
531 png_get_tRNS(png_ptr, info_ptr, NULL, &numTransp, &transpColor);
532
533 bool valid = png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS);
534
535 if (valid && numTransp == 1 && transpColor != NULL) {
536 /* Compute our transparent color, which we'll match against later.
537 We don't really handle 16bit components properly here, since we
538 do our compare *after* the values have been knocked down to 8bit
539 which means we will find more matches than we should. The real
540 fix seems to be to see the actual 16bit components, do the
541 compare, and then knock it down to 8bits ourselves.
542 */
543 if (colorType & PNG_COLOR_MASK_COLOR) {
544 if (16 == bitDepth) {
545 *theTranspColorp = SkPackARGB32(0xFF, transpColor->red >> 8,
546 transpColor->green >> 8,
547 transpColor->blue >> 8);
548 } else {
halcanary@google.comfed30372013-10-04 12:46:45 +0000549 /* We apply the mask because in a very small
550 number of corrupt PNGs, (transpColor->red > 255)
551 and (bitDepth == 8), for certain versions of libpng. */
552 *theTranspColorp = SkPackARGB32(0xFF,
553 0xFF & (transpColor->red),
554 0xFF & (transpColor->green),
555 0xFF & (transpColor->blue));
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000556 }
557 } else { // gray
558 if (16 == bitDepth) {
559 *theTranspColorp = SkPackARGB32(0xFF, transpColor->gray >> 8,
560 transpColor->gray >> 8,
561 transpColor->gray >> 8);
562 } else {
halcanary@google.comfed30372013-10-04 12:46:45 +0000563 /* We apply the mask because in a very small
564 number of corrupt PNGs, (transpColor->red >
565 255) and (bitDepth == 8), for certain versions
566 of libpng. For safety we assume the same could
567 happen with a grayscale PNG. */
568 *theTranspColorp = SkPackARGB32(0xFF,
569 0xFF & (transpColor->gray),
570 0xFF & (transpColor->gray),
571 0xFF & (transpColor->gray));
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000572 }
573 }
574 }
575
576 if (valid ||
577 PNG_COLOR_TYPE_RGB_ALPHA == colorType ||
578 PNG_COLOR_TYPE_GRAY_ALPHA == colorType) {
579 *hasAlphap = true;
580 }
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000581
582 SrcDepth srcDepth = k32Bit_SrcDepth;
583 if (PNG_COLOR_TYPE_GRAY == colorType) {
584 srcDepth = k8BitGray_SrcDepth;
scroggo@google.com8e2ef012013-07-18 20:14:45 +0000585 // Remove this assert, which fails on desk_pokemonwiki.skp
586 //SkASSERT(!*hasAlphap);
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000587 }
588
589 *configp = this->getPrefConfig(srcDepth, *hasAlphap);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000590 // now match the request against our capabilities
591 if (*hasAlphap) {
592 if (*configp != SkBitmap::kARGB_4444_Config) {
593 *configp = SkBitmap::kARGB_8888_Config;
594 }
595 } else {
scroggo@google.com354fd972013-10-02 15:50:19 +0000596 if (SkBitmap::kA8_Config == *configp) {
597 if (k8BitGray_SrcDepth != srcDepth) {
598 // Converting a non grayscale image to A8 is not currently supported.
599 *configp = SkBitmap::kARGB_8888_Config;
600 }
601 } else if (*configp != SkBitmap::kRGB_565_Config &&
602 *configp != SkBitmap::kARGB_4444_Config) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000603 *configp = SkBitmap::kARGB_8888_Config;
604 }
605 }
606 }
607
608 // sanity check for size
609 {
reed@google.com57212f92013-12-30 14:40:38 +0000610 int64_t size = sk_64_mul(origWidth, origHeight);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000611 // now check that if we are 4-bytes per pixel, we also don't overflow
reed@google.com57212f92013-12-30 14:40:38 +0000612 if (size < 0 || size > (0x7FFFFFFF >> 2)) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000613 return false;
614 }
615 }
616
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000617 if (!this->chooseFromOneChoice(*configp, origWidth, origHeight)) {
618 return false;
619 }
620
621 // If the image has alpha and the decoder wants unpremultiplied
622 // colors, the only supported config is 8888.
623 if (this->getRequireUnpremultipliedColors() && *hasAlphap) {
624 *configp = SkBitmap::kARGB_8888_Config;
625 }
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000626
627 if (fImageIndex != NULL) {
628 if (SkBitmap::kNo_Config == fImageIndex->fConfig) {
629 // This is the first time for this subset decode. From now on,
630 // all decodes must be in the same config.
631 fImageIndex->fConfig = *configp;
632 } else if (fImageIndex->fConfig != *configp) {
633 // Requesting a different config for a subsequent decode is not
634 // supported. Report failure before we make changes to png_ptr.
635 return false;
636 }
637 }
638
639 bool convertGrayToRGB = PNG_COLOR_TYPE_GRAY == colorType
640 && *configp != SkBitmap::kA8_Config;
641
642 // Unless the user is requesting A8, convert a grayscale image into RGB.
643 // GRAY_ALPHA will always be converted to RGB
644 if (convertGrayToRGB || colorType == PNG_COLOR_TYPE_GRAY_ALPHA) {
645 png_set_gray_to_rgb(png_ptr);
646 }
647
648 // Add filler (or alpha) byte (after each RGB triplet) if necessary.
649 if (colorType == PNG_COLOR_TYPE_RGB || convertGrayToRGB) {
650 png_set_filler(png_ptr, 0xff, PNG_FILLER_AFTER);
651 }
652
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000653 return true;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000654}
655
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000656typedef uint32_t (*PackColorProc)(U8CPU a, U8CPU r, U8CPU g, U8CPU b);
657
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000658bool SkPNGImageDecoder::decodePalette(png_structp png_ptr, png_infop info_ptr,
659 bool *hasAlphap, bool *reallyHasAlphap,
660 SkColorTable **colorTablep) {
661 int numPalette;
662 png_colorp palette;
663 png_bytep trans;
664 int numTrans;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000665
666 png_get_PLTE(png_ptr, info_ptr, &palette, &numPalette);
667
668 /* BUGGY IMAGE WORKAROUND
669
670 We hit some images (e.g. fruit_.png) who contain bytes that are == colortable_count
671 which is a problem since we use the byte as an index. To work around this we grow
672 the colortable by 1 (if its < 256) and duplicate the last color into that slot.
673 */
674 int colorCount = numPalette + (numPalette < 256);
reed@google.com0a6151d2013-10-10 14:44:56 +0000675 SkPMColor colorStorage[256]; // worst-case storage
676 SkPMColor* colorPtr = colorStorage;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000677
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000678 if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
679 png_get_tRNS(png_ptr, info_ptr, &trans, &numTrans, NULL);
680 *hasAlphap = (numTrans > 0);
681 } else {
682 numTrans = 0;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000683 }
reed@google.com0a6151d2013-10-10 14:44:56 +0000684
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000685 // check for bad images that might make us crash
686 if (numTrans > numPalette) {
687 numTrans = numPalette;
688 }
689
690 int index = 0;
691 int transLessThanFF = 0;
692
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000693 // Choose which function to use to create the color table. If the final destination's
694 // config is unpremultiplied, the color table will store unpremultiplied colors.
695 PackColorProc proc;
696 if (this->getRequireUnpremultipliedColors()) {
697 proc = &SkPackARGB32NoCheck;
698 } else {
699 proc = &SkPreMultiplyARGB;
700 }
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000701 for (; index < numTrans; index++) {
702 transLessThanFF |= (int)*trans - 0xFF;
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000703 *colorPtr++ = proc(*trans++, palette->red, palette->green, palette->blue);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000704 palette++;
705 }
reed@google.com0a6151d2013-10-10 14:44:56 +0000706 bool reallyHasAlpha = (transLessThanFF < 0);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000707
708 for (; index < numPalette; index++) {
709 *colorPtr++ = SkPackARGB32(0xFF, palette->red, palette->green, palette->blue);
710 palette++;
711 }
712
713 // see BUGGY IMAGE WORKAROUND comment above
714 if (numPalette < 256) {
715 *colorPtr = colorPtr[-1];
716 }
reed@google.com0a6151d2013-10-10 14:44:56 +0000717
718 SkAlphaType alphaType = kOpaque_SkAlphaType;
719 if (reallyHasAlpha) {
720 if (this->getRequireUnpremultipliedColors()) {
721 alphaType = kUnpremul_SkAlphaType;
722 } else {
723 alphaType = kPremul_SkAlphaType;
724 }
725 }
726
727 *colorTablep = SkNEW_ARGS(SkColorTable,
728 (colorStorage, colorCount, alphaType));
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000729 *reallyHasAlphap = reallyHasAlpha;
730 return true;
731}
732
733#ifdef SK_BUILD_FOR_ANDROID
734
scroggo@google.comb5571b32013-09-25 21:34:24 +0000735bool SkPNGImageDecoder::onBuildTileIndex(SkStreamRewindable* sk_stream, int *width, int *height) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000736 png_structp png_ptr;
737 png_infop info_ptr;
738
739 if (!onDecodeInit(sk_stream, &png_ptr, &info_ptr)) {
740 return false;
741 }
742
743 if (setjmp(png_jmpbuf(png_ptr)) != 0) {
744 png_destroy_read_struct(&png_ptr, &info_ptr, png_infopp_NULL);
745 return false;
746 }
747
748 png_uint_32 origWidth, origHeight;
749 int bitDepth, colorType;
750 png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
751 &colorType, int_p_NULL, int_p_NULL, int_p_NULL);
752
753 *width = origWidth;
754 *height = origHeight;
755
756 png_build_index(png_ptr);
757
758 if (fImageIndex) {
759 SkDELETE(fImageIndex);
760 }
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000761 fImageIndex = SkNEW_ARGS(SkPNGImageIndex, (sk_stream, png_ptr, info_ptr));
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000762
763 return true;
764}
765
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000766bool SkPNGImageDecoder::onDecodeSubset(SkBitmap* bm, const SkIRect& region) {
767 if (NULL == fImageIndex) {
768 return false;
769 }
770
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000771 png_structp png_ptr = fImageIndex->fPng_ptr;
772 png_infop info_ptr = fImageIndex->fInfo_ptr;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000773 if (setjmp(png_jmpbuf(png_ptr))) {
774 return false;
775 }
776
777 png_uint_32 origWidth, origHeight;
778 int bitDepth, colorType, interlaceType;
779 png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
780 &colorType, &interlaceType, int_p_NULL, int_p_NULL);
781
782 SkIRect rect = SkIRect::MakeWH(origWidth, origHeight);
783
784 if (!rect.intersect(region)) {
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000785 // If the requested region is entirely outside the image, just
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000786 // returns false
787 return false;
788 }
789
790 SkBitmap::Config config;
791 bool hasAlpha = false;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000792 SkPMColor theTranspColor = 0; // 0 tells us not to try to match
793
scroggo@google.com8d239242013-10-01 17:27:15 +0000794 if (!this->getBitmapConfig(png_ptr, info_ptr, &config, &hasAlpha, &theTranspColor)) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000795 return false;
796 }
797
798 const int sampleSize = this->getSampleSize();
799 SkScaledBitmapSampler sampler(origWidth, rect.height(), sampleSize);
800
801 SkBitmap decodedBitmap;
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000802 decodedBitmap.setConfig(config, sampler.scaledWidth(), sampler.scaledHeight());
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000803
804 // from here down we are concerned with colortables and pixels
805
806 // we track if we actually see a non-opaque pixels, since sometimes a PNG sets its colortype
807 // to |= PNG_COLOR_MASK_ALPHA, but all of its pixels are in fact opaque. We care, since we
808 // draw lots faster if we can flag the bitmap has being opaque
809 bool reallyHasAlpha = false;
810 SkColorTable* colorTable = NULL;
811
812 if (colorType == PNG_COLOR_TYPE_PALETTE) {
813 decodePalette(png_ptr, info_ptr, &hasAlpha, &reallyHasAlpha, &colorTable);
814 }
815
816 SkAutoUnref aur(colorTable);
817
818 // Check ahead of time if the swap(dest, src) is possible.
819 // If yes, then we will stick to AllocPixelRef since it's cheaper with the swap happening.
820 // If no, then we will use alloc to allocate pixels to prevent garbage collection.
821 int w = rect.width() / sampleSize;
822 int h = rect.height() / sampleSize;
823 const bool swapOnly = (rect == region) && (w == decodedBitmap.width()) &&
824 (h == decodedBitmap.height()) && bm->isNull();
825 const bool needColorTable = SkBitmap::kIndex8_Config == config;
826 if (swapOnly) {
827 if (!this->allocPixelRef(&decodedBitmap, needColorTable ? colorTable : NULL)) {
828 return false;
829 }
830 } else {
831 if (!decodedBitmap.allocPixels(NULL, needColorTable ? colorTable : NULL)) {
832 return false;
833 }
834 }
835 SkAutoLockPixels alp(decodedBitmap);
836
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000837 /* Turn on interlace handling. REQUIRED if you are not using
838 * png_read_image(). To see how to handle interlacing passes,
839 * see the png_read_row() method below:
840 */
841 const int number_passes = (interlaceType != PNG_INTERLACE_NONE) ?
842 png_set_interlace_handling(png_ptr) : 1;
843
844 /* Optional call to gamma correct and add the background to the palette
845 * and update info structure. REQUIRED if you are expecting libpng to
846 * update the palette for you (ie you selected such a transform above).
847 */
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000848
849 // Direct access to png_ptr fields is deprecated in libpng > 1.2.
850#if defined(PNG_1_0_X) || defined (PNG_1_2_X)
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000851 png_ptr->pass = 0;
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000852#else
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000853 // FIXME: This sets pass as desired, but also sets iwidth. Is that ok?
854 png_set_interlaced_pass(png_ptr, 0);
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000855#endif
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000856 png_read_update_info(png_ptr, info_ptr);
857
858 int actualTop = rect.fTop;
859
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000860 if ((SkBitmap::kA8_Config == config || SkBitmap::kIndex8_Config == config)
861 && 1 == sampleSize) {
scroggo@google.com5ee18dd2013-10-21 20:47:31 +0000862 if (SkBitmap::kA8_Config == config) {
863 // For an A8 bitmap, we assume there is an alpha for speed. It is
864 // possible the bitmap is opaque, but that is an unlikely use case
865 // since it would not be very interesting.
866 reallyHasAlpha = true;
867 // A8 is only allowed if the original was GRAY.
868 SkASSERT(PNG_COLOR_TYPE_GRAY == colorType);
869 }
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000870
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000871 for (int i = 0; i < number_passes; i++) {
872 png_configure_decoder(png_ptr, &actualTop, i);
873 for (int j = 0; j < rect.fTop - actualTop; j++) {
874 uint8_t* bmRow = decodedBitmap.getAddr8(0, 0);
875 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
876 }
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000877 png_uint_32 bitmapHeight = (png_uint_32) decodedBitmap.height();
878 for (png_uint_32 y = 0; y < bitmapHeight; y++) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000879 uint8_t* bmRow = decodedBitmap.getAddr8(0, y);
880 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
881 }
882 }
883 } else {
884 SkScaledBitmapSampler::SrcConfig sc;
885 int srcBytesPerPixel = 4;
886
887 if (colorTable != NULL) {
888 sc = SkScaledBitmapSampler::kIndex;
889 srcBytesPerPixel = 1;
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000890 } else if (SkBitmap::kA8_Config == config) {
891 // A8 is only allowed if the original was GRAY.
892 SkASSERT(PNG_COLOR_TYPE_GRAY == colorType);
893 sc = SkScaledBitmapSampler::kGray;
894 srcBytesPerPixel = 1;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000895 } else if (hasAlpha) {
896 sc = SkScaledBitmapSampler::kRGBA;
897 } else {
898 sc = SkScaledBitmapSampler::kRGBX;
899 }
900
901 /* We have to pass the colortable explicitly, since we may have one
902 even if our decodedBitmap doesn't, due to the request that we
903 upscale png's palette to a direct model
904 */
905 SkAutoLockColors ctLock(colorTable);
scroggo@google.com8d239242013-10-01 17:27:15 +0000906 if (!sampler.begin(&decodedBitmap, sc, *this, ctLock.colors())) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000907 return false;
908 }
909 const int height = decodedBitmap.height();
910
911 if (number_passes > 1) {
912 SkAutoMalloc storage(origWidth * origHeight * srcBytesPerPixel);
913 uint8_t* base = (uint8_t*)storage.get();
914 size_t rb = origWidth * srcBytesPerPixel;
915
916 for (int i = 0; i < number_passes; i++) {
917 png_configure_decoder(png_ptr, &actualTop, i);
918 for (int j = 0; j < rect.fTop - actualTop; j++) {
919 uint8_t* bmRow = (uint8_t*)decodedBitmap.getPixels();
920 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
921 }
922 uint8_t* row = base;
923 for (int32_t y = 0; y < rect.height(); y++) {
924 uint8_t* bmRow = row;
925 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
926 row += rb;
927 }
928 }
929 // now sample it
930 base += sampler.srcY0() * rb;
931 for (int y = 0; y < height; y++) {
932 reallyHasAlpha |= sampler.next(base);
933 base += sampler.srcDY() * rb;
934 }
935 } else {
936 SkAutoMalloc storage(origWidth * srcBytesPerPixel);
937 uint8_t* srcRow = (uint8_t*)storage.get();
938
939 png_configure_decoder(png_ptr, &actualTop, 0);
940 skip_src_rows(png_ptr, srcRow, sampler.srcY0());
941
942 for (int i = 0; i < rect.fTop - actualTop; i++) {
943 uint8_t* bmRow = (uint8_t*)decodedBitmap.getPixels();
944 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
945 }
946 for (int y = 0; y < height; y++) {
947 uint8_t* tmp = srcRow;
948 png_read_rows(png_ptr, &tmp, png_bytepp_NULL, 1);
949 reallyHasAlpha |= sampler.next(srcRow);
950 if (y < height - 1) {
951 skip_src_rows(png_ptr, srcRow, sampler.srcDY() - 1);
952 }
953 }
954 }
955 }
956
957 if (0 != theTranspColor) {
958 reallyHasAlpha |= substituteTranspColor(&decodedBitmap, theTranspColor);
959 }
scroggo@google.com5ee18dd2013-10-21 20:47:31 +0000960 if (reallyHasAlpha && this->getRequireUnpremultipliedColors()) {
961 switch (decodedBitmap.config()) {
962 case SkBitmap::kIndex8_Config:
963 // Fall through.
964 case SkBitmap::kARGB_4444_Config:
965 // We have chosen not to support unpremul for these configs.
966 return false;
967 default: {
968 // Fall through to finish the decode. This config either
969 // supports unpremul or it is irrelevant because it has no
970 // alpha (or only alpha).
971 // These brackets prevent a warning.
972 }
973 }
commit-bot@chromium.org546f70c2013-10-03 17:13:38 +0000974 }
reed@google.com383a6972013-10-21 14:00:07 +0000975 SkAlphaType alphaType = kOpaque_SkAlphaType;
976 if (reallyHasAlpha) {
977 if (this->getRequireUnpremultipliedColors()) {
978 alphaType = kUnpremul_SkAlphaType;
979 } else {
980 alphaType = kPremul_SkAlphaType;
981 }
982 }
983 decodedBitmap.setAlphaType(alphaType);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000984
985 if (swapOnly) {
986 bm->swap(decodedBitmap);
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000987 return true;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000988 }
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000989 return this->cropBitmap(bm, &decodedBitmap, sampleSize, region.x(), region.y(),
990 region.width(), region.height(), 0, rect.y());
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000991}
992#endif
993
reed@android.com8a1c16f2008-12-17 15:59:43 +0000994///////////////////////////////////////////////////////////////////////////////
995
reed@android.com8a1c16f2008-12-17 15:59:43 +0000996#include "SkColorPriv.h"
997#include "SkUnPreMultiply.h"
998
999static void sk_write_fn(png_structp png_ptr, png_bytep data, png_size_t len) {
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +00001000 SkWStream* sk_stream = (SkWStream*)png_get_io_ptr(png_ptr);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001001 if (!sk_stream->write(data, len)) {
1002 png_error(png_ptr, "sk_write_fn Error!");
1003 }
1004}
1005
reed@android.com8a1c16f2008-12-17 15:59:43 +00001006static transform_scanline_proc choose_proc(SkBitmap::Config config,
1007 bool hasAlpha) {
1008 // we don't care about search on alpha if we're kIndex8, since only the
1009 // colortable packing cares about that distinction, not the pixels
1010 if (SkBitmap::kIndex8_Config == config) {
1011 hasAlpha = false; // we store false in the table entries for kIndex8
1012 }
rmistry@google.comd6176b02012-08-23 18:14:13 +00001013
reed@android.com8a1c16f2008-12-17 15:59:43 +00001014 static const struct {
1015 SkBitmap::Config fConfig;
1016 bool fHasAlpha;
1017 transform_scanline_proc fProc;
1018 } gMap[] = {
1019 { SkBitmap::kRGB_565_Config, false, transform_scanline_565 },
1020 { SkBitmap::kARGB_8888_Config, false, transform_scanline_888 },
1021 { SkBitmap::kARGB_8888_Config, true, transform_scanline_8888 },
1022 { SkBitmap::kARGB_4444_Config, false, transform_scanline_444 },
1023 { SkBitmap::kARGB_4444_Config, true, transform_scanline_4444 },
epoger@google.com4ce738b2012-11-16 18:44:18 +00001024 { SkBitmap::kIndex8_Config, false, transform_scanline_memcpy },
reed@android.com8a1c16f2008-12-17 15:59:43 +00001025 };
1026
1027 for (int i = SK_ARRAY_COUNT(gMap) - 1; i >= 0; --i) {
1028 if (gMap[i].fConfig == config && gMap[i].fHasAlpha == hasAlpha) {
1029 return gMap[i].fProc;
1030 }
1031 }
1032 sk_throw();
1033 return NULL;
1034}
1035
1036// return the minimum legal bitdepth (by png standards) for this many colortable
1037// entries. SkBitmap always stores in 8bits per pixel, but for colorcount <= 16,
1038// we can use fewer bits per in png
1039static int computeBitDepth(int colorCount) {
1040#if 0
1041 int bits = SkNextLog2(colorCount);
1042 SkASSERT(bits >= 1 && bits <= 8);
1043 // now we need bits itself to be a power of 2 (e.g. 1, 2, 4, 8)
1044 return SkNextPow2(bits);
1045#else
1046 // for the moment, we don't know how to pack bitdepth < 8
1047 return 8;
1048#endif
1049}
1050
1051/* Pack palette[] with the corresponding colors, and if hasAlpha is true, also
1052 pack trans[] and return the number of trans[] entries written. If hasAlpha
1053 is false, the return value will always be 0.
rmistry@google.comd6176b02012-08-23 18:14:13 +00001054
reed@android.com8a1c16f2008-12-17 15:59:43 +00001055 Note: this routine takes care of unpremultiplying the RGB values when we
1056 have alpha in the colortable, since png doesn't support premul colors
1057*/
reed@android.com6f252972009-01-14 16:46:16 +00001058static inline int pack_palette(SkColorTable* ctable,
reed@android.comb50a60c2009-01-14 17:51:08 +00001059 png_color* SK_RESTRICT palette,
1060 png_byte* SK_RESTRICT trans, bool hasAlpha) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001061 SkAutoLockColors alc(ctable);
1062 const SkPMColor* SK_RESTRICT colors = alc.colors();
1063 const int ctCount = ctable->count();
1064 int i, num_trans = 0;
1065
1066 if (hasAlpha) {
1067 /* first see if we have some number of fully opaque at the end of the
1068 ctable. PNG allows num_trans < num_palette, but all of the trans
1069 entries must come first in the palette. If I was smarter, I'd
1070 reorder the indices and ctable so that all non-opaque colors came
1071 first in the palette. But, since that would slow down the encode,
1072 I'm leaving the indices and ctable order as is, and just looking
1073 at the tail of the ctable for opaqueness.
1074 */
1075 num_trans = ctCount;
1076 for (i = ctCount - 1; i >= 0; --i) {
1077 if (SkGetPackedA32(colors[i]) != 0xFF) {
1078 break;
1079 }
1080 num_trans -= 1;
1081 }
rmistry@google.comd6176b02012-08-23 18:14:13 +00001082
reed@android.com8a1c16f2008-12-17 15:59:43 +00001083 const SkUnPreMultiply::Scale* SK_RESTRICT table =
1084 SkUnPreMultiply::GetScaleTable();
1085
1086 for (i = 0; i < num_trans; i++) {
1087 const SkPMColor c = *colors++;
1088 const unsigned a = SkGetPackedA32(c);
1089 const SkUnPreMultiply::Scale s = table[a];
1090 trans[i] = a;
1091 palette[i].red = SkUnPreMultiply::ApplyScale(s, SkGetPackedR32(c));
1092 palette[i].green = SkUnPreMultiply::ApplyScale(s,SkGetPackedG32(c));
1093 palette[i].blue = SkUnPreMultiply::ApplyScale(s, SkGetPackedB32(c));
rmistry@google.comd6176b02012-08-23 18:14:13 +00001094 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001095 // now fall out of this if-block to use common code for the trailing
1096 // opaque entries
1097 }
rmistry@google.comd6176b02012-08-23 18:14:13 +00001098
reed@android.com8a1c16f2008-12-17 15:59:43 +00001099 // these (remaining) entries are opaque
1100 for (i = num_trans; i < ctCount; i++) {
1101 SkPMColor c = *colors++;
1102 palette[i].red = SkGetPackedR32(c);
1103 palette[i].green = SkGetPackedG32(c);
1104 palette[i].blue = SkGetPackedB32(c);
1105 }
1106 return num_trans;
1107}
1108
1109class SkPNGImageEncoder : public SkImageEncoder {
1110protected:
commit-bot@chromium.orga936e372013-03-14 14:42:18 +00001111 virtual bool onEncode(SkWStream* stream, const SkBitmap& bm, int quality) SK_OVERRIDE;
tomhudson@google.com5c210c72011-07-28 21:06:40 +00001112private:
1113 bool doEncode(SkWStream* stream, const SkBitmap& bm,
1114 const bool& hasAlpha, int colorType,
1115 int bitDepth, SkBitmap::Config config,
1116 png_color_8& sig_bit);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +00001117
1118 typedef SkImageEncoder INHERITED;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001119};
1120
1121bool SkPNGImageEncoder::onEncode(SkWStream* stream, const SkBitmap& bitmap,
1122 int /*quality*/) {
reed@google.com44699382013-10-31 17:28:30 +00001123 SkBitmap::Config config = bitmap.config();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001124
1125 const bool hasAlpha = !bitmap.isOpaque();
1126 int colorType = PNG_COLOR_MASK_COLOR;
1127 int bitDepth = 8; // default for color
1128 png_color_8 sig_bit;
1129
1130 switch (config) {
1131 case SkBitmap::kIndex8_Config:
1132 colorType |= PNG_COLOR_MASK_PALETTE;
1133 // fall through to the ARGB_8888 case
1134 case SkBitmap::kARGB_8888_Config:
1135 sig_bit.red = 8;
1136 sig_bit.green = 8;
1137 sig_bit.blue = 8;
1138 sig_bit.alpha = 8;
1139 break;
1140 case SkBitmap::kARGB_4444_Config:
1141 sig_bit.red = 4;
1142 sig_bit.green = 4;
1143 sig_bit.blue = 4;
1144 sig_bit.alpha = 4;
1145 break;
1146 case SkBitmap::kRGB_565_Config:
1147 sig_bit.red = 5;
1148 sig_bit.green = 6;
1149 sig_bit.blue = 5;
1150 sig_bit.alpha = 0;
1151 break;
1152 default:
1153 return false;
1154 }
rmistry@google.comd6176b02012-08-23 18:14:13 +00001155
reed@android.com8a1c16f2008-12-17 15:59:43 +00001156 if (hasAlpha) {
1157 // don't specify alpha if we're a palette, even if our ctable has alpha
1158 if (!(colorType & PNG_COLOR_MASK_PALETTE)) {
1159 colorType |= PNG_COLOR_MASK_ALPHA;
1160 }
1161 } else {
1162 sig_bit.alpha = 0;
1163 }
rmistry@google.comd6176b02012-08-23 18:14:13 +00001164
reed@android.com8a1c16f2008-12-17 15:59:43 +00001165 SkAutoLockPixels alp(bitmap);
1166 // readyToDraw checks for pixels (and colortable if that is required)
1167 if (!bitmap.readyToDraw()) {
1168 return false;
1169 }
1170
1171 // we must do this after we have locked the pixels
1172 SkColorTable* ctable = bitmap.getColorTable();
1173 if (NULL != ctable) {
1174 if (ctable->count() == 0) {
1175 return false;
1176 }
1177 // check if we can store in fewer than 8 bits
1178 bitDepth = computeBitDepth(ctable->count());
1179 }
1180
tomhudson@google.com5c210c72011-07-28 21:06:40 +00001181 return doEncode(stream, bitmap, hasAlpha, colorType,
1182 bitDepth, config, sig_bit);
1183}
1184
1185bool SkPNGImageEncoder::doEncode(SkWStream* stream, const SkBitmap& bitmap,
1186 const bool& hasAlpha, int colorType,
1187 int bitDepth, SkBitmap::Config config,
1188 png_color_8& sig_bit) {
1189
reed@android.com8a1c16f2008-12-17 15:59:43 +00001190 png_structp png_ptr;
1191 png_infop info_ptr;
1192
1193 png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, sk_error_fn,
1194 NULL);
1195 if (NULL == png_ptr) {
1196 return false;
1197 }
1198
1199 info_ptr = png_create_info_struct(png_ptr);
1200 if (NULL == info_ptr) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +00001201 png_destroy_write_struct(&png_ptr, png_infopp_NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001202 return false;
1203 }
1204
1205 /* Set error handling. REQUIRED if you aren't supplying your own
1206 * error handling functions in the png_create_write_struct() call.
1207 */
1208 if (setjmp(png_jmpbuf(png_ptr))) {
1209 png_destroy_write_struct(&png_ptr, &info_ptr);
1210 return false;
1211 }
1212
commit-bot@chromium.orga936e372013-03-14 14:42:18 +00001213 png_set_write_fn(png_ptr, (void*)stream, sk_write_fn, png_flush_ptr_NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001214
1215 /* Set the image information here. Width and height are up to 2^31,
1216 * bit_depth is one of 1, 2, 4, 8, or 16, but valid values also depend on
1217 * the color_type selected. color_type is one of PNG_COLOR_TYPE_GRAY,
1218 * PNG_COLOR_TYPE_GRAY_ALPHA, PNG_COLOR_TYPE_PALETTE, PNG_COLOR_TYPE_RGB,
1219 * or PNG_COLOR_TYPE_RGB_ALPHA. interlace is either PNG_INTERLACE_NONE or
1220 * PNG_INTERLACE_ADAM7, and the compression_type and filter_type MUST
1221 * currently be PNG_COMPRESSION_TYPE_BASE and PNG_FILTER_TYPE_BASE. REQUIRED
1222 */
1223
1224 png_set_IHDR(png_ptr, info_ptr, bitmap.width(), bitmap.height(),
1225 bitDepth, colorType,
1226 PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE,
1227 PNG_FILTER_TYPE_BASE);
1228
reed@android.com61898772009-07-07 19:38:01 +00001229 // set our colortable/trans arrays if needed
1230 png_color paletteColors[256];
1231 png_byte trans[256];
1232 if (SkBitmap::kIndex8_Config == config) {
1233 SkColorTable* ct = bitmap.getColorTable();
1234 int numTrans = pack_palette(ct, paletteColors, trans, hasAlpha);
1235 png_set_PLTE(png_ptr, info_ptr, paletteColors, ct->count());
1236 if (numTrans > 0) {
1237 png_set_tRNS(png_ptr, info_ptr, trans, numTrans, NULL);
1238 }
1239 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001240
1241 png_set_sBIT(png_ptr, info_ptr, &sig_bit);
1242 png_write_info(png_ptr, info_ptr);
1243
1244 const char* srcImage = (const char*)bitmap.getPixels();
1245 SkAutoSMalloc<1024> rowStorage(bitmap.width() << 2);
1246 char* storage = (char*)rowStorage.get();
1247 transform_scanline_proc proc = choose_proc(config, hasAlpha);
1248
1249 for (int y = 0; y < bitmap.height(); y++) {
1250 png_bytep row_ptr = (png_bytep)storage;
1251 proc(srcImage, bitmap.width(), storage);
1252 png_write_rows(png_ptr, &row_ptr, 1);
1253 srcImage += bitmap.rowBytes();
1254 }
1255
1256 png_write_end(png_ptr, info_ptr);
1257
1258 /* clean up after the write, and free any memory allocated */
1259 png_destroy_write_struct(&png_ptr, &info_ptr);
1260 return true;
1261}
1262
reed@android.com00bf85a2009-01-22 13:04:56 +00001263///////////////////////////////////////////////////////////////////////////////
robertphillips@google.comec51cb82012-03-23 18:13:47 +00001264DEFINE_DECODER_CREATOR(PNGImageDecoder);
1265DEFINE_ENCODER_CREATOR(PNGImageEncoder);
1266///////////////////////////////////////////////////////////////////////////////
reed@android.com00bf85a2009-01-22 13:04:56 +00001267
scroggo@google.comb5571b32013-09-25 21:34:24 +00001268static bool is_png(SkStreamRewindable* stream) {
reed@android.com00bf85a2009-01-22 13:04:56 +00001269 char buf[PNG_BYTES_TO_CHECK];
1270 if (stream->read(buf, PNG_BYTES_TO_CHECK) == PNG_BYTES_TO_CHECK &&
1271 !png_sig_cmp((png_bytep) buf, (png_size_t)0, PNG_BYTES_TO_CHECK)) {
scroggo@google.com39edf4c2013-04-25 17:33:51 +00001272 return true;
1273 }
1274 return false;
1275}
1276
scroggo@google.comb5571b32013-09-25 21:34:24 +00001277SkImageDecoder* sk_libpng_dfactory(SkStreamRewindable* stream) {
scroggo@google.com39edf4c2013-04-25 17:33:51 +00001278 if (is_png(stream)) {
reed@android.com00bf85a2009-01-22 13:04:56 +00001279 return SkNEW(SkPNGImageDecoder);
1280 }
1281 return NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001282}
1283
scroggo@google.comb5571b32013-09-25 21:34:24 +00001284static SkImageDecoder::Format get_format_png(SkStreamRewindable* stream) {
scroggo@google.com39edf4c2013-04-25 17:33:51 +00001285 if (is_png(stream)) {
1286 return SkImageDecoder::kPNG_Format;
1287 }
1288 return SkImageDecoder::kUnknown_Format;
1289}
1290
reed@android.comdfee5792010-04-15 14:24:50 +00001291SkImageEncoder* sk_libpng_efactory(SkImageEncoder::Type t) {
reed@android.com00bf85a2009-01-22 13:04:56 +00001292 return (SkImageEncoder::kPNG_Type == t) ? SkNEW(SkPNGImageEncoder) : NULL;
1293}
1294
mtklein@google.combd6343b2013-09-04 17:20:18 +00001295static SkImageDecoder_DecodeReg gDReg(sk_libpng_dfactory);
1296static SkImageDecoder_FormatReg gFormatReg(get_format_png);
1297static SkImageEncoder_EncodeReg gEReg(sk_libpng_efactory);