blob: 4e4106aede85f8266c3b9ffd234e025f9f6a0e02 [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;
651 bool reallyHasAlpha = false;
652 SkColorTable* colorTable = NULL;
653
654 png_get_PLTE(png_ptr, info_ptr, &palette, &numPalette);
655
656 /* BUGGY IMAGE WORKAROUND
657
658 We hit some images (e.g. fruit_.png) who contain bytes that are == colortable_count
659 which is a problem since we use the byte as an index. To work around this we grow
660 the colortable by 1 (if its < 256) and duplicate the last color into that slot.
661 */
662 int colorCount = numPalette + (numPalette < 256);
663
664 colorTable = SkNEW_ARGS(SkColorTable, (colorCount));
665
666 SkPMColor* colorPtr = colorTable->lockColors();
667 if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
668 png_get_tRNS(png_ptr, info_ptr, &trans, &numTrans, NULL);
669 *hasAlphap = (numTrans > 0);
670 } else {
671 numTrans = 0;
672 colorTable->setFlags(colorTable->getFlags() | SkColorTable::kColorsAreOpaque_Flag);
673 }
674 // check for bad images that might make us crash
675 if (numTrans > numPalette) {
676 numTrans = numPalette;
677 }
678
679 int index = 0;
680 int transLessThanFF = 0;
681
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000682 // Choose which function to use to create the color table. If the final destination's
683 // config is unpremultiplied, the color table will store unpremultiplied colors.
684 PackColorProc proc;
685 if (this->getRequireUnpremultipliedColors()) {
686 proc = &SkPackARGB32NoCheck;
687 } else {
688 proc = &SkPreMultiplyARGB;
689 }
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000690 for (; index < numTrans; index++) {
691 transLessThanFF |= (int)*trans - 0xFF;
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000692 *colorPtr++ = proc(*trans++, palette->red, palette->green, palette->blue);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000693 palette++;
694 }
695 reallyHasAlpha |= (transLessThanFF < 0);
696
697 for (; index < numPalette; index++) {
698 *colorPtr++ = SkPackARGB32(0xFF, palette->red, palette->green, palette->blue);
699 palette++;
700 }
701
702 // see BUGGY IMAGE WORKAROUND comment above
703 if (numPalette < 256) {
704 *colorPtr = colorPtr[-1];
705 }
706 colorTable->unlockColors(true);
707 *colorTablep = colorTable;
708 *reallyHasAlphap = reallyHasAlpha;
709 return true;
710}
711
712#ifdef SK_BUILD_FOR_ANDROID
713
scroggo@google.comb5571b32013-09-25 21:34:24 +0000714bool SkPNGImageDecoder::onBuildTileIndex(SkStreamRewindable* sk_stream, int *width, int *height) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000715 png_structp png_ptr;
716 png_infop info_ptr;
717
718 if (!onDecodeInit(sk_stream, &png_ptr, &info_ptr)) {
719 return false;
720 }
721
722 if (setjmp(png_jmpbuf(png_ptr)) != 0) {
723 png_destroy_read_struct(&png_ptr, &info_ptr, png_infopp_NULL);
724 return false;
725 }
726
727 png_uint_32 origWidth, origHeight;
728 int bitDepth, colorType;
729 png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
730 &colorType, int_p_NULL, int_p_NULL, int_p_NULL);
731
732 *width = origWidth;
733 *height = origHeight;
734
735 png_build_index(png_ptr);
736
737 if (fImageIndex) {
738 SkDELETE(fImageIndex);
739 }
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000740 fImageIndex = SkNEW_ARGS(SkPNGImageIndex, (sk_stream, png_ptr, info_ptr));
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000741
742 return true;
743}
744
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000745bool SkPNGImageDecoder::onDecodeSubset(SkBitmap* bm, const SkIRect& region) {
746 if (NULL == fImageIndex) {
747 return false;
748 }
749
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000750 png_structp png_ptr = fImageIndex->fPng_ptr;
751 png_infop info_ptr = fImageIndex->fInfo_ptr;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000752 if (setjmp(png_jmpbuf(png_ptr))) {
753 return false;
754 }
755
756 png_uint_32 origWidth, origHeight;
757 int bitDepth, colorType, interlaceType;
758 png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
759 &colorType, &interlaceType, int_p_NULL, int_p_NULL);
760
761 SkIRect rect = SkIRect::MakeWH(origWidth, origHeight);
762
763 if (!rect.intersect(region)) {
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000764 // If the requested region is entirely outside the image, just
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000765 // returns false
766 return false;
767 }
768
769 SkBitmap::Config config;
770 bool hasAlpha = false;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000771 SkPMColor theTranspColor = 0; // 0 tells us not to try to match
772
scroggo@google.com8d239242013-10-01 17:27:15 +0000773 if (!this->getBitmapConfig(png_ptr, info_ptr, &config, &hasAlpha, &theTranspColor)) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000774 return false;
775 }
776
777 const int sampleSize = this->getSampleSize();
778 SkScaledBitmapSampler sampler(origWidth, rect.height(), sampleSize);
779
780 SkBitmap decodedBitmap;
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000781 decodedBitmap.setConfig(config, sampler.scaledWidth(), sampler.scaledHeight());
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000782
783 // from here down we are concerned with colortables and pixels
784
785 // we track if we actually see a non-opaque pixels, since sometimes a PNG sets its colortype
786 // to |= PNG_COLOR_MASK_ALPHA, but all of its pixels are in fact opaque. We care, since we
787 // draw lots faster if we can flag the bitmap has being opaque
788 bool reallyHasAlpha = false;
789 SkColorTable* colorTable = NULL;
790
791 if (colorType == PNG_COLOR_TYPE_PALETTE) {
792 decodePalette(png_ptr, info_ptr, &hasAlpha, &reallyHasAlpha, &colorTable);
793 }
794
795 SkAutoUnref aur(colorTable);
796
797 // Check ahead of time if the swap(dest, src) is possible.
798 // If yes, then we will stick to AllocPixelRef since it's cheaper with the swap happening.
799 // If no, then we will use alloc to allocate pixels to prevent garbage collection.
800 int w = rect.width() / sampleSize;
801 int h = rect.height() / sampleSize;
802 const bool swapOnly = (rect == region) && (w == decodedBitmap.width()) &&
803 (h == decodedBitmap.height()) && bm->isNull();
804 const bool needColorTable = SkBitmap::kIndex8_Config == config;
805 if (swapOnly) {
806 if (!this->allocPixelRef(&decodedBitmap, needColorTable ? colorTable : NULL)) {
807 return false;
808 }
809 } else {
810 if (!decodedBitmap.allocPixels(NULL, needColorTable ? colorTable : NULL)) {
811 return false;
812 }
813 }
814 SkAutoLockPixels alp(decodedBitmap);
815
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000816 /* Turn on interlace handling. REQUIRED if you are not using
817 * png_read_image(). To see how to handle interlacing passes,
818 * see the png_read_row() method below:
819 */
820 const int number_passes = (interlaceType != PNG_INTERLACE_NONE) ?
821 png_set_interlace_handling(png_ptr) : 1;
822
823 /* Optional call to gamma correct and add the background to the palette
824 * and update info structure. REQUIRED if you are expecting libpng to
825 * update the palette for you (ie you selected such a transform above).
826 */
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000827
828 // Direct access to png_ptr fields is deprecated in libpng > 1.2.
829#if defined(PNG_1_0_X) || defined (PNG_1_2_X)
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000830 png_ptr->pass = 0;
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000831#else
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000832 // FIXME: This sets pass as desired, but also sets iwidth. Is that ok?
833 png_set_interlaced_pass(png_ptr, 0);
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000834#endif
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000835 png_read_update_info(png_ptr, info_ptr);
836
837 int actualTop = rect.fTop;
838
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000839 if ((SkBitmap::kA8_Config == config || SkBitmap::kIndex8_Config == config)
840 && 1 == sampleSize) {
841 // A8 is only allowed if the original was GRAY.
842 SkASSERT(config != SkBitmap::kA8_Config
843 || PNG_COLOR_TYPE_GRAY == colorType);
844
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000845 for (int i = 0; i < number_passes; i++) {
846 png_configure_decoder(png_ptr, &actualTop, i);
847 for (int j = 0; j < rect.fTop - actualTop; j++) {
848 uint8_t* bmRow = decodedBitmap.getAddr8(0, 0);
849 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
850 }
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000851 png_uint_32 bitmapHeight = (png_uint_32) decodedBitmap.height();
852 for (png_uint_32 y = 0; y < bitmapHeight; y++) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000853 uint8_t* bmRow = decodedBitmap.getAddr8(0, y);
854 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
855 }
856 }
857 } else {
858 SkScaledBitmapSampler::SrcConfig sc;
859 int srcBytesPerPixel = 4;
860
861 if (colorTable != NULL) {
862 sc = SkScaledBitmapSampler::kIndex;
863 srcBytesPerPixel = 1;
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000864 } else if (SkBitmap::kA8_Config == config) {
865 // A8 is only allowed if the original was GRAY.
866 SkASSERT(PNG_COLOR_TYPE_GRAY == colorType);
867 sc = SkScaledBitmapSampler::kGray;
868 srcBytesPerPixel = 1;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000869 } else if (hasAlpha) {
870 sc = SkScaledBitmapSampler::kRGBA;
871 } else {
872 sc = SkScaledBitmapSampler::kRGBX;
873 }
874
875 /* We have to pass the colortable explicitly, since we may have one
876 even if our decodedBitmap doesn't, due to the request that we
877 upscale png's palette to a direct model
878 */
879 SkAutoLockColors ctLock(colorTable);
scroggo@google.com8d239242013-10-01 17:27:15 +0000880 if (!sampler.begin(&decodedBitmap, sc, *this, ctLock.colors())) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000881 return false;
882 }
883 const int height = decodedBitmap.height();
884
885 if (number_passes > 1) {
886 SkAutoMalloc storage(origWidth * origHeight * srcBytesPerPixel);
887 uint8_t* base = (uint8_t*)storage.get();
888 size_t rb = origWidth * srcBytesPerPixel;
889
890 for (int i = 0; i < number_passes; i++) {
891 png_configure_decoder(png_ptr, &actualTop, i);
892 for (int j = 0; j < rect.fTop - actualTop; j++) {
893 uint8_t* bmRow = (uint8_t*)decodedBitmap.getPixels();
894 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
895 }
896 uint8_t* row = base;
897 for (int32_t y = 0; y < rect.height(); y++) {
898 uint8_t* bmRow = row;
899 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
900 row += rb;
901 }
902 }
903 // now sample it
904 base += sampler.srcY0() * rb;
905 for (int y = 0; y < height; y++) {
906 reallyHasAlpha |= sampler.next(base);
907 base += sampler.srcDY() * rb;
908 }
909 } else {
910 SkAutoMalloc storage(origWidth * srcBytesPerPixel);
911 uint8_t* srcRow = (uint8_t*)storage.get();
912
913 png_configure_decoder(png_ptr, &actualTop, 0);
914 skip_src_rows(png_ptr, srcRow, sampler.srcY0());
915
916 for (int i = 0; i < rect.fTop - actualTop; i++) {
917 uint8_t* bmRow = (uint8_t*)decodedBitmap.getPixels();
918 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
919 }
920 for (int y = 0; y < height; y++) {
921 uint8_t* tmp = srcRow;
922 png_read_rows(png_ptr, &tmp, png_bytepp_NULL, 1);
923 reallyHasAlpha |= sampler.next(srcRow);
924 if (y < height - 1) {
925 skip_src_rows(png_ptr, srcRow, sampler.srcDY() - 1);
926 }
927 }
928 }
929 }
930
931 if (0 != theTranspColor) {
932 reallyHasAlpha |= substituteTranspColor(&decodedBitmap, theTranspColor);
933 }
commit-bot@chromium.org546f70c2013-10-03 17:13:38 +0000934 if (SkBitmap::kA8_Config == decodedBitmap.config()) {
935 reallyHasAlpha = true;
936 }
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000937 decodedBitmap.setIsOpaque(!reallyHasAlpha);
938
939 if (swapOnly) {
940 bm->swap(decodedBitmap);
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000941 return true;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000942 }
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000943 return this->cropBitmap(bm, &decodedBitmap, sampleSize, region.x(), region.y(),
944 region.width(), region.height(), 0, rect.y());
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000945}
946#endif
947
reed@android.com8a1c16f2008-12-17 15:59:43 +0000948///////////////////////////////////////////////////////////////////////////////
949
reed@android.com8a1c16f2008-12-17 15:59:43 +0000950#include "SkColorPriv.h"
951#include "SkUnPreMultiply.h"
952
953static void sk_write_fn(png_structp png_ptr, png_bytep data, png_size_t len) {
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000954 SkWStream* sk_stream = (SkWStream*)png_get_io_ptr(png_ptr);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000955 if (!sk_stream->write(data, len)) {
956 png_error(png_ptr, "sk_write_fn Error!");
957 }
958}
959
reed@android.com8a1c16f2008-12-17 15:59:43 +0000960static transform_scanline_proc choose_proc(SkBitmap::Config config,
961 bool hasAlpha) {
962 // we don't care about search on alpha if we're kIndex8, since only the
963 // colortable packing cares about that distinction, not the pixels
964 if (SkBitmap::kIndex8_Config == config) {
965 hasAlpha = false; // we store false in the table entries for kIndex8
966 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000967
reed@android.com8a1c16f2008-12-17 15:59:43 +0000968 static const struct {
969 SkBitmap::Config fConfig;
970 bool fHasAlpha;
971 transform_scanline_proc fProc;
972 } gMap[] = {
973 { SkBitmap::kRGB_565_Config, false, transform_scanline_565 },
974 { SkBitmap::kARGB_8888_Config, false, transform_scanline_888 },
975 { SkBitmap::kARGB_8888_Config, true, transform_scanline_8888 },
976 { SkBitmap::kARGB_4444_Config, false, transform_scanline_444 },
977 { SkBitmap::kARGB_4444_Config, true, transform_scanline_4444 },
epoger@google.com4ce738b2012-11-16 18:44:18 +0000978 { SkBitmap::kIndex8_Config, false, transform_scanline_memcpy },
reed@android.com8a1c16f2008-12-17 15:59:43 +0000979 };
980
981 for (int i = SK_ARRAY_COUNT(gMap) - 1; i >= 0; --i) {
982 if (gMap[i].fConfig == config && gMap[i].fHasAlpha == hasAlpha) {
983 return gMap[i].fProc;
984 }
985 }
986 sk_throw();
987 return NULL;
988}
989
990// return the minimum legal bitdepth (by png standards) for this many colortable
991// entries. SkBitmap always stores in 8bits per pixel, but for colorcount <= 16,
992// we can use fewer bits per in png
993static int computeBitDepth(int colorCount) {
994#if 0
995 int bits = SkNextLog2(colorCount);
996 SkASSERT(bits >= 1 && bits <= 8);
997 // now we need bits itself to be a power of 2 (e.g. 1, 2, 4, 8)
998 return SkNextPow2(bits);
999#else
1000 // for the moment, we don't know how to pack bitdepth < 8
1001 return 8;
1002#endif
1003}
1004
1005/* Pack palette[] with the corresponding colors, and if hasAlpha is true, also
1006 pack trans[] and return the number of trans[] entries written. If hasAlpha
1007 is false, the return value will always be 0.
rmistry@google.comd6176b02012-08-23 18:14:13 +00001008
reed@android.com8a1c16f2008-12-17 15:59:43 +00001009 Note: this routine takes care of unpremultiplying the RGB values when we
1010 have alpha in the colortable, since png doesn't support premul colors
1011*/
reed@android.com6f252972009-01-14 16:46:16 +00001012static inline int pack_palette(SkColorTable* ctable,
reed@android.comb50a60c2009-01-14 17:51:08 +00001013 png_color* SK_RESTRICT palette,
1014 png_byte* SK_RESTRICT trans, bool hasAlpha) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001015 SkAutoLockColors alc(ctable);
1016 const SkPMColor* SK_RESTRICT colors = alc.colors();
1017 const int ctCount = ctable->count();
1018 int i, num_trans = 0;
1019
1020 if (hasAlpha) {
1021 /* first see if we have some number of fully opaque at the end of the
1022 ctable. PNG allows num_trans < num_palette, but all of the trans
1023 entries must come first in the palette. If I was smarter, I'd
1024 reorder the indices and ctable so that all non-opaque colors came
1025 first in the palette. But, since that would slow down the encode,
1026 I'm leaving the indices and ctable order as is, and just looking
1027 at the tail of the ctable for opaqueness.
1028 */
1029 num_trans = ctCount;
1030 for (i = ctCount - 1; i >= 0; --i) {
1031 if (SkGetPackedA32(colors[i]) != 0xFF) {
1032 break;
1033 }
1034 num_trans -= 1;
1035 }
rmistry@google.comd6176b02012-08-23 18:14:13 +00001036
reed@android.com8a1c16f2008-12-17 15:59:43 +00001037 const SkUnPreMultiply::Scale* SK_RESTRICT table =
1038 SkUnPreMultiply::GetScaleTable();
1039
1040 for (i = 0; i < num_trans; i++) {
1041 const SkPMColor c = *colors++;
1042 const unsigned a = SkGetPackedA32(c);
1043 const SkUnPreMultiply::Scale s = table[a];
1044 trans[i] = a;
1045 palette[i].red = SkUnPreMultiply::ApplyScale(s, SkGetPackedR32(c));
1046 palette[i].green = SkUnPreMultiply::ApplyScale(s,SkGetPackedG32(c));
1047 palette[i].blue = SkUnPreMultiply::ApplyScale(s, SkGetPackedB32(c));
rmistry@google.comd6176b02012-08-23 18:14:13 +00001048 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001049 // now fall out of this if-block to use common code for the trailing
1050 // opaque entries
1051 }
rmistry@google.comd6176b02012-08-23 18:14:13 +00001052
reed@android.com8a1c16f2008-12-17 15:59:43 +00001053 // these (remaining) entries are opaque
1054 for (i = num_trans; i < ctCount; i++) {
1055 SkPMColor c = *colors++;
1056 palette[i].red = SkGetPackedR32(c);
1057 palette[i].green = SkGetPackedG32(c);
1058 palette[i].blue = SkGetPackedB32(c);
1059 }
1060 return num_trans;
1061}
1062
1063class SkPNGImageEncoder : public SkImageEncoder {
1064protected:
commit-bot@chromium.orga936e372013-03-14 14:42:18 +00001065 virtual bool onEncode(SkWStream* stream, const SkBitmap& bm, int quality) SK_OVERRIDE;
tomhudson@google.com5c210c72011-07-28 21:06:40 +00001066private:
1067 bool doEncode(SkWStream* stream, const SkBitmap& bm,
1068 const bool& hasAlpha, int colorType,
1069 int bitDepth, SkBitmap::Config config,
1070 png_color_8& sig_bit);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +00001071
1072 typedef SkImageEncoder INHERITED;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001073};
1074
1075bool SkPNGImageEncoder::onEncode(SkWStream* stream, const SkBitmap& bitmap,
1076 int /*quality*/) {
1077 SkBitmap::Config config = bitmap.getConfig();
1078
1079 const bool hasAlpha = !bitmap.isOpaque();
1080 int colorType = PNG_COLOR_MASK_COLOR;
1081 int bitDepth = 8; // default for color
1082 png_color_8 sig_bit;
1083
1084 switch (config) {
1085 case SkBitmap::kIndex8_Config:
1086 colorType |= PNG_COLOR_MASK_PALETTE;
1087 // fall through to the ARGB_8888 case
1088 case SkBitmap::kARGB_8888_Config:
1089 sig_bit.red = 8;
1090 sig_bit.green = 8;
1091 sig_bit.blue = 8;
1092 sig_bit.alpha = 8;
1093 break;
1094 case SkBitmap::kARGB_4444_Config:
1095 sig_bit.red = 4;
1096 sig_bit.green = 4;
1097 sig_bit.blue = 4;
1098 sig_bit.alpha = 4;
1099 break;
1100 case SkBitmap::kRGB_565_Config:
1101 sig_bit.red = 5;
1102 sig_bit.green = 6;
1103 sig_bit.blue = 5;
1104 sig_bit.alpha = 0;
1105 break;
1106 default:
1107 return false;
1108 }
rmistry@google.comd6176b02012-08-23 18:14:13 +00001109
reed@android.com8a1c16f2008-12-17 15:59:43 +00001110 if (hasAlpha) {
1111 // don't specify alpha if we're a palette, even if our ctable has alpha
1112 if (!(colorType & PNG_COLOR_MASK_PALETTE)) {
1113 colorType |= PNG_COLOR_MASK_ALPHA;
1114 }
1115 } else {
1116 sig_bit.alpha = 0;
1117 }
rmistry@google.comd6176b02012-08-23 18:14:13 +00001118
reed@android.com8a1c16f2008-12-17 15:59:43 +00001119 SkAutoLockPixels alp(bitmap);
1120 // readyToDraw checks for pixels (and colortable if that is required)
1121 if (!bitmap.readyToDraw()) {
1122 return false;
1123 }
1124
1125 // we must do this after we have locked the pixels
1126 SkColorTable* ctable = bitmap.getColorTable();
1127 if (NULL != ctable) {
1128 if (ctable->count() == 0) {
1129 return false;
1130 }
1131 // check if we can store in fewer than 8 bits
1132 bitDepth = computeBitDepth(ctable->count());
1133 }
1134
tomhudson@google.com5c210c72011-07-28 21:06:40 +00001135 return doEncode(stream, bitmap, hasAlpha, colorType,
1136 bitDepth, config, sig_bit);
1137}
1138
1139bool SkPNGImageEncoder::doEncode(SkWStream* stream, const SkBitmap& bitmap,
1140 const bool& hasAlpha, int colorType,
1141 int bitDepth, SkBitmap::Config config,
1142 png_color_8& sig_bit) {
1143
reed@android.com8a1c16f2008-12-17 15:59:43 +00001144 png_structp png_ptr;
1145 png_infop info_ptr;
1146
1147 png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, sk_error_fn,
1148 NULL);
1149 if (NULL == png_ptr) {
1150 return false;
1151 }
1152
1153 info_ptr = png_create_info_struct(png_ptr);
1154 if (NULL == info_ptr) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +00001155 png_destroy_write_struct(&png_ptr, png_infopp_NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001156 return false;
1157 }
1158
1159 /* Set error handling. REQUIRED if you aren't supplying your own
1160 * error handling functions in the png_create_write_struct() call.
1161 */
1162 if (setjmp(png_jmpbuf(png_ptr))) {
1163 png_destroy_write_struct(&png_ptr, &info_ptr);
1164 return false;
1165 }
1166
commit-bot@chromium.orga936e372013-03-14 14:42:18 +00001167 png_set_write_fn(png_ptr, (void*)stream, sk_write_fn, png_flush_ptr_NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001168
1169 /* Set the image information here. Width and height are up to 2^31,
1170 * bit_depth is one of 1, 2, 4, 8, or 16, but valid values also depend on
1171 * the color_type selected. color_type is one of PNG_COLOR_TYPE_GRAY,
1172 * PNG_COLOR_TYPE_GRAY_ALPHA, PNG_COLOR_TYPE_PALETTE, PNG_COLOR_TYPE_RGB,
1173 * or PNG_COLOR_TYPE_RGB_ALPHA. interlace is either PNG_INTERLACE_NONE or
1174 * PNG_INTERLACE_ADAM7, and the compression_type and filter_type MUST
1175 * currently be PNG_COMPRESSION_TYPE_BASE and PNG_FILTER_TYPE_BASE. REQUIRED
1176 */
1177
1178 png_set_IHDR(png_ptr, info_ptr, bitmap.width(), bitmap.height(),
1179 bitDepth, colorType,
1180 PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE,
1181 PNG_FILTER_TYPE_BASE);
1182
reed@android.com61898772009-07-07 19:38:01 +00001183 // set our colortable/trans arrays if needed
1184 png_color paletteColors[256];
1185 png_byte trans[256];
1186 if (SkBitmap::kIndex8_Config == config) {
1187 SkColorTable* ct = bitmap.getColorTable();
1188 int numTrans = pack_palette(ct, paletteColors, trans, hasAlpha);
1189 png_set_PLTE(png_ptr, info_ptr, paletteColors, ct->count());
1190 if (numTrans > 0) {
1191 png_set_tRNS(png_ptr, info_ptr, trans, numTrans, NULL);
1192 }
1193 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001194
1195 png_set_sBIT(png_ptr, info_ptr, &sig_bit);
1196 png_write_info(png_ptr, info_ptr);
1197
1198 const char* srcImage = (const char*)bitmap.getPixels();
1199 SkAutoSMalloc<1024> rowStorage(bitmap.width() << 2);
1200 char* storage = (char*)rowStorage.get();
1201 transform_scanline_proc proc = choose_proc(config, hasAlpha);
1202
1203 for (int y = 0; y < bitmap.height(); y++) {
1204 png_bytep row_ptr = (png_bytep)storage;
1205 proc(srcImage, bitmap.width(), storage);
1206 png_write_rows(png_ptr, &row_ptr, 1);
1207 srcImage += bitmap.rowBytes();
1208 }
1209
1210 png_write_end(png_ptr, info_ptr);
1211
1212 /* clean up after the write, and free any memory allocated */
1213 png_destroy_write_struct(&png_ptr, &info_ptr);
1214 return true;
1215}
1216
reed@android.com00bf85a2009-01-22 13:04:56 +00001217///////////////////////////////////////////////////////////////////////////////
robertphillips@google.comec51cb82012-03-23 18:13:47 +00001218DEFINE_DECODER_CREATOR(PNGImageDecoder);
1219DEFINE_ENCODER_CREATOR(PNGImageEncoder);
1220///////////////////////////////////////////////////////////////////////////////
reed@android.com00bf85a2009-01-22 13:04:56 +00001221
scroggo@google.comb5571b32013-09-25 21:34:24 +00001222static bool is_png(SkStreamRewindable* stream) {
reed@android.com00bf85a2009-01-22 13:04:56 +00001223 char buf[PNG_BYTES_TO_CHECK];
1224 if (stream->read(buf, PNG_BYTES_TO_CHECK) == PNG_BYTES_TO_CHECK &&
1225 !png_sig_cmp((png_bytep) buf, (png_size_t)0, PNG_BYTES_TO_CHECK)) {
scroggo@google.com39edf4c2013-04-25 17:33:51 +00001226 return true;
1227 }
1228 return false;
1229}
1230
scroggo@google.comb5571b32013-09-25 21:34:24 +00001231SkImageDecoder* sk_libpng_dfactory(SkStreamRewindable* stream) {
scroggo@google.com39edf4c2013-04-25 17:33:51 +00001232 if (is_png(stream)) {
reed@android.com00bf85a2009-01-22 13:04:56 +00001233 return SkNEW(SkPNGImageDecoder);
1234 }
1235 return NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001236}
1237
scroggo@google.comb5571b32013-09-25 21:34:24 +00001238static SkImageDecoder::Format get_format_png(SkStreamRewindable* stream) {
scroggo@google.com39edf4c2013-04-25 17:33:51 +00001239 if (is_png(stream)) {
1240 return SkImageDecoder::kPNG_Format;
1241 }
1242 return SkImageDecoder::kUnknown_Format;
1243}
1244
reed@android.comdfee5792010-04-15 14:24:50 +00001245SkImageEncoder* sk_libpng_efactory(SkImageEncoder::Type t) {
reed@android.com00bf85a2009-01-22 13:04:56 +00001246 return (SkImageEncoder::kPNG_Type == t) ? SkNEW(SkPNGImageEncoder) : NULL;
1247}
1248
mtklein@google.combd6343b2013-09-04 17:20:18 +00001249static SkImageDecoder_DecodeReg gDReg(sk_libpng_dfactory);
1250static SkImageDecoder_FormatReg gFormatReg(get_format_png);
1251static SkImageEncoder_EncodeReg gEReg(sk_libpng_efactory);