blob: 85e803b455d488a3c4565317c3484a168026814d [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
22extern "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
commit-bot@chromium.orga936e372013-03-14 14:42:18 +000043class SkPNGImageIndex {
44public:
scroggo@google.comc70a3aa2013-07-18 20:03:15 +000045 SkPNGImageIndex(SkStream* stream, png_structp png_ptr, png_infop info_ptr)
46 : fStream(stream)
47 , fPng_ptr(png_ptr)
48 , fInfo_ptr(info_ptr)
49 , fConfig(SkBitmap::kNo_Config) {
50 SkASSERT(stream != NULL);
51 stream->ref();
commit-bot@chromium.orga936e372013-03-14 14:42:18 +000052 }
53 ~SkPNGImageIndex() {
scroggo@google.comc70a3aa2013-07-18 20:03:15 +000054 if (NULL != fPng_ptr) {
55 png_destroy_read_struct(&fPng_ptr, &fInfo_ptr, png_infopp_NULL);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +000056 }
57 }
58
scroggo@google.comc70a3aa2013-07-18 20:03:15 +000059 SkAutoTUnref<SkStream> fStream;
60 png_structp fPng_ptr;
61 png_infop fInfo_ptr;
62 SkBitmap::Config fConfig;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +000063};
64
reed@android.com8a1c16f2008-12-17 15:59:43 +000065class SkPNGImageDecoder : public SkImageDecoder {
66public:
commit-bot@chromium.orga936e372013-03-14 14:42:18 +000067 SkPNGImageDecoder() {
68 fImageIndex = NULL;
69 }
70 virtual Format getFormat() const SK_OVERRIDE {
reed@android.com8a1c16f2008-12-17 15:59:43 +000071 return kPNG_Format;
72 }
scroggo@google.com39edf4c2013-04-25 17:33:51 +000073
commit-bot@chromium.orga936e372013-03-14 14:42:18 +000074 virtual ~SkPNGImageDecoder() {
75 SkDELETE(fImageIndex);
76 }
rmistry@google.comd6176b02012-08-23 18:14:13 +000077
reed@android.com8a1c16f2008-12-17 15:59:43 +000078protected:
commit-bot@chromium.orga936e372013-03-14 14:42:18 +000079#ifdef SK_BUILD_FOR_ANDROID
80 virtual bool onBuildTileIndex(SkStream *stream, int *width, int *height) SK_OVERRIDE;
scroggo@google.com7e6fcee2013-05-03 20:14:28 +000081 virtual bool onDecodeSubset(SkBitmap* bitmap, const SkIRect& region) SK_OVERRIDE;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +000082#endif
83 virtual bool onDecode(SkStream* stream, SkBitmap* bm, Mode) SK_OVERRIDE;
84
85private:
86 SkPNGImageIndex* fImageIndex;
87
88 bool onDecodeInit(SkStream* stream, png_structp *png_ptrp, png_infop *info_ptrp);
scroggo@google.com2bbc2c92013-06-14 15:33:20 +000089 bool decodePalette(png_structp png_ptr, png_infop info_ptr,
90 bool * SK_RESTRICT hasAlphap, bool *reallyHasAlphap,
91 SkColorTable **colorTablep);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +000092 bool getBitmapConfig(png_structp png_ptr, png_infop info_ptr,
93 SkBitmap::Config *config, bool *hasAlpha,
94 bool *doDither, SkPMColor *theTranspColor);
95
96 typedef SkImageDecoder INHERITED;
reed@android.com8a1c16f2008-12-17 15:59:43 +000097};
98
99#ifndef png_jmpbuf
100# define png_jmpbuf(png_ptr) ((png_ptr)->jmpbuf)
101#endif
102
103#define PNG_BYTES_TO_CHECK 4
104
105/* Automatically clean up after throwing an exception */
106struct PNGAutoClean {
107 PNGAutoClean(png_structp p, png_infop i): png_ptr(p), info_ptr(i) {}
108 ~PNGAutoClean() {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000109 png_destroy_read_struct(&png_ptr, &info_ptr, png_infopp_NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000110 }
111private:
112 png_structp png_ptr;
113 png_infop info_ptr;
114};
115
reed@android.com8a1c16f2008-12-17 15:59:43 +0000116static void sk_read_fn(png_structp png_ptr, png_bytep data, png_size_t length) {
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000117 SkStream* sk_stream = (SkStream*) png_get_io_ptr(png_ptr);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000118 size_t bytes = sk_stream->read(data, length);
119 if (bytes != length) {
120 png_error(png_ptr, "Read Error!");
121 }
122}
123
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000124#ifdef SK_BUILD_FOR_ANDROID
125static void sk_seek_fn(png_structp png_ptr, png_uint_32 offset) {
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000126 SkStream* sk_stream = (SkStream*) png_get_io_ptr(png_ptr);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000127 sk_stream->rewind();
128 (void)sk_stream->skip(offset);
129}
130#endif
131
reed@android.com8a1c16f2008-12-17 15:59:43 +0000132static int sk_read_user_chunk(png_structp png_ptr, png_unknown_chunkp chunk) {
133 SkImageDecoder::Peeker* peeker =
134 (SkImageDecoder::Peeker*)png_get_user_chunk_ptr(png_ptr);
135 // peek() returning true means continue decoding
136 return peeker->peek((const char*)chunk->name, chunk->data, chunk->size) ?
137 1 : -1;
138}
139
140static void sk_error_fn(png_structp png_ptr, png_const_charp msg) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000141 SkDEBUGF(("------ png error %s\n", msg));
reed@android.com8a1c16f2008-12-17 15:59:43 +0000142 longjmp(png_jmpbuf(png_ptr), 1);
143}
144
145static void skip_src_rows(png_structp png_ptr, uint8_t storage[], int count) {
146 for (int i = 0; i < count; i++) {
147 uint8_t* tmp = storage;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000148 png_read_rows(png_ptr, &tmp, png_bytepp_NULL, 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000149 }
150}
151
152static bool pos_le(int value, int max) {
153 return value > 0 && value <= max;
154}
155
156static bool substituteTranspColor(SkBitmap* bm, SkPMColor match) {
157 SkASSERT(bm->config() == SkBitmap::kARGB_8888_Config);
rmistry@google.comd6176b02012-08-23 18:14:13 +0000158
reed@android.com8a1c16f2008-12-17 15:59:43 +0000159 bool reallyHasAlpha = false;
160
161 for (int y = bm->height() - 1; y >= 0; --y) {
162 SkPMColor* p = bm->getAddr32(0, y);
163 for (int x = bm->width() - 1; x >= 0; --x) {
164 if (match == *p) {
165 *p = 0;
166 reallyHasAlpha = true;
167 }
168 p += 1;
169 }
170 }
171 return reallyHasAlpha;
172}
173
reed@android.com3f1f06a2010-03-03 21:04:12 +0000174static bool canUpscalePaletteToConfig(SkBitmap::Config dstConfig,
reed@android.comb6137c32009-07-29 20:56:52 +0000175 bool srcHasAlpha) {
reed@android.com3f1f06a2010-03-03 21:04:12 +0000176 switch (dstConfig) {
reed@android.comb6137c32009-07-29 20:56:52 +0000177 case SkBitmap::kARGB_8888_Config:
178 case SkBitmap::kARGB_4444_Config:
179 return true;
180 case SkBitmap::kRGB_565_Config:
181 // only return true if the src is opaque (since 565 is opaque)
182 return !srcHasAlpha;
183 default:
184 return false;
185 }
186}
187
188// call only if color_type is PALETTE. Returns true if the ctable has alpha
189static bool hasTransparencyInPalette(png_structp png_ptr, png_infop info_ptr) {
190 png_bytep trans;
191 int num_trans;
192
193 if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
194 png_get_tRNS(png_ptr, info_ptr, &trans, &num_trans, NULL);
195 return num_trans > 0;
196 }
197 return false;
reed@android.com11344262009-07-08 20:09:23 +0000198}
199
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000200bool SkPNGImageDecoder::onDecodeInit(SkStream* sk_stream, png_structp *png_ptrp,
201 png_infop *info_ptrp) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000202 /* Create and initialize the png_struct with the desired error handler
203 * functions. If you want to use the default stderr and longjump method,
204 * you can supply NULL for the last three parameters. We also supply the
205 * the compiler header file version, so that we know if the application
206 * was compiled with a compatible version of the library. */
207 png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
208 NULL, sk_error_fn, NULL);
209 // png_voidp user_error_ptr, user_error_fn, user_warning_fn);
210 if (png_ptr == NULL) {
211 return false;
212 }
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000213 *png_ptrp = png_ptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000214
215 /* Allocate/initialize the memory for image information. */
216 png_infop info_ptr = png_create_info_struct(png_ptr);
217 if (info_ptr == NULL) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000218 png_destroy_read_struct(&png_ptr, png_infopp_NULL, png_infopp_NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000219 return false;
220 }
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000221 *info_ptrp = info_ptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000222
223 /* Set error handling if you are using the setjmp/longjmp method (this is
224 * the normal method of doing things with libpng). REQUIRED unless you
225 * set up your own error handlers in the png_create_read_struct() earlier.
226 */
227 if (setjmp(png_jmpbuf(png_ptr))) {
228 return false;
229 }
230
231 /* If you are using replacement read functions, instead of calling
232 * png_init_io() here you would call:
233 */
234 png_set_read_fn(png_ptr, (void *)sk_stream, sk_read_fn);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000235#ifdef SK_BUILD_FOR_ANDROID
236 png_set_seek_fn(png_ptr, sk_seek_fn);
237#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000238 /* where user_io_ptr is a structure you want available to the callbacks */
239 /* If we have already read some of the signature */
240// png_set_sig_bytes(png_ptr, 0 /* sig_read */ );
241
242 // hookup our peeker so we can see any user-chunks the caller may be interested in
243 png_set_keep_unknown_chunks(png_ptr, PNG_HANDLE_CHUNK_ALWAYS, (png_byte*)"", 0);
244 if (this->getPeeker()) {
245 png_set_read_user_chunk_fn(png_ptr, (png_voidp)this->getPeeker(), sk_read_user_chunk);
246 }
247
248 /* The call to png_read_info() gives us all of the information from the
249 * PNG file before the first IDAT (image data chunk). */
250 png_read_info(png_ptr, info_ptr);
251 png_uint_32 origWidth, origHeight;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000252 int bitDepth, colorType;
253 png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
254 &colorType, int_p_NULL, int_p_NULL, int_p_NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000255
256 /* tell libpng to strip 16 bit/color files down to 8 bits/color */
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000257 if (bitDepth == 16) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000258 png_set_strip_16(png_ptr);
259 }
260 /* Extract multiple pixels with bit depths of 1, 2, and 4 from a single
261 * byte into separate bytes (useful for paletted and grayscale images). */
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000262 if (bitDepth < 8) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000263 png_set_packing(png_ptr);
264 }
265 /* Expand grayscale images to the full 8 bits from 1, 2, or 4 bits/pixel */
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000266 if (colorType == PNG_COLOR_TYPE_GRAY && bitDepth < 8) {
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000267 png_set_expand_gray_1_2_4_to_8(png_ptr);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000268 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000269
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000270 return true;
271}
272
273bool SkPNGImageDecoder::onDecode(SkStream* sk_stream, SkBitmap* decodedBitmap,
274 Mode mode) {
275 png_structp png_ptr;
276 png_infop info_ptr;
277
278 if (!onDecodeInit(sk_stream, &png_ptr, &info_ptr)) {
279 return false;
280 }
281
282 if (setjmp(png_jmpbuf(png_ptr))) {
283 return false;
284 }
285
286 PNGAutoClean autoClean(png_ptr, info_ptr);
287
288 png_uint_32 origWidth, origHeight;
289 int bitDepth, colorType, interlaceType;
290 png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
291 &colorType, &interlaceType, int_p_NULL, int_p_NULL);
rmistry@google.comd6176b02012-08-23 18:14:13 +0000292
reed@android.com8a1c16f2008-12-17 15:59:43 +0000293 SkBitmap::Config config;
294 bool hasAlpha = false;
295 bool doDither = this->getDitherImage();
296 SkPMColor theTranspColor = 0; // 0 tells us not to try to match
rmistry@google.comd6176b02012-08-23 18:14:13 +0000297
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000298 if (!getBitmapConfig(png_ptr, info_ptr, &config, &hasAlpha, &doDither, &theTranspColor)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000299 return false;
300 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000301
reed@android.com8a1c16f2008-12-17 15:59:43 +0000302 const int sampleSize = this->getSampleSize();
303 SkScaledBitmapSampler sampler(origWidth, origHeight, sampleSize);
scroggo@google.combc69ce92013-07-09 15:45:14 +0000304 decodedBitmap->setConfig(config, sampler.scaledWidth(), sampler.scaledHeight());
reed@android.com8a1c16f2008-12-17 15:59:43 +0000305
reed@android.com8a1c16f2008-12-17 15:59:43 +0000306 if (SkImageDecoder::kDecodeBounds_Mode == mode) {
307 return true;
308 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000309
reed@android.com8a1c16f2008-12-17 15:59:43 +0000310 // from here down we are concerned with colortables and pixels
311
312 // we track if we actually see a non-opaque pixels, since sometimes a PNG sets its colortype
313 // to |= PNG_COLOR_MASK_ALPHA, but all of its pixels are in fact opaque. We care, since we
314 // draw lots faster if we can flag the bitmap has being opaque
315 bool reallyHasAlpha = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000316 SkColorTable* colorTable = NULL;
317
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000318 if (colorType == PNG_COLOR_TYPE_PALETTE) {
319 decodePalette(png_ptr, info_ptr, &hasAlpha, &reallyHasAlpha, &colorTable);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000320 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000321
reed@android.com8a1c16f2008-12-17 15:59:43 +0000322 SkAutoUnref aur(colorTable);
323
scroggo@google.combc69ce92013-07-09 15:45:14 +0000324 if (!this->allocPixelRef(decodedBitmap,
325 SkBitmap::kIndex8_Config == config ? colorTable : NULL)) {
326 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000327 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000328
reed@android.com8a1c16f2008-12-17 15:59:43 +0000329 SkAutoLockPixels alp(*decodedBitmap);
330
reed@android.com8a1c16f2008-12-17 15:59:43 +0000331 /* Turn on interlace handling. REQUIRED if you are not using
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000332 * png_read_image(). To see how to handle interlacing passes,
333 * see the png_read_row() method below:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000334 */
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000335 const int number_passes = (interlaceType != PNG_INTERLACE_NONE) ?
336 png_set_interlace_handling(png_ptr) : 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000337
338 /* Optional call to gamma correct and add the background to the palette
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000339 * and update info structure. REQUIRED if you are expecting libpng to
340 * update the palette for you (ie you selected such a transform above).
reed@android.com8a1c16f2008-12-17 15:59:43 +0000341 */
342 png_read_update_info(png_ptr, info_ptr);
343
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000344 if ((SkBitmap::kA8_Config == config || SkBitmap::kIndex8_Config == config)
345 && 1 == sampleSize) {
346 // A8 is only allowed if the original was GRAY.
347 SkASSERT(config != SkBitmap::kA8_Config
348 || PNG_COLOR_TYPE_GRAY == colorType);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000349 for (int i = 0; i < number_passes; i++) {
350 for (png_uint_32 y = 0; y < origHeight; y++) {
351 uint8_t* bmRow = decodedBitmap->getAddr8(0, y);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000352 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000353 }
354 }
355 } else {
356 SkScaledBitmapSampler::SrcConfig sc;
357 int srcBytesPerPixel = 4;
rmistry@google.comd6176b02012-08-23 18:14:13 +0000358
reed@android.com11344262009-07-08 20:09:23 +0000359 if (colorTable != NULL) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000360 sc = SkScaledBitmapSampler::kIndex;
361 srcBytesPerPixel = 1;
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000362 } else if (SkBitmap::kA8_Config == config) {
363 // A8 is only allowed if the original was GRAY.
364 SkASSERT(PNG_COLOR_TYPE_GRAY == colorType);
365 sc = SkScaledBitmapSampler::kGray;
366 srcBytesPerPixel = 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000367 } else if (hasAlpha) {
368 sc = SkScaledBitmapSampler::kRGBA;
369 } else {
370 sc = SkScaledBitmapSampler::kRGBX;
371 }
reed@android.com11344262009-07-08 20:09:23 +0000372
373 /* We have to pass the colortable explicitly, since we may have one
374 even if our decodedBitmap doesn't, due to the request that we
375 upscale png's palette to a direct model
376 */
377 SkAutoLockColors ctLock(colorTable);
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000378 if (!sampler.begin(decodedBitmap, sc, doDither, ctLock.colors(),
379 this->getRequireUnpremultipliedColors())) {
reed@android.com862e91b2009-04-28 15:27:07 +0000380 return false;
381 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000382 const int height = decodedBitmap->height();
383
reed@android.com862e91b2009-04-28 15:27:07 +0000384 if (number_passes > 1) {
385 SkAutoMalloc storage(origWidth * origHeight * srcBytesPerPixel);
386 uint8_t* base = (uint8_t*)storage.get();
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000387 size_t rowBytes = origWidth * srcBytesPerPixel;
reed@android.coma8a8b8b2009-05-04 15:00:11 +0000388
reed@android.com862e91b2009-04-28 15:27:07 +0000389 for (int i = 0; i < number_passes; i++) {
390 uint8_t* row = base;
391 for (png_uint_32 y = 0; y < origHeight; y++) {
392 uint8_t* bmRow = row;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000393 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
394 row += rowBytes;
reed@android.com862e91b2009-04-28 15:27:07 +0000395 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000396 }
reed@android.com862e91b2009-04-28 15:27:07 +0000397 // now sample it
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000398 base += sampler.srcY0() * rowBytes;
reed@android.com862e91b2009-04-28 15:27:07 +0000399 for (int y = 0; y < height; y++) {
400 reallyHasAlpha |= sampler.next(base);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000401 base += sampler.srcDY() * rowBytes;
reed@android.com862e91b2009-04-28 15:27:07 +0000402 }
403 } else {
404 SkAutoMalloc storage(origWidth * srcBytesPerPixel);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000405 uint8_t* srcRow = (uint8_t*)storage.get();
406 skip_src_rows(png_ptr, srcRow, sampler.srcY0());
407
408 for (int y = 0; y < height; y++) {
409 uint8_t* tmp = srcRow;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000410 png_read_rows(png_ptr, &tmp, png_bytepp_NULL, 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000411 reallyHasAlpha |= sampler.next(srcRow);
412 if (y < height - 1) {
413 skip_src_rows(png_ptr, srcRow, sampler.srcDY() - 1);
414 }
415 }
reed@android.com862e91b2009-04-28 15:27:07 +0000416
reed@android.com8a1c16f2008-12-17 15:59:43 +0000417 // skip the rest of the rows (if any)
418 png_uint_32 read = (height - 1) * sampler.srcDY() +
419 sampler.srcY0() + 1;
420 SkASSERT(read <= origHeight);
421 skip_src_rows(png_ptr, srcRow, origHeight - read);
422 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000423 }
424
425 /* read rest of file, and get additional chunks in info_ptr - REQUIRED */
426 png_read_end(png_ptr, info_ptr);
427
428 if (0 != theTranspColor) {
429 reallyHasAlpha |= substituteTranspColor(decodedBitmap, theTranspColor);
430 }
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000431 if (reallyHasAlpha && this->getRequireUnpremultipliedColors() &&
432 SkBitmap::kARGB_8888_Config != decodedBitmap->config()) {
433 // If the caller wants an unpremultiplied bitmap, and we let them get
434 // away with a config other than 8888, and it has alpha after all,
435 // return false, since the result will have premultiplied colors.
436 return false;
437 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000438 decodedBitmap->setIsOpaque(!reallyHasAlpha);
439 return true;
440}
441
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000442
443
444bool SkPNGImageDecoder::getBitmapConfig(png_structp png_ptr, png_infop info_ptr,
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000445 SkBitmap::Config* SK_RESTRICT configp,
446 bool* SK_RESTRICT hasAlphap,
447 bool* SK_RESTRICT doDitherp,
448 SkPMColor* SK_RESTRICT theTranspColorp) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000449 png_uint_32 origWidth, origHeight;
450 int bitDepth, colorType;
451 png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
452 &colorType, int_p_NULL, int_p_NULL, int_p_NULL);
453
454 // check for sBIT chunk data, in case we should disable dithering because
455 // our data is not truely 8bits per component
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000456 png_color_8p sig_bit;
457 if (*doDitherp && png_get_sBIT(png_ptr, info_ptr, &sig_bit)) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000458#if 0
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000459 SkDebugf("----- sBIT %d %d %d %d\n", sig_bit->red, sig_bit->green,
460 sig_bit->blue, sig_bit->alpha);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000461#endif
462 // 0 seems to indicate no information available
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000463 if (pos_le(sig_bit->red, SK_R16_BITS) &&
464 pos_le(sig_bit->green, SK_G16_BITS) &&
465 pos_le(sig_bit->blue, SK_B16_BITS)) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000466 *doDitherp = false;
467 }
468 }
469
470 if (colorType == PNG_COLOR_TYPE_PALETTE) {
471 bool paletteHasAlpha = hasTransparencyInPalette(png_ptr, info_ptr);
472 *configp = this->getPrefConfig(kIndex_SrcDepth, paletteHasAlpha);
473 // now see if we can upscale to their requested config
474 if (!canUpscalePaletteToConfig(*configp, paletteHasAlpha)) {
475 *configp = SkBitmap::kIndex8_Config;
476 }
477 } else {
478 png_color_16p transpColor = NULL;
479 int numTransp = 0;
480
481 png_get_tRNS(png_ptr, info_ptr, NULL, &numTransp, &transpColor);
482
483 bool valid = png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS);
484
485 if (valid && numTransp == 1 && transpColor != NULL) {
486 /* Compute our transparent color, which we'll match against later.
487 We don't really handle 16bit components properly here, since we
488 do our compare *after* the values have been knocked down to 8bit
489 which means we will find more matches than we should. The real
490 fix seems to be to see the actual 16bit components, do the
491 compare, and then knock it down to 8bits ourselves.
492 */
493 if (colorType & PNG_COLOR_MASK_COLOR) {
494 if (16 == bitDepth) {
495 *theTranspColorp = SkPackARGB32(0xFF, transpColor->red >> 8,
496 transpColor->green >> 8,
497 transpColor->blue >> 8);
498 } else {
499 *theTranspColorp = SkPackARGB32(0xFF, transpColor->red,
500 transpColor->green,
501 transpColor->blue);
502 }
503 } else { // gray
504 if (16 == bitDepth) {
505 *theTranspColorp = SkPackARGB32(0xFF, transpColor->gray >> 8,
506 transpColor->gray >> 8,
507 transpColor->gray >> 8);
508 } else {
509 *theTranspColorp = SkPackARGB32(0xFF, transpColor->gray,
510 transpColor->gray,
511 transpColor->gray);
512 }
513 }
514 }
515
516 if (valid ||
517 PNG_COLOR_TYPE_RGB_ALPHA == colorType ||
518 PNG_COLOR_TYPE_GRAY_ALPHA == colorType) {
519 *hasAlphap = true;
520 }
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000521
522 SrcDepth srcDepth = k32Bit_SrcDepth;
523 if (PNG_COLOR_TYPE_GRAY == colorType) {
524 srcDepth = k8BitGray_SrcDepth;
scroggo@google.com8e2ef012013-07-18 20:14:45 +0000525 // Remove this assert, which fails on desk_pokemonwiki.skp
526 //SkASSERT(!*hasAlphap);
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000527 }
528
529 *configp = this->getPrefConfig(srcDepth, *hasAlphap);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000530 // now match the request against our capabilities
531 if (*hasAlphap) {
532 if (*configp != SkBitmap::kARGB_4444_Config) {
533 *configp = SkBitmap::kARGB_8888_Config;
534 }
535 } else {
536 if (*configp != SkBitmap::kRGB_565_Config &&
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000537 *configp != SkBitmap::kARGB_4444_Config &&
538 *configp != SkBitmap::kA8_Config) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000539 *configp = SkBitmap::kARGB_8888_Config;
540 }
541 }
542 }
543
544 // sanity check for size
545 {
546 Sk64 size;
547 size.setMul(origWidth, origHeight);
548 if (size.isNeg() || !size.is32()) {
549 return false;
550 }
551 // now check that if we are 4-bytes per pixel, we also don't overflow
552 if (size.get32() > (0x7FFFFFFF >> 2)) {
553 return false;
554 }
555 }
556
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000557 if (!this->chooseFromOneChoice(*configp, origWidth, origHeight)) {
558 return false;
559 }
560
561 // If the image has alpha and the decoder wants unpremultiplied
562 // colors, the only supported config is 8888.
563 if (this->getRequireUnpremultipliedColors() && *hasAlphap) {
564 *configp = SkBitmap::kARGB_8888_Config;
565 }
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000566
567 if (fImageIndex != NULL) {
568 if (SkBitmap::kNo_Config == fImageIndex->fConfig) {
569 // This is the first time for this subset decode. From now on,
570 // all decodes must be in the same config.
571 fImageIndex->fConfig = *configp;
572 } else if (fImageIndex->fConfig != *configp) {
573 // Requesting a different config for a subsequent decode is not
574 // supported. Report failure before we make changes to png_ptr.
575 return false;
576 }
577 }
578
579 bool convertGrayToRGB = PNG_COLOR_TYPE_GRAY == colorType
580 && *configp != SkBitmap::kA8_Config;
581
582 // Unless the user is requesting A8, convert a grayscale image into RGB.
583 // GRAY_ALPHA will always be converted to RGB
584 if (convertGrayToRGB || colorType == PNG_COLOR_TYPE_GRAY_ALPHA) {
585 png_set_gray_to_rgb(png_ptr);
586 }
587
588 // Add filler (or alpha) byte (after each RGB triplet) if necessary.
589 if (colorType == PNG_COLOR_TYPE_RGB || convertGrayToRGB) {
590 png_set_filler(png_ptr, 0xff, PNG_FILLER_AFTER);
591 }
592
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000593 return true;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000594}
595
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000596typedef uint32_t (*PackColorProc)(U8CPU a, U8CPU r, U8CPU g, U8CPU b);
597
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000598bool SkPNGImageDecoder::decodePalette(png_structp png_ptr, png_infop info_ptr,
599 bool *hasAlphap, bool *reallyHasAlphap,
600 SkColorTable **colorTablep) {
601 int numPalette;
602 png_colorp palette;
603 png_bytep trans;
604 int numTrans;
605 bool reallyHasAlpha = false;
606 SkColorTable* colorTable = NULL;
607
608 png_get_PLTE(png_ptr, info_ptr, &palette, &numPalette);
609
610 /* BUGGY IMAGE WORKAROUND
611
612 We hit some images (e.g. fruit_.png) who contain bytes that are == colortable_count
613 which is a problem since we use the byte as an index. To work around this we grow
614 the colortable by 1 (if its < 256) and duplicate the last color into that slot.
615 */
616 int colorCount = numPalette + (numPalette < 256);
617
618 colorTable = SkNEW_ARGS(SkColorTable, (colorCount));
619
620 SkPMColor* colorPtr = colorTable->lockColors();
621 if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
622 png_get_tRNS(png_ptr, info_ptr, &trans, &numTrans, NULL);
623 *hasAlphap = (numTrans > 0);
624 } else {
625 numTrans = 0;
626 colorTable->setFlags(colorTable->getFlags() | SkColorTable::kColorsAreOpaque_Flag);
627 }
628 // check for bad images that might make us crash
629 if (numTrans > numPalette) {
630 numTrans = numPalette;
631 }
632
633 int index = 0;
634 int transLessThanFF = 0;
635
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000636 // Choose which function to use to create the color table. If the final destination's
637 // config is unpremultiplied, the color table will store unpremultiplied colors.
638 PackColorProc proc;
639 if (this->getRequireUnpremultipliedColors()) {
640 proc = &SkPackARGB32NoCheck;
641 } else {
642 proc = &SkPreMultiplyARGB;
643 }
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000644 for (; index < numTrans; index++) {
645 transLessThanFF |= (int)*trans - 0xFF;
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000646 *colorPtr++ = proc(*trans++, palette->red, palette->green, palette->blue);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000647 palette++;
648 }
649 reallyHasAlpha |= (transLessThanFF < 0);
650
651 for (; index < numPalette; index++) {
652 *colorPtr++ = SkPackARGB32(0xFF, palette->red, palette->green, palette->blue);
653 palette++;
654 }
655
656 // see BUGGY IMAGE WORKAROUND comment above
657 if (numPalette < 256) {
658 *colorPtr = colorPtr[-1];
659 }
660 colorTable->unlockColors(true);
661 *colorTablep = colorTable;
662 *reallyHasAlphap = reallyHasAlpha;
663 return true;
664}
665
666#ifdef SK_BUILD_FOR_ANDROID
667
668bool SkPNGImageDecoder::onBuildTileIndex(SkStream* sk_stream, int *width, int *height) {
669 png_structp png_ptr;
670 png_infop info_ptr;
671
672 if (!onDecodeInit(sk_stream, &png_ptr, &info_ptr)) {
673 return false;
674 }
675
676 if (setjmp(png_jmpbuf(png_ptr)) != 0) {
677 png_destroy_read_struct(&png_ptr, &info_ptr, png_infopp_NULL);
678 return false;
679 }
680
681 png_uint_32 origWidth, origHeight;
682 int bitDepth, colorType;
683 png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
684 &colorType, int_p_NULL, int_p_NULL, int_p_NULL);
685
686 *width = origWidth;
687 *height = origHeight;
688
689 png_build_index(png_ptr);
690
691 if (fImageIndex) {
692 SkDELETE(fImageIndex);
693 }
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000694 fImageIndex = SkNEW_ARGS(SkPNGImageIndex, (sk_stream, png_ptr, info_ptr));
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000695
696 return true;
697}
698
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000699bool SkPNGImageDecoder::onDecodeSubset(SkBitmap* bm, const SkIRect& region) {
700 if (NULL == fImageIndex) {
701 return false;
702 }
703
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000704 png_structp png_ptr = fImageIndex->fPng_ptr;
705 png_infop info_ptr = fImageIndex->fInfo_ptr;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000706 if (setjmp(png_jmpbuf(png_ptr))) {
707 return false;
708 }
709
710 png_uint_32 origWidth, origHeight;
711 int bitDepth, colorType, interlaceType;
712 png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
713 &colorType, &interlaceType, int_p_NULL, int_p_NULL);
714
715 SkIRect rect = SkIRect::MakeWH(origWidth, origHeight);
716
717 if (!rect.intersect(region)) {
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000718 // If the requested region is entirely outside the image, just
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000719 // returns false
720 return false;
721 }
722
723 SkBitmap::Config config;
724 bool hasAlpha = false;
725 bool doDither = this->getDitherImage();
726 SkPMColor theTranspColor = 0; // 0 tells us not to try to match
727
728 if (!getBitmapConfig(png_ptr, info_ptr, &config, &hasAlpha, &doDither, &theTranspColor)) {
729 return false;
730 }
731
732 const int sampleSize = this->getSampleSize();
733 SkScaledBitmapSampler sampler(origWidth, rect.height(), sampleSize);
734
735 SkBitmap decodedBitmap;
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000736 decodedBitmap.setConfig(config, sampler.scaledWidth(), sampler.scaledHeight());
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000737
738 // from here down we are concerned with colortables and pixels
739
740 // we track if we actually see a non-opaque pixels, since sometimes a PNG sets its colortype
741 // to |= PNG_COLOR_MASK_ALPHA, but all of its pixels are in fact opaque. We care, since we
742 // draw lots faster if we can flag the bitmap has being opaque
743 bool reallyHasAlpha = false;
744 SkColorTable* colorTable = NULL;
745
746 if (colorType == PNG_COLOR_TYPE_PALETTE) {
747 decodePalette(png_ptr, info_ptr, &hasAlpha, &reallyHasAlpha, &colorTable);
748 }
749
750 SkAutoUnref aur(colorTable);
751
752 // Check ahead of time if the swap(dest, src) is possible.
753 // If yes, then we will stick to AllocPixelRef since it's cheaper with the swap happening.
754 // If no, then we will use alloc to allocate pixels to prevent garbage collection.
755 int w = rect.width() / sampleSize;
756 int h = rect.height() / sampleSize;
757 const bool swapOnly = (rect == region) && (w == decodedBitmap.width()) &&
758 (h == decodedBitmap.height()) && bm->isNull();
759 const bool needColorTable = SkBitmap::kIndex8_Config == config;
760 if (swapOnly) {
761 if (!this->allocPixelRef(&decodedBitmap, needColorTable ? colorTable : NULL)) {
762 return false;
763 }
764 } else {
765 if (!decodedBitmap.allocPixels(NULL, needColorTable ? colorTable : NULL)) {
766 return false;
767 }
768 }
769 SkAutoLockPixels alp(decodedBitmap);
770
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000771 /* Turn on interlace handling. REQUIRED if you are not using
772 * png_read_image(). To see how to handle interlacing passes,
773 * see the png_read_row() method below:
774 */
775 const int number_passes = (interlaceType != PNG_INTERLACE_NONE) ?
776 png_set_interlace_handling(png_ptr) : 1;
777
778 /* Optional call to gamma correct and add the background to the palette
779 * and update info structure. REQUIRED if you are expecting libpng to
780 * update the palette for you (ie you selected such a transform above).
781 */
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000782
783 // Direct access to png_ptr fields is deprecated in libpng > 1.2.
784#if defined(PNG_1_0_X) || defined (PNG_1_2_X)
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000785 png_ptr->pass = 0;
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000786#else
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000787 // FIXME: This sets pass as desired, but also sets iwidth. Is that ok?
788 png_set_interlaced_pass(png_ptr, 0);
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000789#endif
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000790 png_read_update_info(png_ptr, info_ptr);
791
792 int actualTop = rect.fTop;
793
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000794 if ((SkBitmap::kA8_Config == config || SkBitmap::kIndex8_Config == config)
795 && 1 == sampleSize) {
796 // A8 is only allowed if the original was GRAY.
797 SkASSERT(config != SkBitmap::kA8_Config
798 || PNG_COLOR_TYPE_GRAY == colorType);
799
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000800 for (int i = 0; i < number_passes; i++) {
801 png_configure_decoder(png_ptr, &actualTop, i);
802 for (int j = 0; j < rect.fTop - actualTop; j++) {
803 uint8_t* bmRow = decodedBitmap.getAddr8(0, 0);
804 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
805 }
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000806 png_uint_32 bitmapHeight = (png_uint_32) decodedBitmap.height();
807 for (png_uint_32 y = 0; y < bitmapHeight; y++) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000808 uint8_t* bmRow = decodedBitmap.getAddr8(0, y);
809 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
810 }
811 }
812 } else {
813 SkScaledBitmapSampler::SrcConfig sc;
814 int srcBytesPerPixel = 4;
815
816 if (colorTable != NULL) {
817 sc = SkScaledBitmapSampler::kIndex;
818 srcBytesPerPixel = 1;
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000819 } else if (SkBitmap::kA8_Config == config) {
820 // A8 is only allowed if the original was GRAY.
821 SkASSERT(PNG_COLOR_TYPE_GRAY == colorType);
822 sc = SkScaledBitmapSampler::kGray;
823 srcBytesPerPixel = 1;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000824 } else if (hasAlpha) {
825 sc = SkScaledBitmapSampler::kRGBA;
826 } else {
827 sc = SkScaledBitmapSampler::kRGBX;
828 }
829
830 /* We have to pass the colortable explicitly, since we may have one
831 even if our decodedBitmap doesn't, due to the request that we
832 upscale png's palette to a direct model
833 */
834 SkAutoLockColors ctLock(colorTable);
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000835 if (!sampler.begin(&decodedBitmap, sc, doDither, ctLock.colors(),
836 this->getRequireUnpremultipliedColors())) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000837 return false;
838 }
839 const int height = decodedBitmap.height();
840
841 if (number_passes > 1) {
842 SkAutoMalloc storage(origWidth * origHeight * srcBytesPerPixel);
843 uint8_t* base = (uint8_t*)storage.get();
844 size_t rb = origWidth * srcBytesPerPixel;
845
846 for (int i = 0; i < number_passes; i++) {
847 png_configure_decoder(png_ptr, &actualTop, i);
848 for (int j = 0; j < rect.fTop - actualTop; j++) {
849 uint8_t* bmRow = (uint8_t*)decodedBitmap.getPixels();
850 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
851 }
852 uint8_t* row = base;
853 for (int32_t y = 0; y < rect.height(); y++) {
854 uint8_t* bmRow = row;
855 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
856 row += rb;
857 }
858 }
859 // now sample it
860 base += sampler.srcY0() * rb;
861 for (int y = 0; y < height; y++) {
862 reallyHasAlpha |= sampler.next(base);
863 base += sampler.srcDY() * rb;
864 }
865 } else {
866 SkAutoMalloc storage(origWidth * srcBytesPerPixel);
867 uint8_t* srcRow = (uint8_t*)storage.get();
868
869 png_configure_decoder(png_ptr, &actualTop, 0);
870 skip_src_rows(png_ptr, srcRow, sampler.srcY0());
871
872 for (int i = 0; i < rect.fTop - actualTop; i++) {
873 uint8_t* bmRow = (uint8_t*)decodedBitmap.getPixels();
874 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
875 }
876 for (int y = 0; y < height; y++) {
877 uint8_t* tmp = srcRow;
878 png_read_rows(png_ptr, &tmp, png_bytepp_NULL, 1);
879 reallyHasAlpha |= sampler.next(srcRow);
880 if (y < height - 1) {
881 skip_src_rows(png_ptr, srcRow, sampler.srcDY() - 1);
882 }
883 }
884 }
885 }
886
887 if (0 != theTranspColor) {
888 reallyHasAlpha |= substituteTranspColor(&decodedBitmap, theTranspColor);
889 }
890 decodedBitmap.setIsOpaque(!reallyHasAlpha);
891
892 if (swapOnly) {
893 bm->swap(decodedBitmap);
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000894 return true;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000895 }
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000896 return this->cropBitmap(bm, &decodedBitmap, sampleSize, region.x(), region.y(),
897 region.width(), region.height(), 0, rect.y());
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000898}
899#endif
900
reed@android.com8a1c16f2008-12-17 15:59:43 +0000901///////////////////////////////////////////////////////////////////////////////
902
reed@android.com8a1c16f2008-12-17 15:59:43 +0000903#include "SkColorPriv.h"
904#include "SkUnPreMultiply.h"
905
906static void sk_write_fn(png_structp png_ptr, png_bytep data, png_size_t len) {
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000907 SkWStream* sk_stream = (SkWStream*)png_get_io_ptr(png_ptr);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000908 if (!sk_stream->write(data, len)) {
909 png_error(png_ptr, "sk_write_fn Error!");
910 }
911}
912
reed@android.com8a1c16f2008-12-17 15:59:43 +0000913static transform_scanline_proc choose_proc(SkBitmap::Config config,
914 bool hasAlpha) {
915 // we don't care about search on alpha if we're kIndex8, since only the
916 // colortable packing cares about that distinction, not the pixels
917 if (SkBitmap::kIndex8_Config == config) {
918 hasAlpha = false; // we store false in the table entries for kIndex8
919 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000920
reed@android.com8a1c16f2008-12-17 15:59:43 +0000921 static const struct {
922 SkBitmap::Config fConfig;
923 bool fHasAlpha;
924 transform_scanline_proc fProc;
925 } gMap[] = {
926 { SkBitmap::kRGB_565_Config, false, transform_scanline_565 },
927 { SkBitmap::kARGB_8888_Config, false, transform_scanline_888 },
928 { SkBitmap::kARGB_8888_Config, true, transform_scanline_8888 },
929 { SkBitmap::kARGB_4444_Config, false, transform_scanline_444 },
930 { SkBitmap::kARGB_4444_Config, true, transform_scanline_4444 },
epoger@google.com4ce738b2012-11-16 18:44:18 +0000931 { SkBitmap::kIndex8_Config, false, transform_scanline_memcpy },
reed@android.com8a1c16f2008-12-17 15:59:43 +0000932 };
933
934 for (int i = SK_ARRAY_COUNT(gMap) - 1; i >= 0; --i) {
935 if (gMap[i].fConfig == config && gMap[i].fHasAlpha == hasAlpha) {
936 return gMap[i].fProc;
937 }
938 }
939 sk_throw();
940 return NULL;
941}
942
943// return the minimum legal bitdepth (by png standards) for this many colortable
944// entries. SkBitmap always stores in 8bits per pixel, but for colorcount <= 16,
945// we can use fewer bits per in png
946static int computeBitDepth(int colorCount) {
947#if 0
948 int bits = SkNextLog2(colorCount);
949 SkASSERT(bits >= 1 && bits <= 8);
950 // now we need bits itself to be a power of 2 (e.g. 1, 2, 4, 8)
951 return SkNextPow2(bits);
952#else
953 // for the moment, we don't know how to pack bitdepth < 8
954 return 8;
955#endif
956}
957
958/* Pack palette[] with the corresponding colors, and if hasAlpha is true, also
959 pack trans[] and return the number of trans[] entries written. If hasAlpha
960 is false, the return value will always be 0.
rmistry@google.comd6176b02012-08-23 18:14:13 +0000961
reed@android.com8a1c16f2008-12-17 15:59:43 +0000962 Note: this routine takes care of unpremultiplying the RGB values when we
963 have alpha in the colortable, since png doesn't support premul colors
964*/
reed@android.com6f252972009-01-14 16:46:16 +0000965static inline int pack_palette(SkColorTable* ctable,
reed@android.comb50a60c2009-01-14 17:51:08 +0000966 png_color* SK_RESTRICT palette,
967 png_byte* SK_RESTRICT trans, bool hasAlpha) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000968 SkAutoLockColors alc(ctable);
969 const SkPMColor* SK_RESTRICT colors = alc.colors();
970 const int ctCount = ctable->count();
971 int i, num_trans = 0;
972
973 if (hasAlpha) {
974 /* first see if we have some number of fully opaque at the end of the
975 ctable. PNG allows num_trans < num_palette, but all of the trans
976 entries must come first in the palette. If I was smarter, I'd
977 reorder the indices and ctable so that all non-opaque colors came
978 first in the palette. But, since that would slow down the encode,
979 I'm leaving the indices and ctable order as is, and just looking
980 at the tail of the ctable for opaqueness.
981 */
982 num_trans = ctCount;
983 for (i = ctCount - 1; i >= 0; --i) {
984 if (SkGetPackedA32(colors[i]) != 0xFF) {
985 break;
986 }
987 num_trans -= 1;
988 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000989
reed@android.com8a1c16f2008-12-17 15:59:43 +0000990 const SkUnPreMultiply::Scale* SK_RESTRICT table =
991 SkUnPreMultiply::GetScaleTable();
992
993 for (i = 0; i < num_trans; i++) {
994 const SkPMColor c = *colors++;
995 const unsigned a = SkGetPackedA32(c);
996 const SkUnPreMultiply::Scale s = table[a];
997 trans[i] = a;
998 palette[i].red = SkUnPreMultiply::ApplyScale(s, SkGetPackedR32(c));
999 palette[i].green = SkUnPreMultiply::ApplyScale(s,SkGetPackedG32(c));
1000 palette[i].blue = SkUnPreMultiply::ApplyScale(s, SkGetPackedB32(c));
rmistry@google.comd6176b02012-08-23 18:14:13 +00001001 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001002 // now fall out of this if-block to use common code for the trailing
1003 // opaque entries
1004 }
rmistry@google.comd6176b02012-08-23 18:14:13 +00001005
reed@android.com8a1c16f2008-12-17 15:59:43 +00001006 // these (remaining) entries are opaque
1007 for (i = num_trans; i < ctCount; i++) {
1008 SkPMColor c = *colors++;
1009 palette[i].red = SkGetPackedR32(c);
1010 palette[i].green = SkGetPackedG32(c);
1011 palette[i].blue = SkGetPackedB32(c);
1012 }
1013 return num_trans;
1014}
1015
1016class SkPNGImageEncoder : public SkImageEncoder {
1017protected:
commit-bot@chromium.orga936e372013-03-14 14:42:18 +00001018 virtual bool onEncode(SkWStream* stream, const SkBitmap& bm, int quality) SK_OVERRIDE;
tomhudson@google.com5c210c72011-07-28 21:06:40 +00001019private:
1020 bool doEncode(SkWStream* stream, const SkBitmap& bm,
1021 const bool& hasAlpha, int colorType,
1022 int bitDepth, SkBitmap::Config config,
1023 png_color_8& sig_bit);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +00001024
1025 typedef SkImageEncoder INHERITED;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001026};
1027
1028bool SkPNGImageEncoder::onEncode(SkWStream* stream, const SkBitmap& bitmap,
1029 int /*quality*/) {
1030 SkBitmap::Config config = bitmap.getConfig();
1031
1032 const bool hasAlpha = !bitmap.isOpaque();
1033 int colorType = PNG_COLOR_MASK_COLOR;
1034 int bitDepth = 8; // default for color
1035 png_color_8 sig_bit;
1036
1037 switch (config) {
1038 case SkBitmap::kIndex8_Config:
1039 colorType |= PNG_COLOR_MASK_PALETTE;
1040 // fall through to the ARGB_8888 case
1041 case SkBitmap::kARGB_8888_Config:
1042 sig_bit.red = 8;
1043 sig_bit.green = 8;
1044 sig_bit.blue = 8;
1045 sig_bit.alpha = 8;
1046 break;
1047 case SkBitmap::kARGB_4444_Config:
1048 sig_bit.red = 4;
1049 sig_bit.green = 4;
1050 sig_bit.blue = 4;
1051 sig_bit.alpha = 4;
1052 break;
1053 case SkBitmap::kRGB_565_Config:
1054 sig_bit.red = 5;
1055 sig_bit.green = 6;
1056 sig_bit.blue = 5;
1057 sig_bit.alpha = 0;
1058 break;
1059 default:
1060 return false;
1061 }
rmistry@google.comd6176b02012-08-23 18:14:13 +00001062
reed@android.com8a1c16f2008-12-17 15:59:43 +00001063 if (hasAlpha) {
1064 // don't specify alpha if we're a palette, even if our ctable has alpha
1065 if (!(colorType & PNG_COLOR_MASK_PALETTE)) {
1066 colorType |= PNG_COLOR_MASK_ALPHA;
1067 }
1068 } else {
1069 sig_bit.alpha = 0;
1070 }
rmistry@google.comd6176b02012-08-23 18:14:13 +00001071
reed@android.com8a1c16f2008-12-17 15:59:43 +00001072 SkAutoLockPixels alp(bitmap);
1073 // readyToDraw checks for pixels (and colortable if that is required)
1074 if (!bitmap.readyToDraw()) {
1075 return false;
1076 }
1077
1078 // we must do this after we have locked the pixels
1079 SkColorTable* ctable = bitmap.getColorTable();
1080 if (NULL != ctable) {
1081 if (ctable->count() == 0) {
1082 return false;
1083 }
1084 // check if we can store in fewer than 8 bits
1085 bitDepth = computeBitDepth(ctable->count());
1086 }
1087
tomhudson@google.com5c210c72011-07-28 21:06:40 +00001088 return doEncode(stream, bitmap, hasAlpha, colorType,
1089 bitDepth, config, sig_bit);
1090}
1091
1092bool SkPNGImageEncoder::doEncode(SkWStream* stream, const SkBitmap& bitmap,
1093 const bool& hasAlpha, int colorType,
1094 int bitDepth, SkBitmap::Config config,
1095 png_color_8& sig_bit) {
1096
reed@android.com8a1c16f2008-12-17 15:59:43 +00001097 png_structp png_ptr;
1098 png_infop info_ptr;
1099
1100 png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, sk_error_fn,
1101 NULL);
1102 if (NULL == png_ptr) {
1103 return false;
1104 }
1105
1106 info_ptr = png_create_info_struct(png_ptr);
1107 if (NULL == info_ptr) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +00001108 png_destroy_write_struct(&png_ptr, png_infopp_NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001109 return false;
1110 }
1111
1112 /* Set error handling. REQUIRED if you aren't supplying your own
1113 * error handling functions in the png_create_write_struct() call.
1114 */
1115 if (setjmp(png_jmpbuf(png_ptr))) {
1116 png_destroy_write_struct(&png_ptr, &info_ptr);
1117 return false;
1118 }
1119
commit-bot@chromium.orga936e372013-03-14 14:42:18 +00001120 png_set_write_fn(png_ptr, (void*)stream, sk_write_fn, png_flush_ptr_NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001121
1122 /* Set the image information here. Width and height are up to 2^31,
1123 * bit_depth is one of 1, 2, 4, 8, or 16, but valid values also depend on
1124 * the color_type selected. color_type is one of PNG_COLOR_TYPE_GRAY,
1125 * PNG_COLOR_TYPE_GRAY_ALPHA, PNG_COLOR_TYPE_PALETTE, PNG_COLOR_TYPE_RGB,
1126 * or PNG_COLOR_TYPE_RGB_ALPHA. interlace is either PNG_INTERLACE_NONE or
1127 * PNG_INTERLACE_ADAM7, and the compression_type and filter_type MUST
1128 * currently be PNG_COMPRESSION_TYPE_BASE and PNG_FILTER_TYPE_BASE. REQUIRED
1129 */
1130
1131 png_set_IHDR(png_ptr, info_ptr, bitmap.width(), bitmap.height(),
1132 bitDepth, colorType,
1133 PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE,
1134 PNG_FILTER_TYPE_BASE);
1135
reed@android.com61898772009-07-07 19:38:01 +00001136 // set our colortable/trans arrays if needed
1137 png_color paletteColors[256];
1138 png_byte trans[256];
1139 if (SkBitmap::kIndex8_Config == config) {
1140 SkColorTable* ct = bitmap.getColorTable();
1141 int numTrans = pack_palette(ct, paletteColors, trans, hasAlpha);
1142 png_set_PLTE(png_ptr, info_ptr, paletteColors, ct->count());
1143 if (numTrans > 0) {
1144 png_set_tRNS(png_ptr, info_ptr, trans, numTrans, NULL);
1145 }
1146 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001147
1148 png_set_sBIT(png_ptr, info_ptr, &sig_bit);
1149 png_write_info(png_ptr, info_ptr);
1150
1151 const char* srcImage = (const char*)bitmap.getPixels();
1152 SkAutoSMalloc<1024> rowStorage(bitmap.width() << 2);
1153 char* storage = (char*)rowStorage.get();
1154 transform_scanline_proc proc = choose_proc(config, hasAlpha);
1155
1156 for (int y = 0; y < bitmap.height(); y++) {
1157 png_bytep row_ptr = (png_bytep)storage;
1158 proc(srcImage, bitmap.width(), storage);
1159 png_write_rows(png_ptr, &row_ptr, 1);
1160 srcImage += bitmap.rowBytes();
1161 }
1162
1163 png_write_end(png_ptr, info_ptr);
1164
1165 /* clean up after the write, and free any memory allocated */
1166 png_destroy_write_struct(&png_ptr, &info_ptr);
1167 return true;
1168}
1169
reed@android.com00bf85a2009-01-22 13:04:56 +00001170///////////////////////////////////////////////////////////////////////////////
robertphillips@google.comec51cb82012-03-23 18:13:47 +00001171DEFINE_DECODER_CREATOR(PNGImageDecoder);
1172DEFINE_ENCODER_CREATOR(PNGImageEncoder);
1173///////////////////////////////////////////////////////////////////////////////
reed@android.com00bf85a2009-01-22 13:04:56 +00001174
1175#include "SkTRegistry.h"
1176
scroggo@google.com39edf4c2013-04-25 17:33:51 +00001177static bool is_png(SkStream* stream) {
reed@android.com00bf85a2009-01-22 13:04:56 +00001178 char buf[PNG_BYTES_TO_CHECK];
1179 if (stream->read(buf, PNG_BYTES_TO_CHECK) == PNG_BYTES_TO_CHECK &&
1180 !png_sig_cmp((png_bytep) buf, (png_size_t)0, PNG_BYTES_TO_CHECK)) {
scroggo@google.com39edf4c2013-04-25 17:33:51 +00001181 return true;
1182 }
1183 return false;
1184}
1185
1186SkImageDecoder* sk_libpng_dfactory(SkStream* stream) {
1187 if (is_png(stream)) {
reed@android.com00bf85a2009-01-22 13:04:56 +00001188 return SkNEW(SkPNGImageDecoder);
1189 }
1190 return NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001191}
1192
scroggo@google.com39edf4c2013-04-25 17:33:51 +00001193static SkImageDecoder::Format get_format_png(SkStream* stream) {
1194 if (is_png(stream)) {
1195 return SkImageDecoder::kPNG_Format;
1196 }
1197 return SkImageDecoder::kUnknown_Format;
1198}
1199
reed@android.comdfee5792010-04-15 14:24:50 +00001200SkImageEncoder* sk_libpng_efactory(SkImageEncoder::Type t) {
reed@android.com00bf85a2009-01-22 13:04:56 +00001201 return (SkImageEncoder::kPNG_Type == t) ? SkNEW(SkPNGImageEncoder) : NULL;
1202}
1203
reed@android.comdfee5792010-04-15 14:24:50 +00001204static SkTRegistry<SkImageEncoder*, SkImageEncoder::Type> gEReg(sk_libpng_efactory);
scroggo@google.com39edf4c2013-04-25 17:33:51 +00001205static SkTRegistry<SkImageDecoder::Format, SkStream*> gFormatReg(get_format_png);
reed@android.comdfee5792010-04-15 14:24:50 +00001206static SkTRegistry<SkImageDecoder*, SkStream*> gDReg(sk_libpng_dfactory);