blob: 5fc9350b519e23d691bb368aac9cef8d3a0d726b [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) {
375 // A8 is only allowed if the original was GRAY.
376 SkASSERT(config != SkBitmap::kA8_Config
377 || PNG_COLOR_TYPE_GRAY == colorType);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000378 for (int i = 0; i < number_passes; i++) {
379 for (png_uint_32 y = 0; y < origHeight; y++) {
380 uint8_t* bmRow = decodedBitmap->getAddr8(0, y);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000381 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000382 }
383 }
384 } else {
385 SkScaledBitmapSampler::SrcConfig sc;
386 int srcBytesPerPixel = 4;
rmistry@google.comd6176b02012-08-23 18:14:13 +0000387
reed@android.com11344262009-07-08 20:09:23 +0000388 if (colorTable != NULL) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000389 sc = SkScaledBitmapSampler::kIndex;
390 srcBytesPerPixel = 1;
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000391 } else if (SkBitmap::kA8_Config == config) {
392 // A8 is only allowed if the original was GRAY.
393 SkASSERT(PNG_COLOR_TYPE_GRAY == colorType);
394 sc = SkScaledBitmapSampler::kGray;
395 srcBytesPerPixel = 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000396 } else if (hasAlpha) {
397 sc = SkScaledBitmapSampler::kRGBA;
398 } else {
399 sc = SkScaledBitmapSampler::kRGBX;
400 }
reed@android.com11344262009-07-08 20:09:23 +0000401
402 /* We have to pass the colortable explicitly, since we may have one
403 even if our decodedBitmap doesn't, due to the request that we
404 upscale png's palette to a direct model
405 */
406 SkAutoLockColors ctLock(colorTable);
scroggo@google.com8d239242013-10-01 17:27:15 +0000407 if (!sampler.begin(decodedBitmap, sc, *this, ctLock.colors())) {
reed@android.com862e91b2009-04-28 15:27:07 +0000408 return false;
409 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000410 const int height = decodedBitmap->height();
411
reed@android.com862e91b2009-04-28 15:27:07 +0000412 if (number_passes > 1) {
413 SkAutoMalloc storage(origWidth * origHeight * srcBytesPerPixel);
414 uint8_t* base = (uint8_t*)storage.get();
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000415 size_t rowBytes = origWidth * srcBytesPerPixel;
reed@android.coma8a8b8b2009-05-04 15:00:11 +0000416
reed@android.com862e91b2009-04-28 15:27:07 +0000417 for (int i = 0; i < number_passes; i++) {
418 uint8_t* row = base;
419 for (png_uint_32 y = 0; y < origHeight; y++) {
420 uint8_t* bmRow = row;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000421 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
422 row += rowBytes;
reed@android.com862e91b2009-04-28 15:27:07 +0000423 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000424 }
reed@android.com862e91b2009-04-28 15:27:07 +0000425 // now sample it
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000426 base += sampler.srcY0() * rowBytes;
reed@android.com862e91b2009-04-28 15:27:07 +0000427 for (int y = 0; y < height; y++) {
428 reallyHasAlpha |= sampler.next(base);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000429 base += sampler.srcDY() * rowBytes;
reed@android.com862e91b2009-04-28 15:27:07 +0000430 }
431 } else {
432 SkAutoMalloc storage(origWidth * srcBytesPerPixel);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000433 uint8_t* srcRow = (uint8_t*)storage.get();
434 skip_src_rows(png_ptr, srcRow, sampler.srcY0());
435
436 for (int y = 0; y < height; y++) {
437 uint8_t* tmp = srcRow;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000438 png_read_rows(png_ptr, &tmp, png_bytepp_NULL, 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000439 reallyHasAlpha |= sampler.next(srcRow);
440 if (y < height - 1) {
441 skip_src_rows(png_ptr, srcRow, sampler.srcDY() - 1);
442 }
443 }
reed@android.com862e91b2009-04-28 15:27:07 +0000444
reed@android.com8a1c16f2008-12-17 15:59:43 +0000445 // skip the rest of the rows (if any)
446 png_uint_32 read = (height - 1) * sampler.srcDY() +
447 sampler.srcY0() + 1;
448 SkASSERT(read <= origHeight);
449 skip_src_rows(png_ptr, srcRow, origHeight - read);
450 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000451 }
452
453 /* read rest of file, and get additional chunks in info_ptr - REQUIRED */
454 png_read_end(png_ptr, info_ptr);
455
456 if (0 != theTranspColor) {
457 reallyHasAlpha |= substituteTranspColor(decodedBitmap, theTranspColor);
458 }
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000459 if (reallyHasAlpha && this->getRequireUnpremultipliedColors() &&
460 SkBitmap::kARGB_8888_Config != decodedBitmap->config()) {
461 // If the caller wants an unpremultiplied bitmap, and we let them get
462 // away with a config other than 8888, and it has alpha after all,
463 // return false, since the result will have premultiplied colors.
464 return false;
465 }
commit-bot@chromium.org546f70c2013-10-03 17:13:38 +0000466 if (SkBitmap::kA8_Config == decodedBitmap->config()) {
467 reallyHasAlpha = true;
468 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000469 decodedBitmap->setIsOpaque(!reallyHasAlpha);
470 return true;
471}
472
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000473
474
475bool SkPNGImageDecoder::getBitmapConfig(png_structp png_ptr, png_infop info_ptr,
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000476 SkBitmap::Config* SK_RESTRICT configp,
477 bool* SK_RESTRICT hasAlphap,
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000478 SkPMColor* SK_RESTRICT theTranspColorp) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000479 png_uint_32 origWidth, origHeight;
480 int bitDepth, colorType;
481 png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
482 &colorType, int_p_NULL, int_p_NULL, int_p_NULL);
483
484 // check for sBIT chunk data, in case we should disable dithering because
485 // our data is not truely 8bits per component
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000486 png_color_8p sig_bit;
scroggo@google.com8d239242013-10-01 17:27:15 +0000487 if (this->getDitherImage() && png_get_sBIT(png_ptr, info_ptr, &sig_bit)) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000488#if 0
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000489 SkDebugf("----- sBIT %d %d %d %d\n", sig_bit->red, sig_bit->green,
490 sig_bit->blue, sig_bit->alpha);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000491#endif
492 // 0 seems to indicate no information available
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000493 if (pos_le(sig_bit->red, SK_R16_BITS) &&
494 pos_le(sig_bit->green, SK_G16_BITS) &&
495 pos_le(sig_bit->blue, SK_B16_BITS)) {
scroggo@google.com8d239242013-10-01 17:27:15 +0000496 this->setDitherImage(false);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000497 }
498 }
499
500 if (colorType == PNG_COLOR_TYPE_PALETTE) {
501 bool paletteHasAlpha = hasTransparencyInPalette(png_ptr, info_ptr);
502 *configp = this->getPrefConfig(kIndex_SrcDepth, paletteHasAlpha);
503 // now see if we can upscale to their requested config
504 if (!canUpscalePaletteToConfig(*configp, paletteHasAlpha)) {
505 *configp = SkBitmap::kIndex8_Config;
506 }
507 } else {
508 png_color_16p transpColor = NULL;
509 int numTransp = 0;
510
511 png_get_tRNS(png_ptr, info_ptr, NULL, &numTransp, &transpColor);
512
513 bool valid = png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS);
514
515 if (valid && numTransp == 1 && transpColor != NULL) {
516 /* Compute our transparent color, which we'll match against later.
517 We don't really handle 16bit components properly here, since we
518 do our compare *after* the values have been knocked down to 8bit
519 which means we will find more matches than we should. The real
520 fix seems to be to see the actual 16bit components, do the
521 compare, and then knock it down to 8bits ourselves.
522 */
523 if (colorType & PNG_COLOR_MASK_COLOR) {
524 if (16 == bitDepth) {
525 *theTranspColorp = SkPackARGB32(0xFF, transpColor->red >> 8,
526 transpColor->green >> 8,
527 transpColor->blue >> 8);
528 } else {
halcanary@google.comfed30372013-10-04 12:46:45 +0000529 /* We apply the mask because in a very small
530 number of corrupt PNGs, (transpColor->red > 255)
531 and (bitDepth == 8), for certain versions of libpng. */
532 *theTranspColorp = SkPackARGB32(0xFF,
533 0xFF & (transpColor->red),
534 0xFF & (transpColor->green),
535 0xFF & (transpColor->blue));
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000536 }
537 } else { // gray
538 if (16 == bitDepth) {
539 *theTranspColorp = SkPackARGB32(0xFF, transpColor->gray >> 8,
540 transpColor->gray >> 8,
541 transpColor->gray >> 8);
542 } else {
halcanary@google.comfed30372013-10-04 12:46:45 +0000543 /* We apply the mask because in a very small
544 number of corrupt PNGs, (transpColor->red >
545 255) and (bitDepth == 8), for certain versions
546 of libpng. For safety we assume the same could
547 happen with a grayscale PNG. */
548 *theTranspColorp = SkPackARGB32(0xFF,
549 0xFF & (transpColor->gray),
550 0xFF & (transpColor->gray),
551 0xFF & (transpColor->gray));
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000552 }
553 }
554 }
555
556 if (valid ||
557 PNG_COLOR_TYPE_RGB_ALPHA == colorType ||
558 PNG_COLOR_TYPE_GRAY_ALPHA == colorType) {
559 *hasAlphap = true;
560 }
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000561
562 SrcDepth srcDepth = k32Bit_SrcDepth;
563 if (PNG_COLOR_TYPE_GRAY == colorType) {
564 srcDepth = k8BitGray_SrcDepth;
scroggo@google.com8e2ef012013-07-18 20:14:45 +0000565 // Remove this assert, which fails on desk_pokemonwiki.skp
566 //SkASSERT(!*hasAlphap);
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000567 }
568
569 *configp = this->getPrefConfig(srcDepth, *hasAlphap);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000570 // now match the request against our capabilities
571 if (*hasAlphap) {
572 if (*configp != SkBitmap::kARGB_4444_Config) {
573 *configp = SkBitmap::kARGB_8888_Config;
574 }
575 } else {
scroggo@google.com354fd972013-10-02 15:50:19 +0000576 if (SkBitmap::kA8_Config == *configp) {
577 if (k8BitGray_SrcDepth != srcDepth) {
578 // Converting a non grayscale image to A8 is not currently supported.
579 *configp = SkBitmap::kARGB_8888_Config;
580 }
581 } else if (*configp != SkBitmap::kRGB_565_Config &&
582 *configp != SkBitmap::kARGB_4444_Config) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000583 *configp = SkBitmap::kARGB_8888_Config;
584 }
585 }
586 }
587
588 // sanity check for size
589 {
590 Sk64 size;
591 size.setMul(origWidth, origHeight);
592 if (size.isNeg() || !size.is32()) {
593 return false;
594 }
595 // now check that if we are 4-bytes per pixel, we also don't overflow
596 if (size.get32() > (0x7FFFFFFF >> 2)) {
597 return false;
598 }
599 }
600
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000601 if (!this->chooseFromOneChoice(*configp, origWidth, origHeight)) {
602 return false;
603 }
604
605 // If the image has alpha and the decoder wants unpremultiplied
606 // colors, the only supported config is 8888.
607 if (this->getRequireUnpremultipliedColors() && *hasAlphap) {
608 *configp = SkBitmap::kARGB_8888_Config;
609 }
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000610
611 if (fImageIndex != NULL) {
612 if (SkBitmap::kNo_Config == fImageIndex->fConfig) {
613 // This is the first time for this subset decode. From now on,
614 // all decodes must be in the same config.
615 fImageIndex->fConfig = *configp;
616 } else if (fImageIndex->fConfig != *configp) {
617 // Requesting a different config for a subsequent decode is not
618 // supported. Report failure before we make changes to png_ptr.
619 return false;
620 }
621 }
622
623 bool convertGrayToRGB = PNG_COLOR_TYPE_GRAY == colorType
624 && *configp != SkBitmap::kA8_Config;
625
626 // Unless the user is requesting A8, convert a grayscale image into RGB.
627 // GRAY_ALPHA will always be converted to RGB
628 if (convertGrayToRGB || colorType == PNG_COLOR_TYPE_GRAY_ALPHA) {
629 png_set_gray_to_rgb(png_ptr);
630 }
631
632 // Add filler (or alpha) byte (after each RGB triplet) if necessary.
633 if (colorType == PNG_COLOR_TYPE_RGB || convertGrayToRGB) {
634 png_set_filler(png_ptr, 0xff, PNG_FILLER_AFTER);
635 }
636
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000637 return true;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000638}
639
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000640typedef uint32_t (*PackColorProc)(U8CPU a, U8CPU r, U8CPU g, U8CPU b);
641
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000642bool SkPNGImageDecoder::decodePalette(png_structp png_ptr, png_infop info_ptr,
643 bool *hasAlphap, bool *reallyHasAlphap,
644 SkColorTable **colorTablep) {
645 int numPalette;
646 png_colorp palette;
647 png_bytep trans;
648 int numTrans;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000649
650 png_get_PLTE(png_ptr, info_ptr, &palette, &numPalette);
651
652 /* BUGGY IMAGE WORKAROUND
653
654 We hit some images (e.g. fruit_.png) who contain bytes that are == colortable_count
655 which is a problem since we use the byte as an index. To work around this we grow
656 the colortable by 1 (if its < 256) and duplicate the last color into that slot.
657 */
658 int colorCount = numPalette + (numPalette < 256);
reed@google.com0a6151d2013-10-10 14:44:56 +0000659 SkPMColor colorStorage[256]; // worst-case storage
660 SkPMColor* colorPtr = colorStorage;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000661
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000662 if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
663 png_get_tRNS(png_ptr, info_ptr, &trans, &numTrans, NULL);
664 *hasAlphap = (numTrans > 0);
665 } else {
666 numTrans = 0;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000667 }
reed@google.com0a6151d2013-10-10 14:44:56 +0000668
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000669 // check for bad images that might make us crash
670 if (numTrans > numPalette) {
671 numTrans = numPalette;
672 }
673
674 int index = 0;
675 int transLessThanFF = 0;
676
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000677 // Choose which function to use to create the color table. If the final destination's
678 // config is unpremultiplied, the color table will store unpremultiplied colors.
679 PackColorProc proc;
680 if (this->getRequireUnpremultipliedColors()) {
681 proc = &SkPackARGB32NoCheck;
682 } else {
683 proc = &SkPreMultiplyARGB;
684 }
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000685 for (; index < numTrans; index++) {
686 transLessThanFF |= (int)*trans - 0xFF;
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000687 *colorPtr++ = proc(*trans++, palette->red, palette->green, palette->blue);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000688 palette++;
689 }
reed@google.com0a6151d2013-10-10 14:44:56 +0000690 bool reallyHasAlpha = (transLessThanFF < 0);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000691
692 for (; index < numPalette; index++) {
693 *colorPtr++ = SkPackARGB32(0xFF, palette->red, palette->green, palette->blue);
694 palette++;
695 }
696
697 // see BUGGY IMAGE WORKAROUND comment above
698 if (numPalette < 256) {
699 *colorPtr = colorPtr[-1];
700 }
reed@google.com0a6151d2013-10-10 14:44:56 +0000701
702 SkAlphaType alphaType = kOpaque_SkAlphaType;
703 if (reallyHasAlpha) {
704 if (this->getRequireUnpremultipliedColors()) {
705 alphaType = kUnpremul_SkAlphaType;
706 } else {
707 alphaType = kPremul_SkAlphaType;
708 }
709 }
710
711 *colorTablep = SkNEW_ARGS(SkColorTable,
712 (colorStorage, colorCount, alphaType));
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000713 *reallyHasAlphap = reallyHasAlpha;
714 return true;
715}
716
717#ifdef SK_BUILD_FOR_ANDROID
718
scroggo@google.comb5571b32013-09-25 21:34:24 +0000719bool SkPNGImageDecoder::onBuildTileIndex(SkStreamRewindable* sk_stream, int *width, int *height) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000720 png_structp png_ptr;
721 png_infop info_ptr;
722
723 if (!onDecodeInit(sk_stream, &png_ptr, &info_ptr)) {
724 return false;
725 }
726
727 if (setjmp(png_jmpbuf(png_ptr)) != 0) {
728 png_destroy_read_struct(&png_ptr, &info_ptr, png_infopp_NULL);
729 return false;
730 }
731
732 png_uint_32 origWidth, origHeight;
733 int bitDepth, colorType;
734 png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
735 &colorType, int_p_NULL, int_p_NULL, int_p_NULL);
736
737 *width = origWidth;
738 *height = origHeight;
739
740 png_build_index(png_ptr);
741
742 if (fImageIndex) {
743 SkDELETE(fImageIndex);
744 }
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000745 fImageIndex = SkNEW_ARGS(SkPNGImageIndex, (sk_stream, png_ptr, info_ptr));
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000746
747 return true;
748}
749
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000750bool SkPNGImageDecoder::onDecodeSubset(SkBitmap* bm, const SkIRect& region) {
751 if (NULL == fImageIndex) {
752 return false;
753 }
754
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000755 png_structp png_ptr = fImageIndex->fPng_ptr;
756 png_infop info_ptr = fImageIndex->fInfo_ptr;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000757 if (setjmp(png_jmpbuf(png_ptr))) {
758 return false;
759 }
760
761 png_uint_32 origWidth, origHeight;
762 int bitDepth, colorType, interlaceType;
763 png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
764 &colorType, &interlaceType, int_p_NULL, int_p_NULL);
765
766 SkIRect rect = SkIRect::MakeWH(origWidth, origHeight);
767
768 if (!rect.intersect(region)) {
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000769 // If the requested region is entirely outside the image, just
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000770 // returns false
771 return false;
772 }
773
774 SkBitmap::Config config;
775 bool hasAlpha = false;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000776 SkPMColor theTranspColor = 0; // 0 tells us not to try to match
777
scroggo@google.com8d239242013-10-01 17:27:15 +0000778 if (!this->getBitmapConfig(png_ptr, info_ptr, &config, &hasAlpha, &theTranspColor)) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000779 return false;
780 }
781
782 const int sampleSize = this->getSampleSize();
783 SkScaledBitmapSampler sampler(origWidth, rect.height(), sampleSize);
784
785 SkBitmap decodedBitmap;
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000786 decodedBitmap.setConfig(config, sampler.scaledWidth(), sampler.scaledHeight());
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000787
788 // from here down we are concerned with colortables and pixels
789
790 // we track if we actually see a non-opaque pixels, since sometimes a PNG sets its colortype
791 // to |= PNG_COLOR_MASK_ALPHA, but all of its pixels are in fact opaque. We care, since we
792 // draw lots faster if we can flag the bitmap has being opaque
793 bool reallyHasAlpha = false;
794 SkColorTable* colorTable = NULL;
795
796 if (colorType == PNG_COLOR_TYPE_PALETTE) {
797 decodePalette(png_ptr, info_ptr, &hasAlpha, &reallyHasAlpha, &colorTable);
798 }
799
800 SkAutoUnref aur(colorTable);
801
802 // Check ahead of time if the swap(dest, src) is possible.
803 // If yes, then we will stick to AllocPixelRef since it's cheaper with the swap happening.
804 // If no, then we will use alloc to allocate pixels to prevent garbage collection.
805 int w = rect.width() / sampleSize;
806 int h = rect.height() / sampleSize;
807 const bool swapOnly = (rect == region) && (w == decodedBitmap.width()) &&
808 (h == decodedBitmap.height()) && bm->isNull();
809 const bool needColorTable = SkBitmap::kIndex8_Config == config;
810 if (swapOnly) {
811 if (!this->allocPixelRef(&decodedBitmap, needColorTable ? colorTable : NULL)) {
812 return false;
813 }
814 } else {
815 if (!decodedBitmap.allocPixels(NULL, needColorTable ? colorTable : NULL)) {
816 return false;
817 }
818 }
819 SkAutoLockPixels alp(decodedBitmap);
820
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000821 /* Turn on interlace handling. REQUIRED if you are not using
822 * png_read_image(). To see how to handle interlacing passes,
823 * see the png_read_row() method below:
824 */
825 const int number_passes = (interlaceType != PNG_INTERLACE_NONE) ?
826 png_set_interlace_handling(png_ptr) : 1;
827
828 /* Optional call to gamma correct and add the background to the palette
829 * and update info structure. REQUIRED if you are expecting libpng to
830 * update the palette for you (ie you selected such a transform above).
831 */
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000832
833 // Direct access to png_ptr fields is deprecated in libpng > 1.2.
834#if defined(PNG_1_0_X) || defined (PNG_1_2_X)
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000835 png_ptr->pass = 0;
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000836#else
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000837 // FIXME: This sets pass as desired, but also sets iwidth. Is that ok?
838 png_set_interlaced_pass(png_ptr, 0);
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000839#endif
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000840 png_read_update_info(png_ptr, info_ptr);
841
842 int actualTop = rect.fTop;
843
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000844 if ((SkBitmap::kA8_Config == config || SkBitmap::kIndex8_Config == config)
845 && 1 == sampleSize) {
846 // A8 is only allowed if the original was GRAY.
847 SkASSERT(config != SkBitmap::kA8_Config
848 || PNG_COLOR_TYPE_GRAY == colorType);
849
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000850 for (int i = 0; i < number_passes; i++) {
851 png_configure_decoder(png_ptr, &actualTop, i);
852 for (int j = 0; j < rect.fTop - actualTop; j++) {
853 uint8_t* bmRow = decodedBitmap.getAddr8(0, 0);
854 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
855 }
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000856 png_uint_32 bitmapHeight = (png_uint_32) decodedBitmap.height();
857 for (png_uint_32 y = 0; y < bitmapHeight; y++) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000858 uint8_t* bmRow = decodedBitmap.getAddr8(0, y);
859 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
860 }
861 }
862 } else {
863 SkScaledBitmapSampler::SrcConfig sc;
864 int srcBytesPerPixel = 4;
865
866 if (colorTable != NULL) {
867 sc = SkScaledBitmapSampler::kIndex;
868 srcBytesPerPixel = 1;
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000869 } else if (SkBitmap::kA8_Config == config) {
870 // A8 is only allowed if the original was GRAY.
871 SkASSERT(PNG_COLOR_TYPE_GRAY == colorType);
872 sc = SkScaledBitmapSampler::kGray;
873 srcBytesPerPixel = 1;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000874 } else if (hasAlpha) {
875 sc = SkScaledBitmapSampler::kRGBA;
876 } else {
877 sc = SkScaledBitmapSampler::kRGBX;
878 }
879
880 /* We have to pass the colortable explicitly, since we may have one
881 even if our decodedBitmap doesn't, due to the request that we
882 upscale png's palette to a direct model
883 */
884 SkAutoLockColors ctLock(colorTable);
scroggo@google.com8d239242013-10-01 17:27:15 +0000885 if (!sampler.begin(&decodedBitmap, sc, *this, ctLock.colors())) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000886 return false;
887 }
888 const int height = decodedBitmap.height();
889
890 if (number_passes > 1) {
891 SkAutoMalloc storage(origWidth * origHeight * srcBytesPerPixel);
892 uint8_t* base = (uint8_t*)storage.get();
893 size_t rb = origWidth * srcBytesPerPixel;
894
895 for (int i = 0; i < number_passes; i++) {
896 png_configure_decoder(png_ptr, &actualTop, i);
897 for (int j = 0; j < rect.fTop - actualTop; j++) {
898 uint8_t* bmRow = (uint8_t*)decodedBitmap.getPixels();
899 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
900 }
901 uint8_t* row = base;
902 for (int32_t y = 0; y < rect.height(); y++) {
903 uint8_t* bmRow = row;
904 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
905 row += rb;
906 }
907 }
908 // now sample it
909 base += sampler.srcY0() * rb;
910 for (int y = 0; y < height; y++) {
911 reallyHasAlpha |= sampler.next(base);
912 base += sampler.srcDY() * rb;
913 }
914 } else {
915 SkAutoMalloc storage(origWidth * srcBytesPerPixel);
916 uint8_t* srcRow = (uint8_t*)storage.get();
917
918 png_configure_decoder(png_ptr, &actualTop, 0);
919 skip_src_rows(png_ptr, srcRow, sampler.srcY0());
920
921 for (int i = 0; i < rect.fTop - actualTop; i++) {
922 uint8_t* bmRow = (uint8_t*)decodedBitmap.getPixels();
923 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
924 }
925 for (int y = 0; y < height; y++) {
926 uint8_t* tmp = srcRow;
927 png_read_rows(png_ptr, &tmp, png_bytepp_NULL, 1);
928 reallyHasAlpha |= sampler.next(srcRow);
929 if (y < height - 1) {
930 skip_src_rows(png_ptr, srcRow, sampler.srcDY() - 1);
931 }
932 }
933 }
934 }
935
936 if (0 != theTranspColor) {
937 reallyHasAlpha |= substituteTranspColor(&decodedBitmap, theTranspColor);
938 }
commit-bot@chromium.org546f70c2013-10-03 17:13:38 +0000939 if (SkBitmap::kA8_Config == decodedBitmap.config()) {
940 reallyHasAlpha = true;
941 }
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000942 decodedBitmap.setIsOpaque(!reallyHasAlpha);
943
944 if (swapOnly) {
945 bm->swap(decodedBitmap);
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000946 return true;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000947 }
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000948 return this->cropBitmap(bm, &decodedBitmap, sampleSize, region.x(), region.y(),
949 region.width(), region.height(), 0, rect.y());
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000950}
951#endif
952
reed@android.com8a1c16f2008-12-17 15:59:43 +0000953///////////////////////////////////////////////////////////////////////////////
954
reed@android.com8a1c16f2008-12-17 15:59:43 +0000955#include "SkColorPriv.h"
956#include "SkUnPreMultiply.h"
957
958static void sk_write_fn(png_structp png_ptr, png_bytep data, png_size_t len) {
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000959 SkWStream* sk_stream = (SkWStream*)png_get_io_ptr(png_ptr);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000960 if (!sk_stream->write(data, len)) {
961 png_error(png_ptr, "sk_write_fn Error!");
962 }
963}
964
reed@android.com8a1c16f2008-12-17 15:59:43 +0000965static transform_scanline_proc choose_proc(SkBitmap::Config config,
966 bool hasAlpha) {
967 // we don't care about search on alpha if we're kIndex8, since only the
968 // colortable packing cares about that distinction, not the pixels
969 if (SkBitmap::kIndex8_Config == config) {
970 hasAlpha = false; // we store false in the table entries for kIndex8
971 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000972
reed@android.com8a1c16f2008-12-17 15:59:43 +0000973 static const struct {
974 SkBitmap::Config fConfig;
975 bool fHasAlpha;
976 transform_scanline_proc fProc;
977 } gMap[] = {
978 { SkBitmap::kRGB_565_Config, false, transform_scanline_565 },
979 { SkBitmap::kARGB_8888_Config, false, transform_scanline_888 },
980 { SkBitmap::kARGB_8888_Config, true, transform_scanline_8888 },
981 { SkBitmap::kARGB_4444_Config, false, transform_scanline_444 },
982 { SkBitmap::kARGB_4444_Config, true, transform_scanline_4444 },
epoger@google.com4ce738b2012-11-16 18:44:18 +0000983 { SkBitmap::kIndex8_Config, false, transform_scanline_memcpy },
reed@android.com8a1c16f2008-12-17 15:59:43 +0000984 };
985
986 for (int i = SK_ARRAY_COUNT(gMap) - 1; i >= 0; --i) {
987 if (gMap[i].fConfig == config && gMap[i].fHasAlpha == hasAlpha) {
988 return gMap[i].fProc;
989 }
990 }
991 sk_throw();
992 return NULL;
993}
994
995// return the minimum legal bitdepth (by png standards) for this many colortable
996// entries. SkBitmap always stores in 8bits per pixel, but for colorcount <= 16,
997// we can use fewer bits per in png
998static int computeBitDepth(int colorCount) {
999#if 0
1000 int bits = SkNextLog2(colorCount);
1001 SkASSERT(bits >= 1 && bits <= 8);
1002 // now we need bits itself to be a power of 2 (e.g. 1, 2, 4, 8)
1003 return SkNextPow2(bits);
1004#else
1005 // for the moment, we don't know how to pack bitdepth < 8
1006 return 8;
1007#endif
1008}
1009
1010/* Pack palette[] with the corresponding colors, and if hasAlpha is true, also
1011 pack trans[] and return the number of trans[] entries written. If hasAlpha
1012 is false, the return value will always be 0.
rmistry@google.comd6176b02012-08-23 18:14:13 +00001013
reed@android.com8a1c16f2008-12-17 15:59:43 +00001014 Note: this routine takes care of unpremultiplying the RGB values when we
1015 have alpha in the colortable, since png doesn't support premul colors
1016*/
reed@android.com6f252972009-01-14 16:46:16 +00001017static inline int pack_palette(SkColorTable* ctable,
reed@android.comb50a60c2009-01-14 17:51:08 +00001018 png_color* SK_RESTRICT palette,
1019 png_byte* SK_RESTRICT trans, bool hasAlpha) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001020 SkAutoLockColors alc(ctable);
1021 const SkPMColor* SK_RESTRICT colors = alc.colors();
1022 const int ctCount = ctable->count();
1023 int i, num_trans = 0;
1024
1025 if (hasAlpha) {
1026 /* first see if we have some number of fully opaque at the end of the
1027 ctable. PNG allows num_trans < num_palette, but all of the trans
1028 entries must come first in the palette. If I was smarter, I'd
1029 reorder the indices and ctable so that all non-opaque colors came
1030 first in the palette. But, since that would slow down the encode,
1031 I'm leaving the indices and ctable order as is, and just looking
1032 at the tail of the ctable for opaqueness.
1033 */
1034 num_trans = ctCount;
1035 for (i = ctCount - 1; i >= 0; --i) {
1036 if (SkGetPackedA32(colors[i]) != 0xFF) {
1037 break;
1038 }
1039 num_trans -= 1;
1040 }
rmistry@google.comd6176b02012-08-23 18:14:13 +00001041
reed@android.com8a1c16f2008-12-17 15:59:43 +00001042 const SkUnPreMultiply::Scale* SK_RESTRICT table =
1043 SkUnPreMultiply::GetScaleTable();
1044
1045 for (i = 0; i < num_trans; i++) {
1046 const SkPMColor c = *colors++;
1047 const unsigned a = SkGetPackedA32(c);
1048 const SkUnPreMultiply::Scale s = table[a];
1049 trans[i] = a;
1050 palette[i].red = SkUnPreMultiply::ApplyScale(s, SkGetPackedR32(c));
1051 palette[i].green = SkUnPreMultiply::ApplyScale(s,SkGetPackedG32(c));
1052 palette[i].blue = SkUnPreMultiply::ApplyScale(s, SkGetPackedB32(c));
rmistry@google.comd6176b02012-08-23 18:14:13 +00001053 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001054 // now fall out of this if-block to use common code for the trailing
1055 // opaque entries
1056 }
rmistry@google.comd6176b02012-08-23 18:14:13 +00001057
reed@android.com8a1c16f2008-12-17 15:59:43 +00001058 // these (remaining) entries are opaque
1059 for (i = num_trans; i < ctCount; i++) {
1060 SkPMColor c = *colors++;
1061 palette[i].red = SkGetPackedR32(c);
1062 palette[i].green = SkGetPackedG32(c);
1063 palette[i].blue = SkGetPackedB32(c);
1064 }
1065 return num_trans;
1066}
1067
1068class SkPNGImageEncoder : public SkImageEncoder {
1069protected:
commit-bot@chromium.orga936e372013-03-14 14:42:18 +00001070 virtual bool onEncode(SkWStream* stream, const SkBitmap& bm, int quality) SK_OVERRIDE;
tomhudson@google.com5c210c72011-07-28 21:06:40 +00001071private:
1072 bool doEncode(SkWStream* stream, const SkBitmap& bm,
1073 const bool& hasAlpha, int colorType,
1074 int bitDepth, SkBitmap::Config config,
1075 png_color_8& sig_bit);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +00001076
1077 typedef SkImageEncoder INHERITED;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001078};
1079
1080bool SkPNGImageEncoder::onEncode(SkWStream* stream, const SkBitmap& bitmap,
1081 int /*quality*/) {
1082 SkBitmap::Config config = bitmap.getConfig();
1083
1084 const bool hasAlpha = !bitmap.isOpaque();
1085 int colorType = PNG_COLOR_MASK_COLOR;
1086 int bitDepth = 8; // default for color
1087 png_color_8 sig_bit;
1088
1089 switch (config) {
1090 case SkBitmap::kIndex8_Config:
1091 colorType |= PNG_COLOR_MASK_PALETTE;
1092 // fall through to the ARGB_8888 case
1093 case SkBitmap::kARGB_8888_Config:
1094 sig_bit.red = 8;
1095 sig_bit.green = 8;
1096 sig_bit.blue = 8;
1097 sig_bit.alpha = 8;
1098 break;
1099 case SkBitmap::kARGB_4444_Config:
1100 sig_bit.red = 4;
1101 sig_bit.green = 4;
1102 sig_bit.blue = 4;
1103 sig_bit.alpha = 4;
1104 break;
1105 case SkBitmap::kRGB_565_Config:
1106 sig_bit.red = 5;
1107 sig_bit.green = 6;
1108 sig_bit.blue = 5;
1109 sig_bit.alpha = 0;
1110 break;
1111 default:
1112 return false;
1113 }
rmistry@google.comd6176b02012-08-23 18:14:13 +00001114
reed@android.com8a1c16f2008-12-17 15:59:43 +00001115 if (hasAlpha) {
1116 // don't specify alpha if we're a palette, even if our ctable has alpha
1117 if (!(colorType & PNG_COLOR_MASK_PALETTE)) {
1118 colorType |= PNG_COLOR_MASK_ALPHA;
1119 }
1120 } else {
1121 sig_bit.alpha = 0;
1122 }
rmistry@google.comd6176b02012-08-23 18:14:13 +00001123
reed@android.com8a1c16f2008-12-17 15:59:43 +00001124 SkAutoLockPixels alp(bitmap);
1125 // readyToDraw checks for pixels (and colortable if that is required)
1126 if (!bitmap.readyToDraw()) {
1127 return false;
1128 }
1129
1130 // we must do this after we have locked the pixels
1131 SkColorTable* ctable = bitmap.getColorTable();
1132 if (NULL != ctable) {
1133 if (ctable->count() == 0) {
1134 return false;
1135 }
1136 // check if we can store in fewer than 8 bits
1137 bitDepth = computeBitDepth(ctable->count());
1138 }
1139
tomhudson@google.com5c210c72011-07-28 21:06:40 +00001140 return doEncode(stream, bitmap, hasAlpha, colorType,
1141 bitDepth, config, sig_bit);
1142}
1143
1144bool SkPNGImageEncoder::doEncode(SkWStream* stream, const SkBitmap& bitmap,
1145 const bool& hasAlpha, int colorType,
1146 int bitDepth, SkBitmap::Config config,
1147 png_color_8& sig_bit) {
1148
reed@android.com8a1c16f2008-12-17 15:59:43 +00001149 png_structp png_ptr;
1150 png_infop info_ptr;
1151
1152 png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, sk_error_fn,
1153 NULL);
1154 if (NULL == png_ptr) {
1155 return false;
1156 }
1157
1158 info_ptr = png_create_info_struct(png_ptr);
1159 if (NULL == info_ptr) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +00001160 png_destroy_write_struct(&png_ptr, png_infopp_NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001161 return false;
1162 }
1163
1164 /* Set error handling. REQUIRED if you aren't supplying your own
1165 * error handling functions in the png_create_write_struct() call.
1166 */
1167 if (setjmp(png_jmpbuf(png_ptr))) {
1168 png_destroy_write_struct(&png_ptr, &info_ptr);
1169 return false;
1170 }
1171
commit-bot@chromium.orga936e372013-03-14 14:42:18 +00001172 png_set_write_fn(png_ptr, (void*)stream, sk_write_fn, png_flush_ptr_NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001173
1174 /* Set the image information here. Width and height are up to 2^31,
1175 * bit_depth is one of 1, 2, 4, 8, or 16, but valid values also depend on
1176 * the color_type selected. color_type is one of PNG_COLOR_TYPE_GRAY,
1177 * PNG_COLOR_TYPE_GRAY_ALPHA, PNG_COLOR_TYPE_PALETTE, PNG_COLOR_TYPE_RGB,
1178 * or PNG_COLOR_TYPE_RGB_ALPHA. interlace is either PNG_INTERLACE_NONE or
1179 * PNG_INTERLACE_ADAM7, and the compression_type and filter_type MUST
1180 * currently be PNG_COMPRESSION_TYPE_BASE and PNG_FILTER_TYPE_BASE. REQUIRED
1181 */
1182
1183 png_set_IHDR(png_ptr, info_ptr, bitmap.width(), bitmap.height(),
1184 bitDepth, colorType,
1185 PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE,
1186 PNG_FILTER_TYPE_BASE);
1187
reed@android.com61898772009-07-07 19:38:01 +00001188 // set our colortable/trans arrays if needed
1189 png_color paletteColors[256];
1190 png_byte trans[256];
1191 if (SkBitmap::kIndex8_Config == config) {
1192 SkColorTable* ct = bitmap.getColorTable();
1193 int numTrans = pack_palette(ct, paletteColors, trans, hasAlpha);
1194 png_set_PLTE(png_ptr, info_ptr, paletteColors, ct->count());
1195 if (numTrans > 0) {
1196 png_set_tRNS(png_ptr, info_ptr, trans, numTrans, NULL);
1197 }
1198 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001199
1200 png_set_sBIT(png_ptr, info_ptr, &sig_bit);
1201 png_write_info(png_ptr, info_ptr);
1202
1203 const char* srcImage = (const char*)bitmap.getPixels();
1204 SkAutoSMalloc<1024> rowStorage(bitmap.width() << 2);
1205 char* storage = (char*)rowStorage.get();
1206 transform_scanline_proc proc = choose_proc(config, hasAlpha);
1207
1208 for (int y = 0; y < bitmap.height(); y++) {
1209 png_bytep row_ptr = (png_bytep)storage;
1210 proc(srcImage, bitmap.width(), storage);
1211 png_write_rows(png_ptr, &row_ptr, 1);
1212 srcImage += bitmap.rowBytes();
1213 }
1214
1215 png_write_end(png_ptr, info_ptr);
1216
1217 /* clean up after the write, and free any memory allocated */
1218 png_destroy_write_struct(&png_ptr, &info_ptr);
1219 return true;
1220}
1221
reed@android.com00bf85a2009-01-22 13:04:56 +00001222///////////////////////////////////////////////////////////////////////////////
robertphillips@google.comec51cb82012-03-23 18:13:47 +00001223DEFINE_DECODER_CREATOR(PNGImageDecoder);
1224DEFINE_ENCODER_CREATOR(PNGImageEncoder);
1225///////////////////////////////////////////////////////////////////////////////
reed@android.com00bf85a2009-01-22 13:04:56 +00001226
scroggo@google.comb5571b32013-09-25 21:34:24 +00001227static bool is_png(SkStreamRewindable* stream) {
reed@android.com00bf85a2009-01-22 13:04:56 +00001228 char buf[PNG_BYTES_TO_CHECK];
1229 if (stream->read(buf, PNG_BYTES_TO_CHECK) == PNG_BYTES_TO_CHECK &&
1230 !png_sig_cmp((png_bytep) buf, (png_size_t)0, PNG_BYTES_TO_CHECK)) {
scroggo@google.com39edf4c2013-04-25 17:33:51 +00001231 return true;
1232 }
1233 return false;
1234}
1235
scroggo@google.comb5571b32013-09-25 21:34:24 +00001236SkImageDecoder* sk_libpng_dfactory(SkStreamRewindable* stream) {
scroggo@google.com39edf4c2013-04-25 17:33:51 +00001237 if (is_png(stream)) {
reed@android.com00bf85a2009-01-22 13:04:56 +00001238 return SkNEW(SkPNGImageDecoder);
1239 }
1240 return NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001241}
1242
scroggo@google.comb5571b32013-09-25 21:34:24 +00001243static SkImageDecoder::Format get_format_png(SkStreamRewindable* stream) {
scroggo@google.com39edf4c2013-04-25 17:33:51 +00001244 if (is_png(stream)) {
1245 return SkImageDecoder::kPNG_Format;
1246 }
1247 return SkImageDecoder::kUnknown_Format;
1248}
1249
reed@android.comdfee5792010-04-15 14:24:50 +00001250SkImageEncoder* sk_libpng_efactory(SkImageEncoder::Type t) {
reed@android.com00bf85a2009-01-22 13:04:56 +00001251 return (SkImageEncoder::kPNG_Type == t) ? SkNEW(SkPNGImageEncoder) : NULL;
1252}
1253
mtklein@google.combd6343b2013-09-04 17:20:18 +00001254static SkImageDecoder_DecodeReg gDReg(sk_libpng_dfactory);
1255static SkImageDecoder_FormatReg gFormatReg(get_format_png);
1256static SkImageEncoder_EncodeReg gEReg(sk_libpng_efactory);