blob: e46def4cab49345643a94511a123804e31e4bde5 [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))) {
258 return false;
259 }
260
261 /* If you are using replacement read functions, instead of calling
262 * png_init_io() here you would call:
263 */
264 png_set_read_fn(png_ptr, (void *)sk_stream, sk_read_fn);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000265#ifdef SK_BUILD_FOR_ANDROID
266 png_set_seek_fn(png_ptr, sk_seek_fn);
267#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000268 /* where user_io_ptr is a structure you want available to the callbacks */
269 /* If we have already read some of the signature */
270// png_set_sig_bytes(png_ptr, 0 /* sig_read */ );
271
272 // hookup our peeker so we can see any user-chunks the caller may be interested in
273 png_set_keep_unknown_chunks(png_ptr, PNG_HANDLE_CHUNK_ALWAYS, (png_byte*)"", 0);
274 if (this->getPeeker()) {
275 png_set_read_user_chunk_fn(png_ptr, (png_voidp)this->getPeeker(), sk_read_user_chunk);
276 }
277
278 /* The call to png_read_info() gives us all of the information from the
279 * PNG file before the first IDAT (image data chunk). */
280 png_read_info(png_ptr, info_ptr);
281 png_uint_32 origWidth, origHeight;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000282 int bitDepth, colorType;
283 png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
284 &colorType, int_p_NULL, int_p_NULL, int_p_NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000285
286 /* tell libpng to strip 16 bit/color files down to 8 bits/color */
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000287 if (bitDepth == 16) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000288 png_set_strip_16(png_ptr);
289 }
290 /* Extract multiple pixels with bit depths of 1, 2, and 4 from a single
291 * byte into separate bytes (useful for paletted and grayscale images). */
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000292 if (bitDepth < 8) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000293 png_set_packing(png_ptr);
294 }
295 /* Expand grayscale images to the full 8 bits from 1, 2, or 4 bits/pixel */
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000296 if (colorType == PNG_COLOR_TYPE_GRAY && bitDepth < 8) {
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000297 png_set_expand_gray_1_2_4_to_8(png_ptr);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000298 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000299
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000300 return true;
301}
302
303bool SkPNGImageDecoder::onDecode(SkStream* sk_stream, SkBitmap* decodedBitmap,
304 Mode mode) {
305 png_structp png_ptr;
306 png_infop info_ptr;
307
308 if (!onDecodeInit(sk_stream, &png_ptr, &info_ptr)) {
309 return false;
310 }
311
312 if (setjmp(png_jmpbuf(png_ptr))) {
313 return false;
314 }
315
316 PNGAutoClean autoClean(png_ptr, info_ptr);
317
318 png_uint_32 origWidth, origHeight;
319 int bitDepth, colorType, interlaceType;
320 png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
321 &colorType, &interlaceType, int_p_NULL, int_p_NULL);
rmistry@google.comd6176b02012-08-23 18:14:13 +0000322
reed@android.com8a1c16f2008-12-17 15:59:43 +0000323 SkBitmap::Config config;
324 bool hasAlpha = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000325 SkPMColor theTranspColor = 0; // 0 tells us not to try to match
rmistry@google.comd6176b02012-08-23 18:14:13 +0000326
scroggo@google.com8d239242013-10-01 17:27:15 +0000327 if (!this->getBitmapConfig(png_ptr, info_ptr, &config, &hasAlpha, &theTranspColor)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000328 return false;
329 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000330
reed@android.com8a1c16f2008-12-17 15:59:43 +0000331 const int sampleSize = this->getSampleSize();
332 SkScaledBitmapSampler sampler(origWidth, origHeight, sampleSize);
scroggo@google.combc69ce92013-07-09 15:45:14 +0000333 decodedBitmap->setConfig(config, sampler.scaledWidth(), sampler.scaledHeight());
reed@android.com8a1c16f2008-12-17 15:59:43 +0000334
reed@android.com8a1c16f2008-12-17 15:59:43 +0000335 if (SkImageDecoder::kDecodeBounds_Mode == mode) {
336 return true;
337 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000338
reed@android.com8a1c16f2008-12-17 15:59:43 +0000339 // from here down we are concerned with colortables and pixels
340
341 // we track if we actually see a non-opaque pixels, since sometimes a PNG sets its colortype
342 // to |= PNG_COLOR_MASK_ALPHA, but all of its pixels are in fact opaque. We care, since we
343 // draw lots faster if we can flag the bitmap has being opaque
344 bool reallyHasAlpha = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000345 SkColorTable* colorTable = NULL;
346
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000347 if (colorType == PNG_COLOR_TYPE_PALETTE) {
348 decodePalette(png_ptr, info_ptr, &hasAlpha, &reallyHasAlpha, &colorTable);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000349 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000350
reed@android.com8a1c16f2008-12-17 15:59:43 +0000351 SkAutoUnref aur(colorTable);
352
scroggo@google.combc69ce92013-07-09 15:45:14 +0000353 if (!this->allocPixelRef(decodedBitmap,
354 SkBitmap::kIndex8_Config == config ? colorTable : NULL)) {
355 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000356 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000357
reed@android.com8a1c16f2008-12-17 15:59:43 +0000358 SkAutoLockPixels alp(*decodedBitmap);
359
reed@android.com8a1c16f2008-12-17 15:59:43 +0000360 /* Turn on interlace handling. REQUIRED if you are not using
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000361 * png_read_image(). To see how to handle interlacing passes,
362 * see the png_read_row() method below:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000363 */
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000364 const int number_passes = (interlaceType != PNG_INTERLACE_NONE) ?
365 png_set_interlace_handling(png_ptr) : 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000366
367 /* Optional call to gamma correct and add the background to the palette
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000368 * and update info structure. REQUIRED if you are expecting libpng to
369 * update the palette for you (ie you selected such a transform above).
reed@android.com8a1c16f2008-12-17 15:59:43 +0000370 */
371 png_read_update_info(png_ptr, info_ptr);
372
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000373 if ((SkBitmap::kA8_Config == config || SkBitmap::kIndex8_Config == config)
374 && 1 == sampleSize) {
scroggo@google.com5ee18dd2013-10-21 20:47:31 +0000375 if (SkBitmap::kA8_Config == config) {
376 // For an A8 bitmap, we assume there is an alpha for speed. It is
377 // possible the bitmap is opaque, but that is an unlikely use case
378 // since it would not be very interesting.
379 reallyHasAlpha = true;
380 // A8 is only allowed if the original was GRAY.
381 SkASSERT(PNG_COLOR_TYPE_GRAY == colorType);
382 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000383 for (int i = 0; i < number_passes; i++) {
384 for (png_uint_32 y = 0; y < origHeight; y++) {
385 uint8_t* bmRow = decodedBitmap->getAddr8(0, y);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000386 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000387 }
388 }
389 } else {
390 SkScaledBitmapSampler::SrcConfig sc;
391 int srcBytesPerPixel = 4;
rmistry@google.comd6176b02012-08-23 18:14:13 +0000392
reed@android.com11344262009-07-08 20:09:23 +0000393 if (colorTable != NULL) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000394 sc = SkScaledBitmapSampler::kIndex;
395 srcBytesPerPixel = 1;
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000396 } else if (SkBitmap::kA8_Config == config) {
397 // A8 is only allowed if the original was GRAY.
398 SkASSERT(PNG_COLOR_TYPE_GRAY == colorType);
399 sc = SkScaledBitmapSampler::kGray;
400 srcBytesPerPixel = 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000401 } else if (hasAlpha) {
402 sc = SkScaledBitmapSampler::kRGBA;
403 } else {
404 sc = SkScaledBitmapSampler::kRGBX;
405 }
reed@android.com11344262009-07-08 20:09:23 +0000406
407 /* We have to pass the colortable explicitly, since we may have one
408 even if our decodedBitmap doesn't, due to the request that we
409 upscale png's palette to a direct model
410 */
411 SkAutoLockColors ctLock(colorTable);
scroggo@google.com8d239242013-10-01 17:27:15 +0000412 if (!sampler.begin(decodedBitmap, sc, *this, ctLock.colors())) {
reed@android.com862e91b2009-04-28 15:27:07 +0000413 return false;
414 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000415 const int height = decodedBitmap->height();
416
reed@android.com862e91b2009-04-28 15:27:07 +0000417 if (number_passes > 1) {
418 SkAutoMalloc storage(origWidth * origHeight * srcBytesPerPixel);
419 uint8_t* base = (uint8_t*)storage.get();
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000420 size_t rowBytes = origWidth * srcBytesPerPixel;
reed@android.coma8a8b8b2009-05-04 15:00:11 +0000421
reed@android.com862e91b2009-04-28 15:27:07 +0000422 for (int i = 0; i < number_passes; i++) {
423 uint8_t* row = base;
424 for (png_uint_32 y = 0; y < origHeight; y++) {
425 uint8_t* bmRow = row;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000426 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
427 row += rowBytes;
reed@android.com862e91b2009-04-28 15:27:07 +0000428 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000429 }
reed@android.com862e91b2009-04-28 15:27:07 +0000430 // now sample it
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000431 base += sampler.srcY0() * rowBytes;
reed@android.com862e91b2009-04-28 15:27:07 +0000432 for (int y = 0; y < height; y++) {
433 reallyHasAlpha |= sampler.next(base);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000434 base += sampler.srcDY() * rowBytes;
reed@android.com862e91b2009-04-28 15:27:07 +0000435 }
436 } else {
437 SkAutoMalloc storage(origWidth * srcBytesPerPixel);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000438 uint8_t* srcRow = (uint8_t*)storage.get();
439 skip_src_rows(png_ptr, srcRow, sampler.srcY0());
440
441 for (int y = 0; y < height; y++) {
442 uint8_t* tmp = srcRow;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000443 png_read_rows(png_ptr, &tmp, png_bytepp_NULL, 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000444 reallyHasAlpha |= sampler.next(srcRow);
445 if (y < height - 1) {
446 skip_src_rows(png_ptr, srcRow, sampler.srcDY() - 1);
447 }
448 }
reed@android.com862e91b2009-04-28 15:27:07 +0000449
reed@android.com8a1c16f2008-12-17 15:59:43 +0000450 // skip the rest of the rows (if any)
451 png_uint_32 read = (height - 1) * sampler.srcDY() +
452 sampler.srcY0() + 1;
453 SkASSERT(read <= origHeight);
454 skip_src_rows(png_ptr, srcRow, origHeight - read);
455 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000456 }
457
458 /* read rest of file, and get additional chunks in info_ptr - REQUIRED */
459 png_read_end(png_ptr, info_ptr);
460
461 if (0 != theTranspColor) {
462 reallyHasAlpha |= substituteTranspColor(decodedBitmap, theTranspColor);
463 }
scroggo@google.com5ee18dd2013-10-21 20:47:31 +0000464 if (reallyHasAlpha && this->getRequireUnpremultipliedColors()) {
465 switch (decodedBitmap->config()) {
466 case SkBitmap::kIndex8_Config:
467 // Fall through.
468 case SkBitmap::kARGB_4444_Config:
469 // We have chosen not to support unpremul for these configs.
470 return false;
471 default: {
472 // Fall through to finish the decode. This config either
473 // supports unpremul or it is irrelevant because it has no
474 // alpha (or only alpha).
475 // These brackets prevent a warning.
476 }
477 }
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000478 }
scroggo@google.com5ee18dd2013-10-21 20:47:31 +0000479
reed@google.com383a6972013-10-21 14:00:07 +0000480 SkAlphaType alphaType = kOpaque_SkAlphaType;
481 if (reallyHasAlpha) {
482 if (this->getRequireUnpremultipliedColors()) {
483 alphaType = kUnpremul_SkAlphaType;
484 } else {
485 alphaType = kPremul_SkAlphaType;
486 }
487 }
488 decodedBitmap->setAlphaType(alphaType);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000489 return true;
490}
491
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000492
493
494bool SkPNGImageDecoder::getBitmapConfig(png_structp png_ptr, png_infop info_ptr,
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000495 SkBitmap::Config* SK_RESTRICT configp,
496 bool* SK_RESTRICT hasAlphap,
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000497 SkPMColor* SK_RESTRICT theTranspColorp) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000498 png_uint_32 origWidth, origHeight;
499 int bitDepth, colorType;
500 png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
501 &colorType, int_p_NULL, int_p_NULL, int_p_NULL);
502
503 // check for sBIT chunk data, in case we should disable dithering because
504 // our data is not truely 8bits per component
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000505 png_color_8p sig_bit;
scroggo@google.com8d239242013-10-01 17:27:15 +0000506 if (this->getDitherImage() && png_get_sBIT(png_ptr, info_ptr, &sig_bit)) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000507#if 0
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000508 SkDebugf("----- sBIT %d %d %d %d\n", sig_bit->red, sig_bit->green,
509 sig_bit->blue, sig_bit->alpha);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000510#endif
511 // 0 seems to indicate no information available
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000512 if (pos_le(sig_bit->red, SK_R16_BITS) &&
513 pos_le(sig_bit->green, SK_G16_BITS) &&
514 pos_le(sig_bit->blue, SK_B16_BITS)) {
scroggo@google.com8d239242013-10-01 17:27:15 +0000515 this->setDitherImage(false);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000516 }
517 }
518
519 if (colorType == PNG_COLOR_TYPE_PALETTE) {
520 bool paletteHasAlpha = hasTransparencyInPalette(png_ptr, info_ptr);
521 *configp = this->getPrefConfig(kIndex_SrcDepth, paletteHasAlpha);
522 // now see if we can upscale to their requested config
523 if (!canUpscalePaletteToConfig(*configp, paletteHasAlpha)) {
524 *configp = SkBitmap::kIndex8_Config;
525 }
526 } else {
527 png_color_16p transpColor = NULL;
528 int numTransp = 0;
529
530 png_get_tRNS(png_ptr, info_ptr, NULL, &numTransp, &transpColor);
531
532 bool valid = png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS);
533
534 if (valid && numTransp == 1 && transpColor != NULL) {
535 /* Compute our transparent color, which we'll match against later.
536 We don't really handle 16bit components properly here, since we
537 do our compare *after* the values have been knocked down to 8bit
538 which means we will find more matches than we should. The real
539 fix seems to be to see the actual 16bit components, do the
540 compare, and then knock it down to 8bits ourselves.
541 */
542 if (colorType & PNG_COLOR_MASK_COLOR) {
543 if (16 == bitDepth) {
544 *theTranspColorp = SkPackARGB32(0xFF, transpColor->red >> 8,
545 transpColor->green >> 8,
546 transpColor->blue >> 8);
547 } else {
halcanary@google.comfed30372013-10-04 12:46:45 +0000548 /* We apply the mask because in a very small
549 number of corrupt PNGs, (transpColor->red > 255)
550 and (bitDepth == 8), for certain versions of libpng. */
551 *theTranspColorp = SkPackARGB32(0xFF,
552 0xFF & (transpColor->red),
553 0xFF & (transpColor->green),
554 0xFF & (transpColor->blue));
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000555 }
556 } else { // gray
557 if (16 == bitDepth) {
558 *theTranspColorp = SkPackARGB32(0xFF, transpColor->gray >> 8,
559 transpColor->gray >> 8,
560 transpColor->gray >> 8);
561 } else {
halcanary@google.comfed30372013-10-04 12:46:45 +0000562 /* We apply the mask because in a very small
563 number of corrupt PNGs, (transpColor->red >
564 255) and (bitDepth == 8), for certain versions
565 of libpng. For safety we assume the same could
566 happen with a grayscale PNG. */
567 *theTranspColorp = SkPackARGB32(0xFF,
568 0xFF & (transpColor->gray),
569 0xFF & (transpColor->gray),
570 0xFF & (transpColor->gray));
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000571 }
572 }
573 }
574
575 if (valid ||
576 PNG_COLOR_TYPE_RGB_ALPHA == colorType ||
577 PNG_COLOR_TYPE_GRAY_ALPHA == colorType) {
578 *hasAlphap = true;
579 }
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000580
581 SrcDepth srcDepth = k32Bit_SrcDepth;
582 if (PNG_COLOR_TYPE_GRAY == colorType) {
583 srcDepth = k8BitGray_SrcDepth;
scroggo@google.com8e2ef012013-07-18 20:14:45 +0000584 // Remove this assert, which fails on desk_pokemonwiki.skp
585 //SkASSERT(!*hasAlphap);
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000586 }
587
588 *configp = this->getPrefConfig(srcDepth, *hasAlphap);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000589 // now match the request against our capabilities
590 if (*hasAlphap) {
591 if (*configp != SkBitmap::kARGB_4444_Config) {
592 *configp = SkBitmap::kARGB_8888_Config;
593 }
594 } else {
scroggo@google.com354fd972013-10-02 15:50:19 +0000595 if (SkBitmap::kA8_Config == *configp) {
596 if (k8BitGray_SrcDepth != srcDepth) {
597 // Converting a non grayscale image to A8 is not currently supported.
598 *configp = SkBitmap::kARGB_8888_Config;
599 }
600 } else if (*configp != SkBitmap::kRGB_565_Config &&
601 *configp != SkBitmap::kARGB_4444_Config) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000602 *configp = SkBitmap::kARGB_8888_Config;
603 }
604 }
605 }
606
607 // sanity check for size
608 {
609 Sk64 size;
610 size.setMul(origWidth, origHeight);
611 if (size.isNeg() || !size.is32()) {
612 return false;
613 }
614 // now check that if we are 4-bytes per pixel, we also don't overflow
615 if (size.get32() > (0x7FFFFFFF >> 2)) {
616 return false;
617 }
618 }
619
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000620 if (!this->chooseFromOneChoice(*configp, origWidth, origHeight)) {
621 return false;
622 }
623
624 // If the image has alpha and the decoder wants unpremultiplied
625 // colors, the only supported config is 8888.
626 if (this->getRequireUnpremultipliedColors() && *hasAlphap) {
627 *configp = SkBitmap::kARGB_8888_Config;
628 }
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000629
630 if (fImageIndex != NULL) {
631 if (SkBitmap::kNo_Config == fImageIndex->fConfig) {
632 // This is the first time for this subset decode. From now on,
633 // all decodes must be in the same config.
634 fImageIndex->fConfig = *configp;
635 } else if (fImageIndex->fConfig != *configp) {
636 // Requesting a different config for a subsequent decode is not
637 // supported. Report failure before we make changes to png_ptr.
638 return false;
639 }
640 }
641
642 bool convertGrayToRGB = PNG_COLOR_TYPE_GRAY == colorType
643 && *configp != SkBitmap::kA8_Config;
644
645 // Unless the user is requesting A8, convert a grayscale image into RGB.
646 // GRAY_ALPHA will always be converted to RGB
647 if (convertGrayToRGB || colorType == PNG_COLOR_TYPE_GRAY_ALPHA) {
648 png_set_gray_to_rgb(png_ptr);
649 }
650
651 // Add filler (or alpha) byte (after each RGB triplet) if necessary.
652 if (colorType == PNG_COLOR_TYPE_RGB || convertGrayToRGB) {
653 png_set_filler(png_ptr, 0xff, PNG_FILLER_AFTER);
654 }
655
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000656 return true;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000657}
658
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000659typedef uint32_t (*PackColorProc)(U8CPU a, U8CPU r, U8CPU g, U8CPU b);
660
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000661bool SkPNGImageDecoder::decodePalette(png_structp png_ptr, png_infop info_ptr,
662 bool *hasAlphap, bool *reallyHasAlphap,
663 SkColorTable **colorTablep) {
664 int numPalette;
665 png_colorp palette;
666 png_bytep trans;
667 int numTrans;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000668
669 png_get_PLTE(png_ptr, info_ptr, &palette, &numPalette);
670
671 /* BUGGY IMAGE WORKAROUND
672
673 We hit some images (e.g. fruit_.png) who contain bytes that are == colortable_count
674 which is a problem since we use the byte as an index. To work around this we grow
675 the colortable by 1 (if its < 256) and duplicate the last color into that slot.
676 */
677 int colorCount = numPalette + (numPalette < 256);
reed@google.com0a6151d2013-10-10 14:44:56 +0000678 SkPMColor colorStorage[256]; // worst-case storage
679 SkPMColor* colorPtr = colorStorage;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000680
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000681 if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
682 png_get_tRNS(png_ptr, info_ptr, &trans, &numTrans, NULL);
683 *hasAlphap = (numTrans > 0);
684 } else {
685 numTrans = 0;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000686 }
reed@google.com0a6151d2013-10-10 14:44:56 +0000687
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000688 // check for bad images that might make us crash
689 if (numTrans > numPalette) {
690 numTrans = numPalette;
691 }
692
693 int index = 0;
694 int transLessThanFF = 0;
695
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000696 // Choose which function to use to create the color table. If the final destination's
697 // config is unpremultiplied, the color table will store unpremultiplied colors.
698 PackColorProc proc;
699 if (this->getRequireUnpremultipliedColors()) {
700 proc = &SkPackARGB32NoCheck;
701 } else {
702 proc = &SkPreMultiplyARGB;
703 }
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000704 for (; index < numTrans; index++) {
705 transLessThanFF |= (int)*trans - 0xFF;
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000706 *colorPtr++ = proc(*trans++, palette->red, palette->green, palette->blue);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000707 palette++;
708 }
reed@google.com0a6151d2013-10-10 14:44:56 +0000709 bool reallyHasAlpha = (transLessThanFF < 0);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000710
711 for (; index < numPalette; index++) {
712 *colorPtr++ = SkPackARGB32(0xFF, palette->red, palette->green, palette->blue);
713 palette++;
714 }
715
716 // see BUGGY IMAGE WORKAROUND comment above
717 if (numPalette < 256) {
718 *colorPtr = colorPtr[-1];
719 }
reed@google.com0a6151d2013-10-10 14:44:56 +0000720
721 SkAlphaType alphaType = kOpaque_SkAlphaType;
722 if (reallyHasAlpha) {
723 if (this->getRequireUnpremultipliedColors()) {
724 alphaType = kUnpremul_SkAlphaType;
725 } else {
726 alphaType = kPremul_SkAlphaType;
727 }
728 }
729
730 *colorTablep = SkNEW_ARGS(SkColorTable,
731 (colorStorage, colorCount, alphaType));
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000732 *reallyHasAlphap = reallyHasAlpha;
733 return true;
734}
735
736#ifdef SK_BUILD_FOR_ANDROID
737
scroggo@google.comb5571b32013-09-25 21:34:24 +0000738bool SkPNGImageDecoder::onBuildTileIndex(SkStreamRewindable* sk_stream, int *width, int *height) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000739 png_structp png_ptr;
740 png_infop info_ptr;
741
742 if (!onDecodeInit(sk_stream, &png_ptr, &info_ptr)) {
743 return false;
744 }
745
746 if (setjmp(png_jmpbuf(png_ptr)) != 0) {
747 png_destroy_read_struct(&png_ptr, &info_ptr, png_infopp_NULL);
748 return false;
749 }
750
751 png_uint_32 origWidth, origHeight;
752 int bitDepth, colorType;
753 png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
754 &colorType, int_p_NULL, int_p_NULL, int_p_NULL);
755
756 *width = origWidth;
757 *height = origHeight;
758
759 png_build_index(png_ptr);
760
761 if (fImageIndex) {
762 SkDELETE(fImageIndex);
763 }
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000764 fImageIndex = SkNEW_ARGS(SkPNGImageIndex, (sk_stream, png_ptr, info_ptr));
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000765
766 return true;
767}
768
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000769bool SkPNGImageDecoder::onDecodeSubset(SkBitmap* bm, const SkIRect& region) {
770 if (NULL == fImageIndex) {
771 return false;
772 }
773
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000774 png_structp png_ptr = fImageIndex->fPng_ptr;
775 png_infop info_ptr = fImageIndex->fInfo_ptr;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000776 if (setjmp(png_jmpbuf(png_ptr))) {
777 return false;
778 }
779
780 png_uint_32 origWidth, origHeight;
781 int bitDepth, colorType, interlaceType;
782 png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
783 &colorType, &interlaceType, int_p_NULL, int_p_NULL);
784
785 SkIRect rect = SkIRect::MakeWH(origWidth, origHeight);
786
787 if (!rect.intersect(region)) {
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000788 // If the requested region is entirely outside the image, just
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000789 // returns false
790 return false;
791 }
792
793 SkBitmap::Config config;
794 bool hasAlpha = false;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000795 SkPMColor theTranspColor = 0; // 0 tells us not to try to match
796
scroggo@google.com8d239242013-10-01 17:27:15 +0000797 if (!this->getBitmapConfig(png_ptr, info_ptr, &config, &hasAlpha, &theTranspColor)) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000798 return false;
799 }
800
801 const int sampleSize = this->getSampleSize();
802 SkScaledBitmapSampler sampler(origWidth, rect.height(), sampleSize);
803
804 SkBitmap decodedBitmap;
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000805 decodedBitmap.setConfig(config, sampler.scaledWidth(), sampler.scaledHeight());
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000806
807 // from here down we are concerned with colortables and pixels
808
809 // we track if we actually see a non-opaque pixels, since sometimes a PNG sets its colortype
810 // to |= PNG_COLOR_MASK_ALPHA, but all of its pixels are in fact opaque. We care, since we
811 // draw lots faster if we can flag the bitmap has being opaque
812 bool reallyHasAlpha = false;
813 SkColorTable* colorTable = NULL;
814
815 if (colorType == PNG_COLOR_TYPE_PALETTE) {
816 decodePalette(png_ptr, info_ptr, &hasAlpha, &reallyHasAlpha, &colorTable);
817 }
818
819 SkAutoUnref aur(colorTable);
820
821 // Check ahead of time if the swap(dest, src) is possible.
822 // If yes, then we will stick to AllocPixelRef since it's cheaper with the swap happening.
823 // If no, then we will use alloc to allocate pixels to prevent garbage collection.
824 int w = rect.width() / sampleSize;
825 int h = rect.height() / sampleSize;
826 const bool swapOnly = (rect == region) && (w == decodedBitmap.width()) &&
827 (h == decodedBitmap.height()) && bm->isNull();
828 const bool needColorTable = SkBitmap::kIndex8_Config == config;
829 if (swapOnly) {
830 if (!this->allocPixelRef(&decodedBitmap, needColorTable ? colorTable : NULL)) {
831 return false;
832 }
833 } else {
834 if (!decodedBitmap.allocPixels(NULL, needColorTable ? colorTable : NULL)) {
835 return false;
836 }
837 }
838 SkAutoLockPixels alp(decodedBitmap);
839
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000840 /* Turn on interlace handling. REQUIRED if you are not using
841 * png_read_image(). To see how to handle interlacing passes,
842 * see the png_read_row() method below:
843 */
844 const int number_passes = (interlaceType != PNG_INTERLACE_NONE) ?
845 png_set_interlace_handling(png_ptr) : 1;
846
847 /* Optional call to gamma correct and add the background to the palette
848 * and update info structure. REQUIRED if you are expecting libpng to
849 * update the palette for you (ie you selected such a transform above).
850 */
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000851
852 // Direct access to png_ptr fields is deprecated in libpng > 1.2.
853#if defined(PNG_1_0_X) || defined (PNG_1_2_X)
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000854 png_ptr->pass = 0;
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000855#else
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000856 // FIXME: This sets pass as desired, but also sets iwidth. Is that ok?
857 png_set_interlaced_pass(png_ptr, 0);
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000858#endif
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000859 png_read_update_info(png_ptr, info_ptr);
860
861 int actualTop = rect.fTop;
862
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000863 if ((SkBitmap::kA8_Config == config || SkBitmap::kIndex8_Config == config)
864 && 1 == sampleSize) {
scroggo@google.com5ee18dd2013-10-21 20:47:31 +0000865 if (SkBitmap::kA8_Config == config) {
866 // For an A8 bitmap, we assume there is an alpha for speed. It is
867 // possible the bitmap is opaque, but that is an unlikely use case
868 // since it would not be very interesting.
869 reallyHasAlpha = true;
870 // A8 is only allowed if the original was GRAY.
871 SkASSERT(PNG_COLOR_TYPE_GRAY == colorType);
872 }
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000873
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000874 for (int i = 0; i < number_passes; i++) {
875 png_configure_decoder(png_ptr, &actualTop, i);
876 for (int j = 0; j < rect.fTop - actualTop; j++) {
877 uint8_t* bmRow = decodedBitmap.getAddr8(0, 0);
878 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
879 }
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000880 png_uint_32 bitmapHeight = (png_uint_32) decodedBitmap.height();
881 for (png_uint_32 y = 0; y < bitmapHeight; y++) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000882 uint8_t* bmRow = decodedBitmap.getAddr8(0, y);
883 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
884 }
885 }
886 } else {
887 SkScaledBitmapSampler::SrcConfig sc;
888 int srcBytesPerPixel = 4;
889
890 if (colorTable != NULL) {
891 sc = SkScaledBitmapSampler::kIndex;
892 srcBytesPerPixel = 1;
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000893 } else if (SkBitmap::kA8_Config == config) {
894 // A8 is only allowed if the original was GRAY.
895 SkASSERT(PNG_COLOR_TYPE_GRAY == colorType);
896 sc = SkScaledBitmapSampler::kGray;
897 srcBytesPerPixel = 1;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000898 } else if (hasAlpha) {
899 sc = SkScaledBitmapSampler::kRGBA;
900 } else {
901 sc = SkScaledBitmapSampler::kRGBX;
902 }
903
904 /* We have to pass the colortable explicitly, since we may have one
905 even if our decodedBitmap doesn't, due to the request that we
906 upscale png's palette to a direct model
907 */
908 SkAutoLockColors ctLock(colorTable);
scroggo@google.com8d239242013-10-01 17:27:15 +0000909 if (!sampler.begin(&decodedBitmap, sc, *this, ctLock.colors())) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000910 return false;
911 }
912 const int height = decodedBitmap.height();
913
914 if (number_passes > 1) {
915 SkAutoMalloc storage(origWidth * origHeight * srcBytesPerPixel);
916 uint8_t* base = (uint8_t*)storage.get();
917 size_t rb = origWidth * srcBytesPerPixel;
918
919 for (int i = 0; i < number_passes; i++) {
920 png_configure_decoder(png_ptr, &actualTop, i);
921 for (int j = 0; j < rect.fTop - actualTop; j++) {
922 uint8_t* bmRow = (uint8_t*)decodedBitmap.getPixels();
923 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
924 }
925 uint8_t* row = base;
926 for (int32_t y = 0; y < rect.height(); y++) {
927 uint8_t* bmRow = row;
928 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
929 row += rb;
930 }
931 }
932 // now sample it
933 base += sampler.srcY0() * rb;
934 for (int y = 0; y < height; y++) {
935 reallyHasAlpha |= sampler.next(base);
936 base += sampler.srcDY() * rb;
937 }
938 } else {
939 SkAutoMalloc storage(origWidth * srcBytesPerPixel);
940 uint8_t* srcRow = (uint8_t*)storage.get();
941
942 png_configure_decoder(png_ptr, &actualTop, 0);
943 skip_src_rows(png_ptr, srcRow, sampler.srcY0());
944
945 for (int i = 0; i < rect.fTop - actualTop; i++) {
946 uint8_t* bmRow = (uint8_t*)decodedBitmap.getPixels();
947 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
948 }
949 for (int y = 0; y < height; y++) {
950 uint8_t* tmp = srcRow;
951 png_read_rows(png_ptr, &tmp, png_bytepp_NULL, 1);
952 reallyHasAlpha |= sampler.next(srcRow);
953 if (y < height - 1) {
954 skip_src_rows(png_ptr, srcRow, sampler.srcDY() - 1);
955 }
956 }
957 }
958 }
959
960 if (0 != theTranspColor) {
961 reallyHasAlpha |= substituteTranspColor(&decodedBitmap, theTranspColor);
962 }
scroggo@google.com5ee18dd2013-10-21 20:47:31 +0000963 if (reallyHasAlpha && this->getRequireUnpremultipliedColors()) {
964 switch (decodedBitmap.config()) {
965 case SkBitmap::kIndex8_Config:
966 // Fall through.
967 case SkBitmap::kARGB_4444_Config:
968 // We have chosen not to support unpremul for these configs.
969 return false;
970 default: {
971 // Fall through to finish the decode. This config either
972 // supports unpremul or it is irrelevant because it has no
973 // alpha (or only alpha).
974 // These brackets prevent a warning.
975 }
976 }
commit-bot@chromium.org546f70c2013-10-03 17:13:38 +0000977 }
reed@google.com383a6972013-10-21 14:00:07 +0000978 SkAlphaType alphaType = kOpaque_SkAlphaType;
979 if (reallyHasAlpha) {
980 if (this->getRequireUnpremultipliedColors()) {
981 alphaType = kUnpremul_SkAlphaType;
982 } else {
983 alphaType = kPremul_SkAlphaType;
984 }
985 }
986 decodedBitmap.setAlphaType(alphaType);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000987
988 if (swapOnly) {
989 bm->swap(decodedBitmap);
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000990 return true;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000991 }
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000992 return this->cropBitmap(bm, &decodedBitmap, sampleSize, region.x(), region.y(),
993 region.width(), region.height(), 0, rect.y());
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000994}
995#endif
996
reed@android.com8a1c16f2008-12-17 15:59:43 +0000997///////////////////////////////////////////////////////////////////////////////
998
reed@android.com8a1c16f2008-12-17 15:59:43 +0000999#include "SkColorPriv.h"
1000#include "SkUnPreMultiply.h"
1001
1002static void sk_write_fn(png_structp png_ptr, png_bytep data, png_size_t len) {
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +00001003 SkWStream* sk_stream = (SkWStream*)png_get_io_ptr(png_ptr);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001004 if (!sk_stream->write(data, len)) {
1005 png_error(png_ptr, "sk_write_fn Error!");
1006 }
1007}
1008
reed@android.com8a1c16f2008-12-17 15:59:43 +00001009static transform_scanline_proc choose_proc(SkBitmap::Config config,
1010 bool hasAlpha) {
1011 // we don't care about search on alpha if we're kIndex8, since only the
1012 // colortable packing cares about that distinction, not the pixels
1013 if (SkBitmap::kIndex8_Config == config) {
1014 hasAlpha = false; // we store false in the table entries for kIndex8
1015 }
rmistry@google.comd6176b02012-08-23 18:14:13 +00001016
reed@android.com8a1c16f2008-12-17 15:59:43 +00001017 static const struct {
1018 SkBitmap::Config fConfig;
1019 bool fHasAlpha;
1020 transform_scanline_proc fProc;
1021 } gMap[] = {
1022 { SkBitmap::kRGB_565_Config, false, transform_scanline_565 },
1023 { SkBitmap::kARGB_8888_Config, false, transform_scanline_888 },
1024 { SkBitmap::kARGB_8888_Config, true, transform_scanline_8888 },
1025 { SkBitmap::kARGB_4444_Config, false, transform_scanline_444 },
1026 { SkBitmap::kARGB_4444_Config, true, transform_scanline_4444 },
epoger@google.com4ce738b2012-11-16 18:44:18 +00001027 { SkBitmap::kIndex8_Config, false, transform_scanline_memcpy },
reed@android.com8a1c16f2008-12-17 15:59:43 +00001028 };
1029
1030 for (int i = SK_ARRAY_COUNT(gMap) - 1; i >= 0; --i) {
1031 if (gMap[i].fConfig == config && gMap[i].fHasAlpha == hasAlpha) {
1032 return gMap[i].fProc;
1033 }
1034 }
1035 sk_throw();
1036 return NULL;
1037}
1038
1039// return the minimum legal bitdepth (by png standards) for this many colortable
1040// entries. SkBitmap always stores in 8bits per pixel, but for colorcount <= 16,
1041// we can use fewer bits per in png
1042static int computeBitDepth(int colorCount) {
1043#if 0
1044 int bits = SkNextLog2(colorCount);
1045 SkASSERT(bits >= 1 && bits <= 8);
1046 // now we need bits itself to be a power of 2 (e.g. 1, 2, 4, 8)
1047 return SkNextPow2(bits);
1048#else
1049 // for the moment, we don't know how to pack bitdepth < 8
1050 return 8;
1051#endif
1052}
1053
1054/* Pack palette[] with the corresponding colors, and if hasAlpha is true, also
1055 pack trans[] and return the number of trans[] entries written. If hasAlpha
1056 is false, the return value will always be 0.
rmistry@google.comd6176b02012-08-23 18:14:13 +00001057
reed@android.com8a1c16f2008-12-17 15:59:43 +00001058 Note: this routine takes care of unpremultiplying the RGB values when we
1059 have alpha in the colortable, since png doesn't support premul colors
1060*/
reed@android.com6f252972009-01-14 16:46:16 +00001061static inline int pack_palette(SkColorTable* ctable,
reed@android.comb50a60c2009-01-14 17:51:08 +00001062 png_color* SK_RESTRICT palette,
1063 png_byte* SK_RESTRICT trans, bool hasAlpha) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001064 SkAutoLockColors alc(ctable);
1065 const SkPMColor* SK_RESTRICT colors = alc.colors();
1066 const int ctCount = ctable->count();
1067 int i, num_trans = 0;
1068
1069 if (hasAlpha) {
1070 /* first see if we have some number of fully opaque at the end of the
1071 ctable. PNG allows num_trans < num_palette, but all of the trans
1072 entries must come first in the palette. If I was smarter, I'd
1073 reorder the indices and ctable so that all non-opaque colors came
1074 first in the palette. But, since that would slow down the encode,
1075 I'm leaving the indices and ctable order as is, and just looking
1076 at the tail of the ctable for opaqueness.
1077 */
1078 num_trans = ctCount;
1079 for (i = ctCount - 1; i >= 0; --i) {
1080 if (SkGetPackedA32(colors[i]) != 0xFF) {
1081 break;
1082 }
1083 num_trans -= 1;
1084 }
rmistry@google.comd6176b02012-08-23 18:14:13 +00001085
reed@android.com8a1c16f2008-12-17 15:59:43 +00001086 const SkUnPreMultiply::Scale* SK_RESTRICT table =
1087 SkUnPreMultiply::GetScaleTable();
1088
1089 for (i = 0; i < num_trans; i++) {
1090 const SkPMColor c = *colors++;
1091 const unsigned a = SkGetPackedA32(c);
1092 const SkUnPreMultiply::Scale s = table[a];
1093 trans[i] = a;
1094 palette[i].red = SkUnPreMultiply::ApplyScale(s, SkGetPackedR32(c));
1095 palette[i].green = SkUnPreMultiply::ApplyScale(s,SkGetPackedG32(c));
1096 palette[i].blue = SkUnPreMultiply::ApplyScale(s, SkGetPackedB32(c));
rmistry@google.comd6176b02012-08-23 18:14:13 +00001097 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001098 // now fall out of this if-block to use common code for the trailing
1099 // opaque entries
1100 }
rmistry@google.comd6176b02012-08-23 18:14:13 +00001101
reed@android.com8a1c16f2008-12-17 15:59:43 +00001102 // these (remaining) entries are opaque
1103 for (i = num_trans; i < ctCount; i++) {
1104 SkPMColor c = *colors++;
1105 palette[i].red = SkGetPackedR32(c);
1106 palette[i].green = SkGetPackedG32(c);
1107 palette[i].blue = SkGetPackedB32(c);
1108 }
1109 return num_trans;
1110}
1111
1112class SkPNGImageEncoder : public SkImageEncoder {
1113protected:
commit-bot@chromium.orga936e372013-03-14 14:42:18 +00001114 virtual bool onEncode(SkWStream* stream, const SkBitmap& bm, int quality) SK_OVERRIDE;
tomhudson@google.com5c210c72011-07-28 21:06:40 +00001115private:
1116 bool doEncode(SkWStream* stream, const SkBitmap& bm,
1117 const bool& hasAlpha, int colorType,
1118 int bitDepth, SkBitmap::Config config,
1119 png_color_8& sig_bit);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +00001120
1121 typedef SkImageEncoder INHERITED;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001122};
1123
1124bool SkPNGImageEncoder::onEncode(SkWStream* stream, const SkBitmap& bitmap,
1125 int /*quality*/) {
reed@google.com44699382013-10-31 17:28:30 +00001126 SkBitmap::Config config = bitmap.config();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001127
1128 const bool hasAlpha = !bitmap.isOpaque();
1129 int colorType = PNG_COLOR_MASK_COLOR;
1130 int bitDepth = 8; // default for color
1131 png_color_8 sig_bit;
1132
1133 switch (config) {
1134 case SkBitmap::kIndex8_Config:
1135 colorType |= PNG_COLOR_MASK_PALETTE;
1136 // fall through to the ARGB_8888 case
1137 case SkBitmap::kARGB_8888_Config:
1138 sig_bit.red = 8;
1139 sig_bit.green = 8;
1140 sig_bit.blue = 8;
1141 sig_bit.alpha = 8;
1142 break;
1143 case SkBitmap::kARGB_4444_Config:
1144 sig_bit.red = 4;
1145 sig_bit.green = 4;
1146 sig_bit.blue = 4;
1147 sig_bit.alpha = 4;
1148 break;
1149 case SkBitmap::kRGB_565_Config:
1150 sig_bit.red = 5;
1151 sig_bit.green = 6;
1152 sig_bit.blue = 5;
1153 sig_bit.alpha = 0;
1154 break;
1155 default:
1156 return false;
1157 }
rmistry@google.comd6176b02012-08-23 18:14:13 +00001158
reed@android.com8a1c16f2008-12-17 15:59:43 +00001159 if (hasAlpha) {
1160 // don't specify alpha if we're a palette, even if our ctable has alpha
1161 if (!(colorType & PNG_COLOR_MASK_PALETTE)) {
1162 colorType |= PNG_COLOR_MASK_ALPHA;
1163 }
1164 } else {
1165 sig_bit.alpha = 0;
1166 }
rmistry@google.comd6176b02012-08-23 18:14:13 +00001167
reed@android.com8a1c16f2008-12-17 15:59:43 +00001168 SkAutoLockPixels alp(bitmap);
1169 // readyToDraw checks for pixels (and colortable if that is required)
1170 if (!bitmap.readyToDraw()) {
1171 return false;
1172 }
1173
1174 // we must do this after we have locked the pixels
1175 SkColorTable* ctable = bitmap.getColorTable();
1176 if (NULL != ctable) {
1177 if (ctable->count() == 0) {
1178 return false;
1179 }
1180 // check if we can store in fewer than 8 bits
1181 bitDepth = computeBitDepth(ctable->count());
1182 }
1183
tomhudson@google.com5c210c72011-07-28 21:06:40 +00001184 return doEncode(stream, bitmap, hasAlpha, colorType,
1185 bitDepth, config, sig_bit);
1186}
1187
1188bool SkPNGImageEncoder::doEncode(SkWStream* stream, const SkBitmap& bitmap,
1189 const bool& hasAlpha, int colorType,
1190 int bitDepth, SkBitmap::Config config,
1191 png_color_8& sig_bit) {
1192
reed@android.com8a1c16f2008-12-17 15:59:43 +00001193 png_structp png_ptr;
1194 png_infop info_ptr;
1195
1196 png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, sk_error_fn,
1197 NULL);
1198 if (NULL == png_ptr) {
1199 return false;
1200 }
1201
1202 info_ptr = png_create_info_struct(png_ptr);
1203 if (NULL == info_ptr) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +00001204 png_destroy_write_struct(&png_ptr, png_infopp_NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001205 return false;
1206 }
1207
1208 /* Set error handling. REQUIRED if you aren't supplying your own
1209 * error handling functions in the png_create_write_struct() call.
1210 */
1211 if (setjmp(png_jmpbuf(png_ptr))) {
1212 png_destroy_write_struct(&png_ptr, &info_ptr);
1213 return false;
1214 }
1215
commit-bot@chromium.orga936e372013-03-14 14:42:18 +00001216 png_set_write_fn(png_ptr, (void*)stream, sk_write_fn, png_flush_ptr_NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001217
1218 /* Set the image information here. Width and height are up to 2^31,
1219 * bit_depth is one of 1, 2, 4, 8, or 16, but valid values also depend on
1220 * the color_type selected. color_type is one of PNG_COLOR_TYPE_GRAY,
1221 * PNG_COLOR_TYPE_GRAY_ALPHA, PNG_COLOR_TYPE_PALETTE, PNG_COLOR_TYPE_RGB,
1222 * or PNG_COLOR_TYPE_RGB_ALPHA. interlace is either PNG_INTERLACE_NONE or
1223 * PNG_INTERLACE_ADAM7, and the compression_type and filter_type MUST
1224 * currently be PNG_COMPRESSION_TYPE_BASE and PNG_FILTER_TYPE_BASE. REQUIRED
1225 */
1226
1227 png_set_IHDR(png_ptr, info_ptr, bitmap.width(), bitmap.height(),
1228 bitDepth, colorType,
1229 PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE,
1230 PNG_FILTER_TYPE_BASE);
1231
reed@android.com61898772009-07-07 19:38:01 +00001232 // set our colortable/trans arrays if needed
1233 png_color paletteColors[256];
1234 png_byte trans[256];
1235 if (SkBitmap::kIndex8_Config == config) {
1236 SkColorTable* ct = bitmap.getColorTable();
1237 int numTrans = pack_palette(ct, paletteColors, trans, hasAlpha);
1238 png_set_PLTE(png_ptr, info_ptr, paletteColors, ct->count());
1239 if (numTrans > 0) {
1240 png_set_tRNS(png_ptr, info_ptr, trans, numTrans, NULL);
1241 }
1242 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001243
1244 png_set_sBIT(png_ptr, info_ptr, &sig_bit);
1245 png_write_info(png_ptr, info_ptr);
1246
1247 const char* srcImage = (const char*)bitmap.getPixels();
1248 SkAutoSMalloc<1024> rowStorage(bitmap.width() << 2);
1249 char* storage = (char*)rowStorage.get();
1250 transform_scanline_proc proc = choose_proc(config, hasAlpha);
1251
1252 for (int y = 0; y < bitmap.height(); y++) {
1253 png_bytep row_ptr = (png_bytep)storage;
1254 proc(srcImage, bitmap.width(), storage);
1255 png_write_rows(png_ptr, &row_ptr, 1);
1256 srcImage += bitmap.rowBytes();
1257 }
1258
1259 png_write_end(png_ptr, info_ptr);
1260
1261 /* clean up after the write, and free any memory allocated */
1262 png_destroy_write_struct(&png_ptr, &info_ptr);
1263 return true;
1264}
1265
reed@android.com00bf85a2009-01-22 13:04:56 +00001266///////////////////////////////////////////////////////////////////////////////
robertphillips@google.comec51cb82012-03-23 18:13:47 +00001267DEFINE_DECODER_CREATOR(PNGImageDecoder);
1268DEFINE_ENCODER_CREATOR(PNGImageEncoder);
1269///////////////////////////////////////////////////////////////////////////////
reed@android.com00bf85a2009-01-22 13:04:56 +00001270
scroggo@google.comb5571b32013-09-25 21:34:24 +00001271static bool is_png(SkStreamRewindable* stream) {
reed@android.com00bf85a2009-01-22 13:04:56 +00001272 char buf[PNG_BYTES_TO_CHECK];
1273 if (stream->read(buf, PNG_BYTES_TO_CHECK) == PNG_BYTES_TO_CHECK &&
1274 !png_sig_cmp((png_bytep) buf, (png_size_t)0, PNG_BYTES_TO_CHECK)) {
scroggo@google.com39edf4c2013-04-25 17:33:51 +00001275 return true;
1276 }
1277 return false;
1278}
1279
scroggo@google.comb5571b32013-09-25 21:34:24 +00001280SkImageDecoder* sk_libpng_dfactory(SkStreamRewindable* stream) {
scroggo@google.com39edf4c2013-04-25 17:33:51 +00001281 if (is_png(stream)) {
reed@android.com00bf85a2009-01-22 13:04:56 +00001282 return SkNEW(SkPNGImageDecoder);
1283 }
1284 return NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001285}
1286
scroggo@google.comb5571b32013-09-25 21:34:24 +00001287static SkImageDecoder::Format get_format_png(SkStreamRewindable* stream) {
scroggo@google.com39edf4c2013-04-25 17:33:51 +00001288 if (is_png(stream)) {
1289 return SkImageDecoder::kPNG_Format;
1290 }
1291 return SkImageDecoder::kUnknown_Format;
1292}
1293
reed@android.comdfee5792010-04-15 14:24:50 +00001294SkImageEncoder* sk_libpng_efactory(SkImageEncoder::Type t) {
reed@android.com00bf85a2009-01-22 13:04:56 +00001295 return (SkImageEncoder::kPNG_Type == t) ? SkNEW(SkPNGImageEncoder) : NULL;
1296}
1297
mtklein@google.combd6343b2013-09-04 17:20:18 +00001298static SkImageDecoder_DecodeReg gDReg(sk_libpng_dfactory);
1299static SkImageDecoder_FormatReg gFormatReg(get_format_png);
1300static SkImageEncoder_EncodeReg gEReg(sk_libpng_efactory);