blob: 951083b0f467d4ad9e31889447f05009f6090916 [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"
16#include "SkScaledBitmapSampler.h"
17#include "SkStream.h"
18#include "SkTemplates.h"
19#include "SkUtils.h"
epoger@google.com4ce738b2012-11-16 18:44:18 +000020#include "transform_scanline.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000021
halcanary@google.comfed30372013-10-04 12:46:45 +000022#if defined(SK_DEBUG)
23#include "SkRTConf.h" // SK_CONF_DECLARE
24#endif // defined(SK_DEBUG)
25
reed@android.com8a1c16f2008-12-17 15:59:43 +000026extern "C" {
27#include "png.h"
28}
29
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +000030/* These were dropped in libpng >= 1.4 */
31#ifndef png_infopp_NULL
32#define png_infopp_NULL NULL
33#endif
34
35#ifndef png_bytepp_NULL
36#define png_bytepp_NULL NULL
37#endif
38
39#ifndef int_p_NULL
40#define int_p_NULL NULL
41#endif
42
43#ifndef png_flush_ptr_NULL
44#define png_flush_ptr_NULL NULL
45#endif
46
halcanary@google.comfed30372013-10-04 12:46:45 +000047#if defined(SK_DEBUG)
48SK_CONF_DECLARE(bool, c_suppressPNGImageDecoderWarnings,
49 "images.png.suppressDecoderWarnings", false,
50 "Suppress most PNG warnings when calling image decode functions.");
51#endif // defined(SK_DEBUG)
52
53
commit-bot@chromium.orga936e372013-03-14 14:42:18 +000054class SkPNGImageIndex {
55public:
scroggo@google.comb5571b32013-09-25 21:34:24 +000056 SkPNGImageIndex(SkStreamRewindable* stream, png_structp png_ptr, png_infop info_ptr)
scroggo@google.comc70a3aa2013-07-18 20:03:15 +000057 : fStream(stream)
58 , fPng_ptr(png_ptr)
59 , fInfo_ptr(info_ptr)
60 , fConfig(SkBitmap::kNo_Config) {
61 SkASSERT(stream != NULL);
62 stream->ref();
commit-bot@chromium.orga936e372013-03-14 14:42:18 +000063 }
64 ~SkPNGImageIndex() {
scroggo@google.comc70a3aa2013-07-18 20:03:15 +000065 if (NULL != fPng_ptr) {
66 png_destroy_read_struct(&fPng_ptr, &fInfo_ptr, png_infopp_NULL);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +000067 }
68 }
69
scroggo@google.comb5571b32013-09-25 21:34:24 +000070 SkAutoTUnref<SkStreamRewindable> fStream;
71 png_structp fPng_ptr;
72 png_infop fInfo_ptr;
73 SkBitmap::Config fConfig;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +000074};
75
reed@android.com8a1c16f2008-12-17 15:59:43 +000076class SkPNGImageDecoder : public SkImageDecoder {
77public:
commit-bot@chromium.orga936e372013-03-14 14:42:18 +000078 SkPNGImageDecoder() {
79 fImageIndex = NULL;
80 }
81 virtual Format getFormat() const SK_OVERRIDE {
reed@android.com8a1c16f2008-12-17 15:59:43 +000082 return kPNG_Format;
83 }
scroggo@google.com39edf4c2013-04-25 17:33:51 +000084
commit-bot@chromium.orga936e372013-03-14 14:42:18 +000085 virtual ~SkPNGImageDecoder() {
86 SkDELETE(fImageIndex);
87 }
rmistry@google.comd6176b02012-08-23 18:14:13 +000088
reed@android.com8a1c16f2008-12-17 15:59:43 +000089protected:
commit-bot@chromium.orga936e372013-03-14 14:42:18 +000090#ifdef SK_BUILD_FOR_ANDROID
scroggo@google.comb5571b32013-09-25 21:34:24 +000091 virtual bool onBuildTileIndex(SkStreamRewindable *stream, int *width, int *height) SK_OVERRIDE;
scroggo@google.com7e6fcee2013-05-03 20:14:28 +000092 virtual bool onDecodeSubset(SkBitmap* bitmap, const SkIRect& region) SK_OVERRIDE;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +000093#endif
94 virtual bool onDecode(SkStream* stream, SkBitmap* bm, Mode) SK_OVERRIDE;
95
96private:
97 SkPNGImageIndex* fImageIndex;
98
99 bool onDecodeInit(SkStream* stream, png_structp *png_ptrp, png_infop *info_ptrp);
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000100 bool decodePalette(png_structp png_ptr, png_infop info_ptr,
101 bool * SK_RESTRICT hasAlphap, bool *reallyHasAlphap,
102 SkColorTable **colorTablep);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000103 bool getBitmapConfig(png_structp png_ptr, png_infop info_ptr,
104 SkBitmap::Config *config, bool *hasAlpha,
scroggo@google.com8d239242013-10-01 17:27:15 +0000105 SkPMColor *theTranspColor);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000106
107 typedef SkImageDecoder INHERITED;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000108};
109
110#ifndef png_jmpbuf
111# define png_jmpbuf(png_ptr) ((png_ptr)->jmpbuf)
112#endif
113
114#define PNG_BYTES_TO_CHECK 4
115
116/* Automatically clean up after throwing an exception */
117struct PNGAutoClean {
118 PNGAutoClean(png_structp p, png_infop i): png_ptr(p), info_ptr(i) {}
119 ~PNGAutoClean() {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000120 png_destroy_read_struct(&png_ptr, &info_ptr, png_infopp_NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000121 }
122private:
123 png_structp png_ptr;
124 png_infop info_ptr;
125};
126
reed@android.com8a1c16f2008-12-17 15:59:43 +0000127static void sk_read_fn(png_structp png_ptr, png_bytep data, png_size_t length) {
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000128 SkStream* sk_stream = (SkStream*) png_get_io_ptr(png_ptr);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000129 size_t bytes = sk_stream->read(data, length);
130 if (bytes != length) {
131 png_error(png_ptr, "Read Error!");
132 }
133}
134
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000135#ifdef SK_BUILD_FOR_ANDROID
136static void sk_seek_fn(png_structp png_ptr, png_uint_32 offset) {
scroggo@google.comb5571b32013-09-25 21:34:24 +0000137 SkStreamRewindable* sk_stream = (SkStreamRewindable*) png_get_io_ptr(png_ptr);
scroggo@google.com4d213ab2013-08-28 13:08:54 +0000138 if (!sk_stream->rewind()) {
139 png_error(png_ptr, "Failed to rewind stream!");
140 }
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000141 (void)sk_stream->skip(offset);
142}
143#endif
144
reed@android.com8a1c16f2008-12-17 15:59:43 +0000145static int sk_read_user_chunk(png_structp png_ptr, png_unknown_chunkp chunk) {
146 SkImageDecoder::Peeker* peeker =
147 (SkImageDecoder::Peeker*)png_get_user_chunk_ptr(png_ptr);
148 // peek() returning true means continue decoding
149 return peeker->peek((const char*)chunk->name, chunk->data, chunk->size) ?
150 1 : -1;
151}
152
153static void sk_error_fn(png_structp png_ptr, png_const_charp msg) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000154 SkDEBUGF(("------ png error %s\n", msg));
reed@android.com8a1c16f2008-12-17 15:59:43 +0000155 longjmp(png_jmpbuf(png_ptr), 1);
156}
157
158static void skip_src_rows(png_structp png_ptr, uint8_t storage[], int count) {
159 for (int i = 0; i < count; i++) {
160 uint8_t* tmp = storage;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000161 png_read_rows(png_ptr, &tmp, png_bytepp_NULL, 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000162 }
163}
164
165static bool pos_le(int value, int max) {
166 return value > 0 && value <= max;
167}
168
169static bool substituteTranspColor(SkBitmap* bm, SkPMColor match) {
170 SkASSERT(bm->config() == SkBitmap::kARGB_8888_Config);
rmistry@google.comd6176b02012-08-23 18:14:13 +0000171
reed@android.com8a1c16f2008-12-17 15:59:43 +0000172 bool reallyHasAlpha = false;
173
174 for (int y = bm->height() - 1; y >= 0; --y) {
175 SkPMColor* p = bm->getAddr32(0, y);
176 for (int x = bm->width() - 1; x >= 0; --x) {
177 if (match == *p) {
178 *p = 0;
179 reallyHasAlpha = true;
180 }
181 p += 1;
182 }
183 }
184 return reallyHasAlpha;
185}
186
reed@android.com3f1f06a2010-03-03 21:04:12 +0000187static bool canUpscalePaletteToConfig(SkBitmap::Config dstConfig,
reed@android.comb6137c32009-07-29 20:56:52 +0000188 bool srcHasAlpha) {
reed@android.com3f1f06a2010-03-03 21:04:12 +0000189 switch (dstConfig) {
reed@android.comb6137c32009-07-29 20:56:52 +0000190 case SkBitmap::kARGB_8888_Config:
191 case SkBitmap::kARGB_4444_Config:
192 return true;
193 case SkBitmap::kRGB_565_Config:
194 // only return true if the src is opaque (since 565 is opaque)
195 return !srcHasAlpha;
196 default:
197 return false;
198 }
199}
200
201// call only if color_type is PALETTE. Returns true if the ctable has alpha
202static bool hasTransparencyInPalette(png_structp png_ptr, png_infop info_ptr) {
203 png_bytep trans;
204 int num_trans;
205
206 if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
207 png_get_tRNS(png_ptr, info_ptr, &trans, &num_trans, NULL);
208 return num_trans > 0;
209 }
210 return false;
reed@android.com11344262009-07-08 20:09:23 +0000211}
212
halcanary@google.comfed30372013-10-04 12:46:45 +0000213void do_nothing_warning_fn(png_structp, png_const_charp) {
214 /* do nothing */
215}
216
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000217bool SkPNGImageDecoder::onDecodeInit(SkStream* sk_stream, png_structp *png_ptrp,
218 png_infop *info_ptrp) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000219 /* Create and initialize the png_struct with the desired error handler
220 * functions. If you want to use the default stderr and longjump method,
221 * you can supply NULL for the last three parameters. We also supply the
222 * the compiler header file version, so that we know if the application
223 * was compiled with a compatible version of the library. */
halcanary@google.comfed30372013-10-04 12:46:45 +0000224
225#if defined(SK_DEBUG)
226 png_error_ptr user_warning_fn =
227 (c_suppressPNGImageDecoderWarnings) ? (&do_nothing_warning_fn) : NULL;
228 /* NULL means to leave as default library behavior. */
229 /* c_suppressPNGImageDecoderWarnings defaults to false. */
230 /* To suppress warnings with a SK_DEBUG binary, set the
231 * environment variable "skia_images_png_suppressDecoderWarnings"
232 * to "true". Inside a program that links to skia:
233 * SK_CONF_SET("images.png.suppressDecoderWarnings", true); */
234#else // Always suppress in release mode
235 png_error_ptr user_warning_fn = &do_nothing_warning_fn;
236#endif // defined(SK_DEBUG)
237
reed@android.com8a1c16f2008-12-17 15:59:43 +0000238 png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
halcanary@google.comfed30372013-10-04 12:46:45 +0000239 NULL, sk_error_fn, user_warning_fn);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000240 // png_voidp user_error_ptr, user_error_fn, user_warning_fn);
241 if (png_ptr == NULL) {
242 return false;
243 }
halcanary@google.comfed30372013-10-04 12:46:45 +0000244
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000245 *png_ptrp = png_ptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000246
247 /* Allocate/initialize the memory for image information. */
248 png_infop info_ptr = png_create_info_struct(png_ptr);
249 if (info_ptr == NULL) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000250 png_destroy_read_struct(&png_ptr, png_infopp_NULL, png_infopp_NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000251 return false;
252 }
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000253 *info_ptrp = info_ptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000254
255 /* Set error handling if you are using the setjmp/longjmp method (this is
256 * the normal method of doing things with libpng). REQUIRED unless you
257 * set up your own error handlers in the png_create_read_struct() earlier.
258 */
259 if (setjmp(png_jmpbuf(png_ptr))) {
260 return false;
261 }
262
263 /* If you are using replacement read functions, instead of calling
264 * png_init_io() here you would call:
265 */
266 png_set_read_fn(png_ptr, (void *)sk_stream, sk_read_fn);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000267#ifdef SK_BUILD_FOR_ANDROID
268 png_set_seek_fn(png_ptr, sk_seek_fn);
269#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000270 /* where user_io_ptr is a structure you want available to the callbacks */
271 /* If we have already read some of the signature */
272// png_set_sig_bytes(png_ptr, 0 /* sig_read */ );
273
274 // hookup our peeker so we can see any user-chunks the caller may be interested in
275 png_set_keep_unknown_chunks(png_ptr, PNG_HANDLE_CHUNK_ALWAYS, (png_byte*)"", 0);
276 if (this->getPeeker()) {
277 png_set_read_user_chunk_fn(png_ptr, (png_voidp)this->getPeeker(), sk_read_user_chunk);
278 }
279
280 /* The call to png_read_info() gives us all of the information from the
281 * PNG file before the first IDAT (image data chunk). */
282 png_read_info(png_ptr, info_ptr);
283 png_uint_32 origWidth, origHeight;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000284 int bitDepth, colorType;
285 png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
286 &colorType, int_p_NULL, int_p_NULL, int_p_NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000287
288 /* tell libpng to strip 16 bit/color files down to 8 bits/color */
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000289 if (bitDepth == 16) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000290 png_set_strip_16(png_ptr);
291 }
292 /* Extract multiple pixels with bit depths of 1, 2, and 4 from a single
293 * byte into separate bytes (useful for paletted and grayscale images). */
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000294 if (bitDepth < 8) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000295 png_set_packing(png_ptr);
296 }
297 /* Expand grayscale images to the full 8 bits from 1, 2, or 4 bits/pixel */
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000298 if (colorType == PNG_COLOR_TYPE_GRAY && bitDepth < 8) {
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000299 png_set_expand_gray_1_2_4_to_8(png_ptr);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000300 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000301
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000302 return true;
303}
304
305bool SkPNGImageDecoder::onDecode(SkStream* sk_stream, SkBitmap* decodedBitmap,
306 Mode mode) {
307 png_structp png_ptr;
308 png_infop info_ptr;
309
310 if (!onDecodeInit(sk_stream, &png_ptr, &info_ptr)) {
311 return false;
312 }
313
314 if (setjmp(png_jmpbuf(png_ptr))) {
315 return false;
316 }
317
318 PNGAutoClean autoClean(png_ptr, info_ptr);
319
320 png_uint_32 origWidth, origHeight;
321 int bitDepth, colorType, interlaceType;
322 png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
323 &colorType, &interlaceType, int_p_NULL, int_p_NULL);
rmistry@google.comd6176b02012-08-23 18:14:13 +0000324
reed@android.com8a1c16f2008-12-17 15:59:43 +0000325 SkBitmap::Config config;
326 bool hasAlpha = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000327 SkPMColor theTranspColor = 0; // 0 tells us not to try to match
rmistry@google.comd6176b02012-08-23 18:14:13 +0000328
scroggo@google.com8d239242013-10-01 17:27:15 +0000329 if (!this->getBitmapConfig(png_ptr, info_ptr, &config, &hasAlpha, &theTranspColor)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000330 return false;
331 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000332
reed@android.com8a1c16f2008-12-17 15:59:43 +0000333 const int sampleSize = this->getSampleSize();
334 SkScaledBitmapSampler sampler(origWidth, origHeight, sampleSize);
scroggo@google.combc69ce92013-07-09 15:45:14 +0000335 decodedBitmap->setConfig(config, sampler.scaledWidth(), sampler.scaledHeight());
reed@android.com8a1c16f2008-12-17 15:59:43 +0000336
reed@android.com8a1c16f2008-12-17 15:59:43 +0000337 if (SkImageDecoder::kDecodeBounds_Mode == mode) {
338 return true;
339 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000340
reed@android.com8a1c16f2008-12-17 15:59:43 +0000341 // from here down we are concerned with colortables and pixels
342
343 // we track if we actually see a non-opaque pixels, since sometimes a PNG sets its colortype
344 // to |= PNG_COLOR_MASK_ALPHA, but all of its pixels are in fact opaque. We care, since we
345 // draw lots faster if we can flag the bitmap has being opaque
346 bool reallyHasAlpha = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000347 SkColorTable* colorTable = NULL;
348
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000349 if (colorType == PNG_COLOR_TYPE_PALETTE) {
350 decodePalette(png_ptr, info_ptr, &hasAlpha, &reallyHasAlpha, &colorTable);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000351 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000352
reed@android.com8a1c16f2008-12-17 15:59:43 +0000353 SkAutoUnref aur(colorTable);
354
scroggo@google.combc69ce92013-07-09 15:45:14 +0000355 if (!this->allocPixelRef(decodedBitmap,
356 SkBitmap::kIndex8_Config == config ? colorTable : NULL)) {
357 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000358 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000359
reed@android.com8a1c16f2008-12-17 15:59:43 +0000360 SkAutoLockPixels alp(*decodedBitmap);
361
reed@android.com8a1c16f2008-12-17 15:59:43 +0000362 /* Turn on interlace handling. REQUIRED if you are not using
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000363 * png_read_image(). To see how to handle interlacing passes,
364 * see the png_read_row() method below:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000365 */
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000366 const int number_passes = (interlaceType != PNG_INTERLACE_NONE) ?
367 png_set_interlace_handling(png_ptr) : 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000368
369 /* Optional call to gamma correct and add the background to the palette
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000370 * and update info structure. REQUIRED if you are expecting libpng to
371 * update the palette for you (ie you selected such a transform above).
reed@android.com8a1c16f2008-12-17 15:59:43 +0000372 */
373 png_read_update_info(png_ptr, info_ptr);
374
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000375 if ((SkBitmap::kA8_Config == config || SkBitmap::kIndex8_Config == config)
376 && 1 == sampleSize) {
377 // A8 is only allowed if the original was GRAY.
378 SkASSERT(config != SkBitmap::kA8_Config
379 || PNG_COLOR_TYPE_GRAY == colorType);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000380 for (int i = 0; i < number_passes; i++) {
381 for (png_uint_32 y = 0; y < origHeight; y++) {
382 uint8_t* bmRow = decodedBitmap->getAddr8(0, y);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000383 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000384 }
385 }
386 } else {
387 SkScaledBitmapSampler::SrcConfig sc;
388 int srcBytesPerPixel = 4;
rmistry@google.comd6176b02012-08-23 18:14:13 +0000389
reed@android.com11344262009-07-08 20:09:23 +0000390 if (colorTable != NULL) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000391 sc = SkScaledBitmapSampler::kIndex;
392 srcBytesPerPixel = 1;
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000393 } else if (SkBitmap::kA8_Config == config) {
394 // A8 is only allowed if the original was GRAY.
395 SkASSERT(PNG_COLOR_TYPE_GRAY == colorType);
396 sc = SkScaledBitmapSampler::kGray;
397 srcBytesPerPixel = 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000398 } else if (hasAlpha) {
399 sc = SkScaledBitmapSampler::kRGBA;
400 } else {
401 sc = SkScaledBitmapSampler::kRGBX;
402 }
reed@android.com11344262009-07-08 20:09:23 +0000403
404 /* We have to pass the colortable explicitly, since we may have one
405 even if our decodedBitmap doesn't, due to the request that we
406 upscale png's palette to a direct model
407 */
408 SkAutoLockColors ctLock(colorTable);
scroggo@google.com8d239242013-10-01 17:27:15 +0000409 if (!sampler.begin(decodedBitmap, sc, *this, ctLock.colors())) {
reed@android.com862e91b2009-04-28 15:27:07 +0000410 return false;
411 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000412 const int height = decodedBitmap->height();
413
reed@android.com862e91b2009-04-28 15:27:07 +0000414 if (number_passes > 1) {
415 SkAutoMalloc storage(origWidth * origHeight * srcBytesPerPixel);
416 uint8_t* base = (uint8_t*)storage.get();
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000417 size_t rowBytes = origWidth * srcBytesPerPixel;
reed@android.coma8a8b8b2009-05-04 15:00:11 +0000418
reed@android.com862e91b2009-04-28 15:27:07 +0000419 for (int i = 0; i < number_passes; i++) {
420 uint8_t* row = base;
421 for (png_uint_32 y = 0; y < origHeight; y++) {
422 uint8_t* bmRow = row;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000423 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
424 row += rowBytes;
reed@android.com862e91b2009-04-28 15:27:07 +0000425 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000426 }
reed@android.com862e91b2009-04-28 15:27:07 +0000427 // now sample it
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000428 base += sampler.srcY0() * rowBytes;
reed@android.com862e91b2009-04-28 15:27:07 +0000429 for (int y = 0; y < height; y++) {
430 reallyHasAlpha |= sampler.next(base);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000431 base += sampler.srcDY() * rowBytes;
reed@android.com862e91b2009-04-28 15:27:07 +0000432 }
433 } else {
434 SkAutoMalloc storage(origWidth * srcBytesPerPixel);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000435 uint8_t* srcRow = (uint8_t*)storage.get();
436 skip_src_rows(png_ptr, srcRow, sampler.srcY0());
437
438 for (int y = 0; y < height; y++) {
439 uint8_t* tmp = srcRow;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000440 png_read_rows(png_ptr, &tmp, png_bytepp_NULL, 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000441 reallyHasAlpha |= sampler.next(srcRow);
442 if (y < height - 1) {
443 skip_src_rows(png_ptr, srcRow, sampler.srcDY() - 1);
444 }
445 }
reed@android.com862e91b2009-04-28 15:27:07 +0000446
reed@android.com8a1c16f2008-12-17 15:59:43 +0000447 // skip the rest of the rows (if any)
448 png_uint_32 read = (height - 1) * sampler.srcDY() +
449 sampler.srcY0() + 1;
450 SkASSERT(read <= origHeight);
451 skip_src_rows(png_ptr, srcRow, origHeight - read);
452 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000453 }
454
455 /* read rest of file, and get additional chunks in info_ptr - REQUIRED */
456 png_read_end(png_ptr, info_ptr);
457
458 if (0 != theTranspColor) {
459 reallyHasAlpha |= substituteTranspColor(decodedBitmap, theTranspColor);
460 }
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000461 if (reallyHasAlpha && this->getRequireUnpremultipliedColors() &&
462 SkBitmap::kARGB_8888_Config != decodedBitmap->config()) {
463 // If the caller wants an unpremultiplied bitmap, and we let them get
464 // away with a config other than 8888, and it has alpha after all,
465 // return false, since the result will have premultiplied colors.
466 return false;
467 }
commit-bot@chromium.org546f70c2013-10-03 17:13:38 +0000468 if (SkBitmap::kA8_Config == decodedBitmap->config()) {
469 reallyHasAlpha = true;
470 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000471 decodedBitmap->setIsOpaque(!reallyHasAlpha);
472 return true;
473}
474
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000475
476
477bool SkPNGImageDecoder::getBitmapConfig(png_structp png_ptr, png_infop info_ptr,
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000478 SkBitmap::Config* SK_RESTRICT configp,
479 bool* SK_RESTRICT hasAlphap,
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000480 SkPMColor* SK_RESTRICT theTranspColorp) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000481 png_uint_32 origWidth, origHeight;
482 int bitDepth, colorType;
483 png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
484 &colorType, int_p_NULL, int_p_NULL, int_p_NULL);
485
486 // check for sBIT chunk data, in case we should disable dithering because
487 // our data is not truely 8bits per component
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000488 png_color_8p sig_bit;
scroggo@google.com8d239242013-10-01 17:27:15 +0000489 if (this->getDitherImage() && png_get_sBIT(png_ptr, info_ptr, &sig_bit)) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000490#if 0
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000491 SkDebugf("----- sBIT %d %d %d %d\n", sig_bit->red, sig_bit->green,
492 sig_bit->blue, sig_bit->alpha);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000493#endif
494 // 0 seems to indicate no information available
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000495 if (pos_le(sig_bit->red, SK_R16_BITS) &&
496 pos_le(sig_bit->green, SK_G16_BITS) &&
497 pos_le(sig_bit->blue, SK_B16_BITS)) {
scroggo@google.com8d239242013-10-01 17:27:15 +0000498 this->setDitherImage(false);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000499 }
500 }
501
502 if (colorType == PNG_COLOR_TYPE_PALETTE) {
503 bool paletteHasAlpha = hasTransparencyInPalette(png_ptr, info_ptr);
504 *configp = this->getPrefConfig(kIndex_SrcDepth, paletteHasAlpha);
505 // now see if we can upscale to their requested config
506 if (!canUpscalePaletteToConfig(*configp, paletteHasAlpha)) {
507 *configp = SkBitmap::kIndex8_Config;
508 }
509 } else {
510 png_color_16p transpColor = NULL;
511 int numTransp = 0;
512
513 png_get_tRNS(png_ptr, info_ptr, NULL, &numTransp, &transpColor);
514
515 bool valid = png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS);
516
517 if (valid && numTransp == 1 && transpColor != NULL) {
518 /* Compute our transparent color, which we'll match against later.
519 We don't really handle 16bit components properly here, since we
520 do our compare *after* the values have been knocked down to 8bit
521 which means we will find more matches than we should. The real
522 fix seems to be to see the actual 16bit components, do the
523 compare, and then knock it down to 8bits ourselves.
524 */
525 if (colorType & PNG_COLOR_MASK_COLOR) {
526 if (16 == bitDepth) {
527 *theTranspColorp = SkPackARGB32(0xFF, transpColor->red >> 8,
528 transpColor->green >> 8,
529 transpColor->blue >> 8);
530 } else {
halcanary@google.comfed30372013-10-04 12:46:45 +0000531 /* We apply the mask because in a very small
532 number of corrupt PNGs, (transpColor->red > 255)
533 and (bitDepth == 8), for certain versions of libpng. */
534 *theTranspColorp = SkPackARGB32(0xFF,
535 0xFF & (transpColor->red),
536 0xFF & (transpColor->green),
537 0xFF & (transpColor->blue));
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000538 }
539 } else { // gray
540 if (16 == bitDepth) {
541 *theTranspColorp = SkPackARGB32(0xFF, transpColor->gray >> 8,
542 transpColor->gray >> 8,
543 transpColor->gray >> 8);
544 } else {
halcanary@google.comfed30372013-10-04 12:46:45 +0000545 /* We apply the mask because in a very small
546 number of corrupt PNGs, (transpColor->red >
547 255) and (bitDepth == 8), for certain versions
548 of libpng. For safety we assume the same could
549 happen with a grayscale PNG. */
550 *theTranspColorp = SkPackARGB32(0xFF,
551 0xFF & (transpColor->gray),
552 0xFF & (transpColor->gray),
553 0xFF & (transpColor->gray));
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000554 }
555 }
556 }
557
558 if (valid ||
559 PNG_COLOR_TYPE_RGB_ALPHA == colorType ||
560 PNG_COLOR_TYPE_GRAY_ALPHA == colorType) {
561 *hasAlphap = true;
562 }
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000563
564 SrcDepth srcDepth = k32Bit_SrcDepth;
565 if (PNG_COLOR_TYPE_GRAY == colorType) {
566 srcDepth = k8BitGray_SrcDepth;
scroggo@google.com8e2ef012013-07-18 20:14:45 +0000567 // Remove this assert, which fails on desk_pokemonwiki.skp
568 //SkASSERT(!*hasAlphap);
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000569 }
570
571 *configp = this->getPrefConfig(srcDepth, *hasAlphap);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000572 // now match the request against our capabilities
573 if (*hasAlphap) {
574 if (*configp != SkBitmap::kARGB_4444_Config) {
575 *configp = SkBitmap::kARGB_8888_Config;
576 }
577 } else {
scroggo@google.com354fd972013-10-02 15:50:19 +0000578 if (SkBitmap::kA8_Config == *configp) {
579 if (k8BitGray_SrcDepth != srcDepth) {
580 // Converting a non grayscale image to A8 is not currently supported.
581 *configp = SkBitmap::kARGB_8888_Config;
582 }
583 } else if (*configp != SkBitmap::kRGB_565_Config &&
584 *configp != SkBitmap::kARGB_4444_Config) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000585 *configp = SkBitmap::kARGB_8888_Config;
586 }
587 }
588 }
589
590 // sanity check for size
591 {
592 Sk64 size;
593 size.setMul(origWidth, origHeight);
594 if (size.isNeg() || !size.is32()) {
595 return false;
596 }
597 // now check that if we are 4-bytes per pixel, we also don't overflow
598 if (size.get32() > (0x7FFFFFFF >> 2)) {
599 return false;
600 }
601 }
602
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000603 if (!this->chooseFromOneChoice(*configp, origWidth, origHeight)) {
604 return false;
605 }
606
607 // If the image has alpha and the decoder wants unpremultiplied
608 // colors, the only supported config is 8888.
609 if (this->getRequireUnpremultipliedColors() && *hasAlphap) {
610 *configp = SkBitmap::kARGB_8888_Config;
611 }
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000612
613 if (fImageIndex != NULL) {
614 if (SkBitmap::kNo_Config == fImageIndex->fConfig) {
615 // This is the first time for this subset decode. From now on,
616 // all decodes must be in the same config.
617 fImageIndex->fConfig = *configp;
618 } else if (fImageIndex->fConfig != *configp) {
619 // Requesting a different config for a subsequent decode is not
620 // supported. Report failure before we make changes to png_ptr.
621 return false;
622 }
623 }
624
625 bool convertGrayToRGB = PNG_COLOR_TYPE_GRAY == colorType
626 && *configp != SkBitmap::kA8_Config;
627
628 // Unless the user is requesting A8, convert a grayscale image into RGB.
629 // GRAY_ALPHA will always be converted to RGB
630 if (convertGrayToRGB || colorType == PNG_COLOR_TYPE_GRAY_ALPHA) {
631 png_set_gray_to_rgb(png_ptr);
632 }
633
634 // Add filler (or alpha) byte (after each RGB triplet) if necessary.
635 if (colorType == PNG_COLOR_TYPE_RGB || convertGrayToRGB) {
636 png_set_filler(png_ptr, 0xff, PNG_FILLER_AFTER);
637 }
638
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000639 return true;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000640}
641
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000642typedef uint32_t (*PackColorProc)(U8CPU a, U8CPU r, U8CPU g, U8CPU b);
643
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000644bool SkPNGImageDecoder::decodePalette(png_structp png_ptr, png_infop info_ptr,
645 bool *hasAlphap, bool *reallyHasAlphap,
646 SkColorTable **colorTablep) {
647 int numPalette;
648 png_colorp palette;
649 png_bytep trans;
650 int numTrans;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000651
652 png_get_PLTE(png_ptr, info_ptr, &palette, &numPalette);
653
654 /* BUGGY IMAGE WORKAROUND
655
656 We hit some images (e.g. fruit_.png) who contain bytes that are == colortable_count
657 which is a problem since we use the byte as an index. To work around this we grow
658 the colortable by 1 (if its < 256) and duplicate the last color into that slot.
659 */
660 int colorCount = numPalette + (numPalette < 256);
reed@google.com0a6151d2013-10-10 14:44:56 +0000661 SkPMColor colorStorage[256]; // worst-case storage
662 SkPMColor* colorPtr = colorStorage;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000663
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000664 if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
665 png_get_tRNS(png_ptr, info_ptr, &trans, &numTrans, NULL);
666 *hasAlphap = (numTrans > 0);
667 } else {
668 numTrans = 0;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000669 }
reed@google.com0a6151d2013-10-10 14:44:56 +0000670
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000671 // check for bad images that might make us crash
672 if (numTrans > numPalette) {
673 numTrans = numPalette;
674 }
675
676 int index = 0;
677 int transLessThanFF = 0;
678
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000679 // Choose which function to use to create the color table. If the final destination's
680 // config is unpremultiplied, the color table will store unpremultiplied colors.
681 PackColorProc proc;
682 if (this->getRequireUnpremultipliedColors()) {
683 proc = &SkPackARGB32NoCheck;
684 } else {
685 proc = &SkPreMultiplyARGB;
686 }
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000687 for (; index < numTrans; index++) {
688 transLessThanFF |= (int)*trans - 0xFF;
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000689 *colorPtr++ = proc(*trans++, palette->red, palette->green, palette->blue);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000690 palette++;
691 }
reed@google.com0a6151d2013-10-10 14:44:56 +0000692 bool reallyHasAlpha = (transLessThanFF < 0);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000693
694 for (; index < numPalette; index++) {
695 *colorPtr++ = SkPackARGB32(0xFF, palette->red, palette->green, palette->blue);
696 palette++;
697 }
698
699 // see BUGGY IMAGE WORKAROUND comment above
700 if (numPalette < 256) {
701 *colorPtr = colorPtr[-1];
702 }
reed@google.com0a6151d2013-10-10 14:44:56 +0000703
704 SkAlphaType alphaType = kOpaque_SkAlphaType;
705 if (reallyHasAlpha) {
706 if (this->getRequireUnpremultipliedColors()) {
707 alphaType = kUnpremul_SkAlphaType;
708 } else {
709 alphaType = kPremul_SkAlphaType;
710 }
711 }
712
713 *colorTablep = SkNEW_ARGS(SkColorTable,
714 (colorStorage, colorCount, alphaType));
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000715 *reallyHasAlphap = reallyHasAlpha;
716 return true;
717}
718
719#ifdef SK_BUILD_FOR_ANDROID
720
scroggo@google.comb5571b32013-09-25 21:34:24 +0000721bool SkPNGImageDecoder::onBuildTileIndex(SkStreamRewindable* sk_stream, int *width, int *height) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000722 png_structp png_ptr;
723 png_infop info_ptr;
724
725 if (!onDecodeInit(sk_stream, &png_ptr, &info_ptr)) {
726 return false;
727 }
728
729 if (setjmp(png_jmpbuf(png_ptr)) != 0) {
730 png_destroy_read_struct(&png_ptr, &info_ptr, png_infopp_NULL);
731 return false;
732 }
733
734 png_uint_32 origWidth, origHeight;
735 int bitDepth, colorType;
736 png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
737 &colorType, int_p_NULL, int_p_NULL, int_p_NULL);
738
739 *width = origWidth;
740 *height = origHeight;
741
742 png_build_index(png_ptr);
743
744 if (fImageIndex) {
745 SkDELETE(fImageIndex);
746 }
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000747 fImageIndex = SkNEW_ARGS(SkPNGImageIndex, (sk_stream, png_ptr, info_ptr));
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000748
749 return true;
750}
751
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000752bool SkPNGImageDecoder::onDecodeSubset(SkBitmap* bm, const SkIRect& region) {
753 if (NULL == fImageIndex) {
754 return false;
755 }
756
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000757 png_structp png_ptr = fImageIndex->fPng_ptr;
758 png_infop info_ptr = fImageIndex->fInfo_ptr;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000759 if (setjmp(png_jmpbuf(png_ptr))) {
760 return false;
761 }
762
763 png_uint_32 origWidth, origHeight;
764 int bitDepth, colorType, interlaceType;
765 png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
766 &colorType, &interlaceType, int_p_NULL, int_p_NULL);
767
768 SkIRect rect = SkIRect::MakeWH(origWidth, origHeight);
769
770 if (!rect.intersect(region)) {
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000771 // If the requested region is entirely outside the image, just
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000772 // returns false
773 return false;
774 }
775
776 SkBitmap::Config config;
777 bool hasAlpha = false;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000778 SkPMColor theTranspColor = 0; // 0 tells us not to try to match
779
scroggo@google.com8d239242013-10-01 17:27:15 +0000780 if (!this->getBitmapConfig(png_ptr, info_ptr, &config, &hasAlpha, &theTranspColor)) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000781 return false;
782 }
783
784 const int sampleSize = this->getSampleSize();
785 SkScaledBitmapSampler sampler(origWidth, rect.height(), sampleSize);
786
787 SkBitmap decodedBitmap;
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000788 decodedBitmap.setConfig(config, sampler.scaledWidth(), sampler.scaledHeight());
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000789
790 // from here down we are concerned with colortables and pixels
791
792 // we track if we actually see a non-opaque pixels, since sometimes a PNG sets its colortype
793 // to |= PNG_COLOR_MASK_ALPHA, but all of its pixels are in fact opaque. We care, since we
794 // draw lots faster if we can flag the bitmap has being opaque
795 bool reallyHasAlpha = false;
796 SkColorTable* colorTable = NULL;
797
798 if (colorType == PNG_COLOR_TYPE_PALETTE) {
799 decodePalette(png_ptr, info_ptr, &hasAlpha, &reallyHasAlpha, &colorTable);
800 }
801
802 SkAutoUnref aur(colorTable);
803
804 // Check ahead of time if the swap(dest, src) is possible.
805 // If yes, then we will stick to AllocPixelRef since it's cheaper with the swap happening.
806 // If no, then we will use alloc to allocate pixels to prevent garbage collection.
807 int w = rect.width() / sampleSize;
808 int h = rect.height() / sampleSize;
809 const bool swapOnly = (rect == region) && (w == decodedBitmap.width()) &&
810 (h == decodedBitmap.height()) && bm->isNull();
811 const bool needColorTable = SkBitmap::kIndex8_Config == config;
812 if (swapOnly) {
813 if (!this->allocPixelRef(&decodedBitmap, needColorTable ? colorTable : NULL)) {
814 return false;
815 }
816 } else {
817 if (!decodedBitmap.allocPixels(NULL, needColorTable ? colorTable : NULL)) {
818 return false;
819 }
820 }
821 SkAutoLockPixels alp(decodedBitmap);
822
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000823 /* Turn on interlace handling. REQUIRED if you are not using
824 * png_read_image(). To see how to handle interlacing passes,
825 * see the png_read_row() method below:
826 */
827 const int number_passes = (interlaceType != PNG_INTERLACE_NONE) ?
828 png_set_interlace_handling(png_ptr) : 1;
829
830 /* Optional call to gamma correct and add the background to the palette
831 * and update info structure. REQUIRED if you are expecting libpng to
832 * update the palette for you (ie you selected such a transform above).
833 */
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000834
835 // Direct access to png_ptr fields is deprecated in libpng > 1.2.
836#if defined(PNG_1_0_X) || defined (PNG_1_2_X)
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000837 png_ptr->pass = 0;
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000838#else
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000839 // FIXME: This sets pass as desired, but also sets iwidth. Is that ok?
840 png_set_interlaced_pass(png_ptr, 0);
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000841#endif
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000842 png_read_update_info(png_ptr, info_ptr);
843
844 int actualTop = rect.fTop;
845
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000846 if ((SkBitmap::kA8_Config == config || SkBitmap::kIndex8_Config == config)
847 && 1 == sampleSize) {
848 // A8 is only allowed if the original was GRAY.
849 SkASSERT(config != SkBitmap::kA8_Config
850 || PNG_COLOR_TYPE_GRAY == colorType);
851
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000852 for (int i = 0; i < number_passes; i++) {
853 png_configure_decoder(png_ptr, &actualTop, i);
854 for (int j = 0; j < rect.fTop - actualTop; j++) {
855 uint8_t* bmRow = decodedBitmap.getAddr8(0, 0);
856 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
857 }
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000858 png_uint_32 bitmapHeight = (png_uint_32) decodedBitmap.height();
859 for (png_uint_32 y = 0; y < bitmapHeight; y++) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000860 uint8_t* bmRow = decodedBitmap.getAddr8(0, y);
861 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
862 }
863 }
864 } else {
865 SkScaledBitmapSampler::SrcConfig sc;
866 int srcBytesPerPixel = 4;
867
868 if (colorTable != NULL) {
869 sc = SkScaledBitmapSampler::kIndex;
870 srcBytesPerPixel = 1;
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000871 } else if (SkBitmap::kA8_Config == config) {
872 // A8 is only allowed if the original was GRAY.
873 SkASSERT(PNG_COLOR_TYPE_GRAY == colorType);
874 sc = SkScaledBitmapSampler::kGray;
875 srcBytesPerPixel = 1;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000876 } else if (hasAlpha) {
877 sc = SkScaledBitmapSampler::kRGBA;
878 } else {
879 sc = SkScaledBitmapSampler::kRGBX;
880 }
881
882 /* We have to pass the colortable explicitly, since we may have one
883 even if our decodedBitmap doesn't, due to the request that we
884 upscale png's palette to a direct model
885 */
886 SkAutoLockColors ctLock(colorTable);
scroggo@google.com8d239242013-10-01 17:27:15 +0000887 if (!sampler.begin(&decodedBitmap, sc, *this, ctLock.colors())) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000888 return false;
889 }
890 const int height = decodedBitmap.height();
891
892 if (number_passes > 1) {
893 SkAutoMalloc storage(origWidth * origHeight * srcBytesPerPixel);
894 uint8_t* base = (uint8_t*)storage.get();
895 size_t rb = origWidth * srcBytesPerPixel;
896
897 for (int i = 0; i < number_passes; i++) {
898 png_configure_decoder(png_ptr, &actualTop, i);
899 for (int j = 0; j < rect.fTop - actualTop; j++) {
900 uint8_t* bmRow = (uint8_t*)decodedBitmap.getPixels();
901 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
902 }
903 uint8_t* row = base;
904 for (int32_t y = 0; y < rect.height(); y++) {
905 uint8_t* bmRow = row;
906 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
907 row += rb;
908 }
909 }
910 // now sample it
911 base += sampler.srcY0() * rb;
912 for (int y = 0; y < height; y++) {
913 reallyHasAlpha |= sampler.next(base);
914 base += sampler.srcDY() * rb;
915 }
916 } else {
917 SkAutoMalloc storage(origWidth * srcBytesPerPixel);
918 uint8_t* srcRow = (uint8_t*)storage.get();
919
920 png_configure_decoder(png_ptr, &actualTop, 0);
921 skip_src_rows(png_ptr, srcRow, sampler.srcY0());
922
923 for (int i = 0; i < rect.fTop - actualTop; i++) {
924 uint8_t* bmRow = (uint8_t*)decodedBitmap.getPixels();
925 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
926 }
927 for (int y = 0; y < height; y++) {
928 uint8_t* tmp = srcRow;
929 png_read_rows(png_ptr, &tmp, png_bytepp_NULL, 1);
930 reallyHasAlpha |= sampler.next(srcRow);
931 if (y < height - 1) {
932 skip_src_rows(png_ptr, srcRow, sampler.srcDY() - 1);
933 }
934 }
935 }
936 }
937
938 if (0 != theTranspColor) {
939 reallyHasAlpha |= substituteTranspColor(&decodedBitmap, theTranspColor);
940 }
commit-bot@chromium.org546f70c2013-10-03 17:13:38 +0000941 if (SkBitmap::kA8_Config == decodedBitmap.config()) {
942 reallyHasAlpha = true;
943 }
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000944 decodedBitmap.setIsOpaque(!reallyHasAlpha);
945
946 if (swapOnly) {
947 bm->swap(decodedBitmap);
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000948 return true;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000949 }
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000950 return this->cropBitmap(bm, &decodedBitmap, sampleSize, region.x(), region.y(),
951 region.width(), region.height(), 0, rect.y());
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000952}
953#endif
954
reed@android.com8a1c16f2008-12-17 15:59:43 +0000955///////////////////////////////////////////////////////////////////////////////
956
reed@android.com8a1c16f2008-12-17 15:59:43 +0000957#include "SkColorPriv.h"
958#include "SkUnPreMultiply.h"
959
960static void sk_write_fn(png_structp png_ptr, png_bytep data, png_size_t len) {
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000961 SkWStream* sk_stream = (SkWStream*)png_get_io_ptr(png_ptr);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000962 if (!sk_stream->write(data, len)) {
963 png_error(png_ptr, "sk_write_fn Error!");
964 }
965}
966
reed@android.com8a1c16f2008-12-17 15:59:43 +0000967static transform_scanline_proc choose_proc(SkBitmap::Config config,
968 bool hasAlpha) {
969 // we don't care about search on alpha if we're kIndex8, since only the
970 // colortable packing cares about that distinction, not the pixels
971 if (SkBitmap::kIndex8_Config == config) {
972 hasAlpha = false; // we store false in the table entries for kIndex8
973 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000974
reed@android.com8a1c16f2008-12-17 15:59:43 +0000975 static const struct {
976 SkBitmap::Config fConfig;
977 bool fHasAlpha;
978 transform_scanline_proc fProc;
979 } gMap[] = {
980 { SkBitmap::kRGB_565_Config, false, transform_scanline_565 },
981 { SkBitmap::kARGB_8888_Config, false, transform_scanline_888 },
982 { SkBitmap::kARGB_8888_Config, true, transform_scanline_8888 },
983 { SkBitmap::kARGB_4444_Config, false, transform_scanline_444 },
984 { SkBitmap::kARGB_4444_Config, true, transform_scanline_4444 },
epoger@google.com4ce738b2012-11-16 18:44:18 +0000985 { SkBitmap::kIndex8_Config, false, transform_scanline_memcpy },
reed@android.com8a1c16f2008-12-17 15:59:43 +0000986 };
987
988 for (int i = SK_ARRAY_COUNT(gMap) - 1; i >= 0; --i) {
989 if (gMap[i].fConfig == config && gMap[i].fHasAlpha == hasAlpha) {
990 return gMap[i].fProc;
991 }
992 }
993 sk_throw();
994 return NULL;
995}
996
997// return the minimum legal bitdepth (by png standards) for this many colortable
998// entries. SkBitmap always stores in 8bits per pixel, but for colorcount <= 16,
999// we can use fewer bits per in png
1000static int computeBitDepth(int colorCount) {
1001#if 0
1002 int bits = SkNextLog2(colorCount);
1003 SkASSERT(bits >= 1 && bits <= 8);
1004 // now we need bits itself to be a power of 2 (e.g. 1, 2, 4, 8)
1005 return SkNextPow2(bits);
1006#else
1007 // for the moment, we don't know how to pack bitdepth < 8
1008 return 8;
1009#endif
1010}
1011
1012/* Pack palette[] with the corresponding colors, and if hasAlpha is true, also
1013 pack trans[] and return the number of trans[] entries written. If hasAlpha
1014 is false, the return value will always be 0.
rmistry@google.comd6176b02012-08-23 18:14:13 +00001015
reed@android.com8a1c16f2008-12-17 15:59:43 +00001016 Note: this routine takes care of unpremultiplying the RGB values when we
1017 have alpha in the colortable, since png doesn't support premul colors
1018*/
reed@android.com6f252972009-01-14 16:46:16 +00001019static inline int pack_palette(SkColorTable* ctable,
reed@android.comb50a60c2009-01-14 17:51:08 +00001020 png_color* SK_RESTRICT palette,
1021 png_byte* SK_RESTRICT trans, bool hasAlpha) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001022 SkAutoLockColors alc(ctable);
1023 const SkPMColor* SK_RESTRICT colors = alc.colors();
1024 const int ctCount = ctable->count();
1025 int i, num_trans = 0;
1026
1027 if (hasAlpha) {
1028 /* first see if we have some number of fully opaque at the end of the
1029 ctable. PNG allows num_trans < num_palette, but all of the trans
1030 entries must come first in the palette. If I was smarter, I'd
1031 reorder the indices and ctable so that all non-opaque colors came
1032 first in the palette. But, since that would slow down the encode,
1033 I'm leaving the indices and ctable order as is, and just looking
1034 at the tail of the ctable for opaqueness.
1035 */
1036 num_trans = ctCount;
1037 for (i = ctCount - 1; i >= 0; --i) {
1038 if (SkGetPackedA32(colors[i]) != 0xFF) {
1039 break;
1040 }
1041 num_trans -= 1;
1042 }
rmistry@google.comd6176b02012-08-23 18:14:13 +00001043
reed@android.com8a1c16f2008-12-17 15:59:43 +00001044 const SkUnPreMultiply::Scale* SK_RESTRICT table =
1045 SkUnPreMultiply::GetScaleTable();
1046
1047 for (i = 0; i < num_trans; i++) {
1048 const SkPMColor c = *colors++;
1049 const unsigned a = SkGetPackedA32(c);
1050 const SkUnPreMultiply::Scale s = table[a];
1051 trans[i] = a;
1052 palette[i].red = SkUnPreMultiply::ApplyScale(s, SkGetPackedR32(c));
1053 palette[i].green = SkUnPreMultiply::ApplyScale(s,SkGetPackedG32(c));
1054 palette[i].blue = SkUnPreMultiply::ApplyScale(s, SkGetPackedB32(c));
rmistry@google.comd6176b02012-08-23 18:14:13 +00001055 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001056 // now fall out of this if-block to use common code for the trailing
1057 // opaque entries
1058 }
rmistry@google.comd6176b02012-08-23 18:14:13 +00001059
reed@android.com8a1c16f2008-12-17 15:59:43 +00001060 // these (remaining) entries are opaque
1061 for (i = num_trans; i < ctCount; i++) {
1062 SkPMColor c = *colors++;
1063 palette[i].red = SkGetPackedR32(c);
1064 palette[i].green = SkGetPackedG32(c);
1065 palette[i].blue = SkGetPackedB32(c);
1066 }
1067 return num_trans;
1068}
1069
1070class SkPNGImageEncoder : public SkImageEncoder {
1071protected:
commit-bot@chromium.orga936e372013-03-14 14:42:18 +00001072 virtual bool onEncode(SkWStream* stream, const SkBitmap& bm, int quality) SK_OVERRIDE;
tomhudson@google.com5c210c72011-07-28 21:06:40 +00001073private:
1074 bool doEncode(SkWStream* stream, const SkBitmap& bm,
1075 const bool& hasAlpha, int colorType,
1076 int bitDepth, SkBitmap::Config config,
1077 png_color_8& sig_bit);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +00001078
1079 typedef SkImageEncoder INHERITED;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001080};
1081
1082bool SkPNGImageEncoder::onEncode(SkWStream* stream, const SkBitmap& bitmap,
1083 int /*quality*/) {
1084 SkBitmap::Config config = bitmap.getConfig();
1085
1086 const bool hasAlpha = !bitmap.isOpaque();
1087 int colorType = PNG_COLOR_MASK_COLOR;
1088 int bitDepth = 8; // default for color
1089 png_color_8 sig_bit;
1090
1091 switch (config) {
1092 case SkBitmap::kIndex8_Config:
1093 colorType |= PNG_COLOR_MASK_PALETTE;
1094 // fall through to the ARGB_8888 case
1095 case SkBitmap::kARGB_8888_Config:
1096 sig_bit.red = 8;
1097 sig_bit.green = 8;
1098 sig_bit.blue = 8;
1099 sig_bit.alpha = 8;
1100 break;
1101 case SkBitmap::kARGB_4444_Config:
1102 sig_bit.red = 4;
1103 sig_bit.green = 4;
1104 sig_bit.blue = 4;
1105 sig_bit.alpha = 4;
1106 break;
1107 case SkBitmap::kRGB_565_Config:
1108 sig_bit.red = 5;
1109 sig_bit.green = 6;
1110 sig_bit.blue = 5;
1111 sig_bit.alpha = 0;
1112 break;
1113 default:
1114 return false;
1115 }
rmistry@google.comd6176b02012-08-23 18:14:13 +00001116
reed@android.com8a1c16f2008-12-17 15:59:43 +00001117 if (hasAlpha) {
1118 // don't specify alpha if we're a palette, even if our ctable has alpha
1119 if (!(colorType & PNG_COLOR_MASK_PALETTE)) {
1120 colorType |= PNG_COLOR_MASK_ALPHA;
1121 }
1122 } else {
1123 sig_bit.alpha = 0;
1124 }
rmistry@google.comd6176b02012-08-23 18:14:13 +00001125
reed@android.com8a1c16f2008-12-17 15:59:43 +00001126 SkAutoLockPixels alp(bitmap);
1127 // readyToDraw checks for pixels (and colortable if that is required)
1128 if (!bitmap.readyToDraw()) {
1129 return false;
1130 }
1131
1132 // we must do this after we have locked the pixels
1133 SkColorTable* ctable = bitmap.getColorTable();
1134 if (NULL != ctable) {
1135 if (ctable->count() == 0) {
1136 return false;
1137 }
1138 // check if we can store in fewer than 8 bits
1139 bitDepth = computeBitDepth(ctable->count());
1140 }
1141
tomhudson@google.com5c210c72011-07-28 21:06:40 +00001142 return doEncode(stream, bitmap, hasAlpha, colorType,
1143 bitDepth, config, sig_bit);
1144}
1145
1146bool SkPNGImageEncoder::doEncode(SkWStream* stream, const SkBitmap& bitmap,
1147 const bool& hasAlpha, int colorType,
1148 int bitDepth, SkBitmap::Config config,
1149 png_color_8& sig_bit) {
1150
reed@android.com8a1c16f2008-12-17 15:59:43 +00001151 png_structp png_ptr;
1152 png_infop info_ptr;
1153
1154 png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, sk_error_fn,
1155 NULL);
1156 if (NULL == png_ptr) {
1157 return false;
1158 }
1159
1160 info_ptr = png_create_info_struct(png_ptr);
1161 if (NULL == info_ptr) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +00001162 png_destroy_write_struct(&png_ptr, png_infopp_NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001163 return false;
1164 }
1165
1166 /* Set error handling. REQUIRED if you aren't supplying your own
1167 * error handling functions in the png_create_write_struct() call.
1168 */
1169 if (setjmp(png_jmpbuf(png_ptr))) {
1170 png_destroy_write_struct(&png_ptr, &info_ptr);
1171 return false;
1172 }
1173
commit-bot@chromium.orga936e372013-03-14 14:42:18 +00001174 png_set_write_fn(png_ptr, (void*)stream, sk_write_fn, png_flush_ptr_NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001175
1176 /* Set the image information here. Width and height are up to 2^31,
1177 * bit_depth is one of 1, 2, 4, 8, or 16, but valid values also depend on
1178 * the color_type selected. color_type is one of PNG_COLOR_TYPE_GRAY,
1179 * PNG_COLOR_TYPE_GRAY_ALPHA, PNG_COLOR_TYPE_PALETTE, PNG_COLOR_TYPE_RGB,
1180 * or PNG_COLOR_TYPE_RGB_ALPHA. interlace is either PNG_INTERLACE_NONE or
1181 * PNG_INTERLACE_ADAM7, and the compression_type and filter_type MUST
1182 * currently be PNG_COMPRESSION_TYPE_BASE and PNG_FILTER_TYPE_BASE. REQUIRED
1183 */
1184
1185 png_set_IHDR(png_ptr, info_ptr, bitmap.width(), bitmap.height(),
1186 bitDepth, colorType,
1187 PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE,
1188 PNG_FILTER_TYPE_BASE);
1189
reed@android.com61898772009-07-07 19:38:01 +00001190 // set our colortable/trans arrays if needed
1191 png_color paletteColors[256];
1192 png_byte trans[256];
1193 if (SkBitmap::kIndex8_Config == config) {
1194 SkColorTable* ct = bitmap.getColorTable();
1195 int numTrans = pack_palette(ct, paletteColors, trans, hasAlpha);
1196 png_set_PLTE(png_ptr, info_ptr, paletteColors, ct->count());
1197 if (numTrans > 0) {
1198 png_set_tRNS(png_ptr, info_ptr, trans, numTrans, NULL);
1199 }
1200 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001201
1202 png_set_sBIT(png_ptr, info_ptr, &sig_bit);
1203 png_write_info(png_ptr, info_ptr);
1204
1205 const char* srcImage = (const char*)bitmap.getPixels();
1206 SkAutoSMalloc<1024> rowStorage(bitmap.width() << 2);
1207 char* storage = (char*)rowStorage.get();
1208 transform_scanline_proc proc = choose_proc(config, hasAlpha);
1209
1210 for (int y = 0; y < bitmap.height(); y++) {
1211 png_bytep row_ptr = (png_bytep)storage;
1212 proc(srcImage, bitmap.width(), storage);
1213 png_write_rows(png_ptr, &row_ptr, 1);
1214 srcImage += bitmap.rowBytes();
1215 }
1216
1217 png_write_end(png_ptr, info_ptr);
1218
1219 /* clean up after the write, and free any memory allocated */
1220 png_destroy_write_struct(&png_ptr, &info_ptr);
1221 return true;
1222}
1223
reed@android.com00bf85a2009-01-22 13:04:56 +00001224///////////////////////////////////////////////////////////////////////////////
robertphillips@google.comec51cb82012-03-23 18:13:47 +00001225DEFINE_DECODER_CREATOR(PNGImageDecoder);
1226DEFINE_ENCODER_CREATOR(PNGImageEncoder);
1227///////////////////////////////////////////////////////////////////////////////
reed@android.com00bf85a2009-01-22 13:04:56 +00001228
scroggo@google.comb5571b32013-09-25 21:34:24 +00001229static bool is_png(SkStreamRewindable* stream) {
reed@android.com00bf85a2009-01-22 13:04:56 +00001230 char buf[PNG_BYTES_TO_CHECK];
1231 if (stream->read(buf, PNG_BYTES_TO_CHECK) == PNG_BYTES_TO_CHECK &&
1232 !png_sig_cmp((png_bytep) buf, (png_size_t)0, PNG_BYTES_TO_CHECK)) {
scroggo@google.com39edf4c2013-04-25 17:33:51 +00001233 return true;
1234 }
1235 return false;
1236}
1237
scroggo@google.comb5571b32013-09-25 21:34:24 +00001238SkImageDecoder* sk_libpng_dfactory(SkStreamRewindable* stream) {
scroggo@google.com39edf4c2013-04-25 17:33:51 +00001239 if (is_png(stream)) {
reed@android.com00bf85a2009-01-22 13:04:56 +00001240 return SkNEW(SkPNGImageDecoder);
1241 }
1242 return NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001243}
1244
scroggo@google.comb5571b32013-09-25 21:34:24 +00001245static SkImageDecoder::Format get_format_png(SkStreamRewindable* stream) {
scroggo@google.com39edf4c2013-04-25 17:33:51 +00001246 if (is_png(stream)) {
1247 return SkImageDecoder::kPNG_Format;
1248 }
1249 return SkImageDecoder::kUnknown_Format;
1250}
1251
reed@android.comdfee5792010-04-15 14:24:50 +00001252SkImageEncoder* sk_libpng_efactory(SkImageEncoder::Type t) {
reed@android.com00bf85a2009-01-22 13:04:56 +00001253 return (SkImageEncoder::kPNG_Type == t) ? SkNEW(SkPNGImageEncoder) : NULL;
1254}
1255
mtklein@google.combd6343b2013-09-04 17:20:18 +00001256static SkImageDecoder_DecodeReg gDReg(sk_libpng_dfactory);
1257static SkImageDecoder_FormatReg gFormatReg(get_format_png);
1258static SkImageEncoder_EncodeReg gEReg(sk_libpng_efactory);