blob: 4c44b9109caadc6deba3cbea737c15239dbf7aff [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;
525 SkASSERT(!*hasAlphap);
526 }
527
528 *configp = this->getPrefConfig(srcDepth, *hasAlphap);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000529 // now match the request against our capabilities
530 if (*hasAlphap) {
531 if (*configp != SkBitmap::kARGB_4444_Config) {
532 *configp = SkBitmap::kARGB_8888_Config;
533 }
534 } else {
535 if (*configp != SkBitmap::kRGB_565_Config &&
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000536 *configp != SkBitmap::kARGB_4444_Config &&
537 *configp != SkBitmap::kA8_Config) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000538 *configp = SkBitmap::kARGB_8888_Config;
539 }
540 }
541 }
542
543 // sanity check for size
544 {
545 Sk64 size;
546 size.setMul(origWidth, origHeight);
547 if (size.isNeg() || !size.is32()) {
548 return false;
549 }
550 // now check that if we are 4-bytes per pixel, we also don't overflow
551 if (size.get32() > (0x7FFFFFFF >> 2)) {
552 return false;
553 }
554 }
555
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000556 if (!this->chooseFromOneChoice(*configp, origWidth, origHeight)) {
557 return false;
558 }
559
560 // If the image has alpha and the decoder wants unpremultiplied
561 // colors, the only supported config is 8888.
562 if (this->getRequireUnpremultipliedColors() && *hasAlphap) {
563 *configp = SkBitmap::kARGB_8888_Config;
564 }
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000565
566 if (fImageIndex != NULL) {
567 if (SkBitmap::kNo_Config == fImageIndex->fConfig) {
568 // This is the first time for this subset decode. From now on,
569 // all decodes must be in the same config.
570 fImageIndex->fConfig = *configp;
571 } else if (fImageIndex->fConfig != *configp) {
572 // Requesting a different config for a subsequent decode is not
573 // supported. Report failure before we make changes to png_ptr.
574 return false;
575 }
576 }
577
578 bool convertGrayToRGB = PNG_COLOR_TYPE_GRAY == colorType
579 && *configp != SkBitmap::kA8_Config;
580
581 // Unless the user is requesting A8, convert a grayscale image into RGB.
582 // GRAY_ALPHA will always be converted to RGB
583 if (convertGrayToRGB || colorType == PNG_COLOR_TYPE_GRAY_ALPHA) {
584 png_set_gray_to_rgb(png_ptr);
585 }
586
587 // Add filler (or alpha) byte (after each RGB triplet) if necessary.
588 if (colorType == PNG_COLOR_TYPE_RGB || convertGrayToRGB) {
589 png_set_filler(png_ptr, 0xff, PNG_FILLER_AFTER);
590 }
591
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000592 return true;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000593}
594
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000595typedef uint32_t (*PackColorProc)(U8CPU a, U8CPU r, U8CPU g, U8CPU b);
596
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000597bool SkPNGImageDecoder::decodePalette(png_structp png_ptr, png_infop info_ptr,
598 bool *hasAlphap, bool *reallyHasAlphap,
599 SkColorTable **colorTablep) {
600 int numPalette;
601 png_colorp palette;
602 png_bytep trans;
603 int numTrans;
604 bool reallyHasAlpha = false;
605 SkColorTable* colorTable = NULL;
606
607 png_get_PLTE(png_ptr, info_ptr, &palette, &numPalette);
608
609 /* BUGGY IMAGE WORKAROUND
610
611 We hit some images (e.g. fruit_.png) who contain bytes that are == colortable_count
612 which is a problem since we use the byte as an index. To work around this we grow
613 the colortable by 1 (if its < 256) and duplicate the last color into that slot.
614 */
615 int colorCount = numPalette + (numPalette < 256);
616
617 colorTable = SkNEW_ARGS(SkColorTable, (colorCount));
618
619 SkPMColor* colorPtr = colorTable->lockColors();
620 if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
621 png_get_tRNS(png_ptr, info_ptr, &trans, &numTrans, NULL);
622 *hasAlphap = (numTrans > 0);
623 } else {
624 numTrans = 0;
625 colorTable->setFlags(colorTable->getFlags() | SkColorTable::kColorsAreOpaque_Flag);
626 }
627 // check for bad images that might make us crash
628 if (numTrans > numPalette) {
629 numTrans = numPalette;
630 }
631
632 int index = 0;
633 int transLessThanFF = 0;
634
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000635 // Choose which function to use to create the color table. If the final destination's
636 // config is unpremultiplied, the color table will store unpremultiplied colors.
637 PackColorProc proc;
638 if (this->getRequireUnpremultipliedColors()) {
639 proc = &SkPackARGB32NoCheck;
640 } else {
641 proc = &SkPreMultiplyARGB;
642 }
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000643 for (; index < numTrans; index++) {
644 transLessThanFF |= (int)*trans - 0xFF;
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000645 *colorPtr++ = proc(*trans++, palette->red, palette->green, palette->blue);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000646 palette++;
647 }
648 reallyHasAlpha |= (transLessThanFF < 0);
649
650 for (; index < numPalette; index++) {
651 *colorPtr++ = SkPackARGB32(0xFF, palette->red, palette->green, palette->blue);
652 palette++;
653 }
654
655 // see BUGGY IMAGE WORKAROUND comment above
656 if (numPalette < 256) {
657 *colorPtr = colorPtr[-1];
658 }
659 colorTable->unlockColors(true);
660 *colorTablep = colorTable;
661 *reallyHasAlphap = reallyHasAlpha;
662 return true;
663}
664
665#ifdef SK_BUILD_FOR_ANDROID
666
667bool SkPNGImageDecoder::onBuildTileIndex(SkStream* sk_stream, int *width, int *height) {
668 png_structp png_ptr;
669 png_infop info_ptr;
670
671 if (!onDecodeInit(sk_stream, &png_ptr, &info_ptr)) {
672 return false;
673 }
674
675 if (setjmp(png_jmpbuf(png_ptr)) != 0) {
676 png_destroy_read_struct(&png_ptr, &info_ptr, png_infopp_NULL);
677 return false;
678 }
679
680 png_uint_32 origWidth, origHeight;
681 int bitDepth, colorType;
682 png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
683 &colorType, int_p_NULL, int_p_NULL, int_p_NULL);
684
685 *width = origWidth;
686 *height = origHeight;
687
688 png_build_index(png_ptr);
689
690 if (fImageIndex) {
691 SkDELETE(fImageIndex);
692 }
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000693 fImageIndex = SkNEW_ARGS(SkPNGImageIndex, (sk_stream, png_ptr, info_ptr));
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000694
695 return true;
696}
697
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000698bool SkPNGImageDecoder::onDecodeSubset(SkBitmap* bm, const SkIRect& region) {
699 if (NULL == fImageIndex) {
700 return false;
701 }
702
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000703 png_structp png_ptr = fImageIndex->fPng_ptr;
704 png_infop info_ptr = fImageIndex->fInfo_ptr;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000705 if (setjmp(png_jmpbuf(png_ptr))) {
706 return false;
707 }
708
709 png_uint_32 origWidth, origHeight;
710 int bitDepth, colorType, interlaceType;
711 png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
712 &colorType, &interlaceType, int_p_NULL, int_p_NULL);
713
714 SkIRect rect = SkIRect::MakeWH(origWidth, origHeight);
715
716 if (!rect.intersect(region)) {
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000717 // If the requested region is entirely outside the image, just
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000718 // returns false
719 return false;
720 }
721
722 SkBitmap::Config config;
723 bool hasAlpha = false;
724 bool doDither = this->getDitherImage();
725 SkPMColor theTranspColor = 0; // 0 tells us not to try to match
726
727 if (!getBitmapConfig(png_ptr, info_ptr, &config, &hasAlpha, &doDither, &theTranspColor)) {
728 return false;
729 }
730
731 const int sampleSize = this->getSampleSize();
732 SkScaledBitmapSampler sampler(origWidth, rect.height(), sampleSize);
733
734 SkBitmap decodedBitmap;
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000735 decodedBitmap.setConfig(config, sampler.scaledWidth(), sampler.scaledHeight());
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000736
737 // from here down we are concerned with colortables and pixels
738
739 // we track if we actually see a non-opaque pixels, since sometimes a PNG sets its colortype
740 // to |= PNG_COLOR_MASK_ALPHA, but all of its pixels are in fact opaque. We care, since we
741 // draw lots faster if we can flag the bitmap has being opaque
742 bool reallyHasAlpha = false;
743 SkColorTable* colorTable = NULL;
744
745 if (colorType == PNG_COLOR_TYPE_PALETTE) {
746 decodePalette(png_ptr, info_ptr, &hasAlpha, &reallyHasAlpha, &colorTable);
747 }
748
749 SkAutoUnref aur(colorTable);
750
751 // Check ahead of time if the swap(dest, src) is possible.
752 // If yes, then we will stick to AllocPixelRef since it's cheaper with the swap happening.
753 // If no, then we will use alloc to allocate pixels to prevent garbage collection.
754 int w = rect.width() / sampleSize;
755 int h = rect.height() / sampleSize;
756 const bool swapOnly = (rect == region) && (w == decodedBitmap.width()) &&
757 (h == decodedBitmap.height()) && bm->isNull();
758 const bool needColorTable = SkBitmap::kIndex8_Config == config;
759 if (swapOnly) {
760 if (!this->allocPixelRef(&decodedBitmap, needColorTable ? colorTable : NULL)) {
761 return false;
762 }
763 } else {
764 if (!decodedBitmap.allocPixels(NULL, needColorTable ? colorTable : NULL)) {
765 return false;
766 }
767 }
768 SkAutoLockPixels alp(decodedBitmap);
769
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000770 /* Turn on interlace handling. REQUIRED if you are not using
771 * png_read_image(). To see how to handle interlacing passes,
772 * see the png_read_row() method below:
773 */
774 const int number_passes = (interlaceType != PNG_INTERLACE_NONE) ?
775 png_set_interlace_handling(png_ptr) : 1;
776
777 /* Optional call to gamma correct and add the background to the palette
778 * and update info structure. REQUIRED if you are expecting libpng to
779 * update the palette for you (ie you selected such a transform above).
780 */
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000781
782 // Direct access to png_ptr fields is deprecated in libpng > 1.2.
783#if defined(PNG_1_0_X) || defined (PNG_1_2_X)
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000784 png_ptr->pass = 0;
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000785#else
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000786 // FIXME: This sets pass as desired, but also sets iwidth. Is that ok?
787 png_set_interlaced_pass(png_ptr, 0);
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000788#endif
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000789 png_read_update_info(png_ptr, info_ptr);
790
791 int actualTop = rect.fTop;
792
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000793 if ((SkBitmap::kA8_Config == config || SkBitmap::kIndex8_Config == config)
794 && 1 == sampleSize) {
795 // A8 is only allowed if the original was GRAY.
796 SkASSERT(config != SkBitmap::kA8_Config
797 || PNG_COLOR_TYPE_GRAY == colorType);
798
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000799 for (int i = 0; i < number_passes; i++) {
800 png_configure_decoder(png_ptr, &actualTop, i);
801 for (int j = 0; j < rect.fTop - actualTop; j++) {
802 uint8_t* bmRow = decodedBitmap.getAddr8(0, 0);
803 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
804 }
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000805 png_uint_32 bitmapHeight = (png_uint_32) decodedBitmap.height();
806 for (png_uint_32 y = 0; y < bitmapHeight; y++) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000807 uint8_t* bmRow = decodedBitmap.getAddr8(0, y);
808 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
809 }
810 }
811 } else {
812 SkScaledBitmapSampler::SrcConfig sc;
813 int srcBytesPerPixel = 4;
814
815 if (colorTable != NULL) {
816 sc = SkScaledBitmapSampler::kIndex;
817 srcBytesPerPixel = 1;
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000818 } else if (SkBitmap::kA8_Config == config) {
819 // A8 is only allowed if the original was GRAY.
820 SkASSERT(PNG_COLOR_TYPE_GRAY == colorType);
821 sc = SkScaledBitmapSampler::kGray;
822 srcBytesPerPixel = 1;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000823 } else if (hasAlpha) {
824 sc = SkScaledBitmapSampler::kRGBA;
825 } else {
826 sc = SkScaledBitmapSampler::kRGBX;
827 }
828
829 /* We have to pass the colortable explicitly, since we may have one
830 even if our decodedBitmap doesn't, due to the request that we
831 upscale png's palette to a direct model
832 */
833 SkAutoLockColors ctLock(colorTable);
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000834 if (!sampler.begin(&decodedBitmap, sc, doDither, ctLock.colors(),
835 this->getRequireUnpremultipliedColors())) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000836 return false;
837 }
838 const int height = decodedBitmap.height();
839
840 if (number_passes > 1) {
841 SkAutoMalloc storage(origWidth * origHeight * srcBytesPerPixel);
842 uint8_t* base = (uint8_t*)storage.get();
843 size_t rb = origWidth * srcBytesPerPixel;
844
845 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 = (uint8_t*)decodedBitmap.getPixels();
849 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
850 }
851 uint8_t* row = base;
852 for (int32_t y = 0; y < rect.height(); y++) {
853 uint8_t* bmRow = row;
854 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
855 row += rb;
856 }
857 }
858 // now sample it
859 base += sampler.srcY0() * rb;
860 for (int y = 0; y < height; y++) {
861 reallyHasAlpha |= sampler.next(base);
862 base += sampler.srcDY() * rb;
863 }
864 } else {
865 SkAutoMalloc storage(origWidth * srcBytesPerPixel);
866 uint8_t* srcRow = (uint8_t*)storage.get();
867
868 png_configure_decoder(png_ptr, &actualTop, 0);
869 skip_src_rows(png_ptr, srcRow, sampler.srcY0());
870
871 for (int i = 0; i < rect.fTop - actualTop; i++) {
872 uint8_t* bmRow = (uint8_t*)decodedBitmap.getPixels();
873 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
874 }
875 for (int y = 0; y < height; y++) {
876 uint8_t* tmp = srcRow;
877 png_read_rows(png_ptr, &tmp, png_bytepp_NULL, 1);
878 reallyHasAlpha |= sampler.next(srcRow);
879 if (y < height - 1) {
880 skip_src_rows(png_ptr, srcRow, sampler.srcDY() - 1);
881 }
882 }
883 }
884 }
885
886 if (0 != theTranspColor) {
887 reallyHasAlpha |= substituteTranspColor(&decodedBitmap, theTranspColor);
888 }
889 decodedBitmap.setIsOpaque(!reallyHasAlpha);
890
891 if (swapOnly) {
892 bm->swap(decodedBitmap);
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000893 return true;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000894 }
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000895 return this->cropBitmap(bm, &decodedBitmap, sampleSize, region.x(), region.y(),
896 region.width(), region.height(), 0, rect.y());
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000897}
898#endif
899
reed@android.com8a1c16f2008-12-17 15:59:43 +0000900///////////////////////////////////////////////////////////////////////////////
901
reed@android.com8a1c16f2008-12-17 15:59:43 +0000902#include "SkColorPriv.h"
903#include "SkUnPreMultiply.h"
904
905static void sk_write_fn(png_structp png_ptr, png_bytep data, png_size_t len) {
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000906 SkWStream* sk_stream = (SkWStream*)png_get_io_ptr(png_ptr);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000907 if (!sk_stream->write(data, len)) {
908 png_error(png_ptr, "sk_write_fn Error!");
909 }
910}
911
reed@android.com8a1c16f2008-12-17 15:59:43 +0000912static transform_scanline_proc choose_proc(SkBitmap::Config config,
913 bool hasAlpha) {
914 // we don't care about search on alpha if we're kIndex8, since only the
915 // colortable packing cares about that distinction, not the pixels
916 if (SkBitmap::kIndex8_Config == config) {
917 hasAlpha = false; // we store false in the table entries for kIndex8
918 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000919
reed@android.com8a1c16f2008-12-17 15:59:43 +0000920 static const struct {
921 SkBitmap::Config fConfig;
922 bool fHasAlpha;
923 transform_scanline_proc fProc;
924 } gMap[] = {
925 { SkBitmap::kRGB_565_Config, false, transform_scanline_565 },
926 { SkBitmap::kARGB_8888_Config, false, transform_scanline_888 },
927 { SkBitmap::kARGB_8888_Config, true, transform_scanline_8888 },
928 { SkBitmap::kARGB_4444_Config, false, transform_scanline_444 },
929 { SkBitmap::kARGB_4444_Config, true, transform_scanline_4444 },
epoger@google.com4ce738b2012-11-16 18:44:18 +0000930 { SkBitmap::kIndex8_Config, false, transform_scanline_memcpy },
reed@android.com8a1c16f2008-12-17 15:59:43 +0000931 };
932
933 for (int i = SK_ARRAY_COUNT(gMap) - 1; i >= 0; --i) {
934 if (gMap[i].fConfig == config && gMap[i].fHasAlpha == hasAlpha) {
935 return gMap[i].fProc;
936 }
937 }
938 sk_throw();
939 return NULL;
940}
941
942// return the minimum legal bitdepth (by png standards) for this many colortable
943// entries. SkBitmap always stores in 8bits per pixel, but for colorcount <= 16,
944// we can use fewer bits per in png
945static int computeBitDepth(int colorCount) {
946#if 0
947 int bits = SkNextLog2(colorCount);
948 SkASSERT(bits >= 1 && bits <= 8);
949 // now we need bits itself to be a power of 2 (e.g. 1, 2, 4, 8)
950 return SkNextPow2(bits);
951#else
952 // for the moment, we don't know how to pack bitdepth < 8
953 return 8;
954#endif
955}
956
957/* Pack palette[] with the corresponding colors, and if hasAlpha is true, also
958 pack trans[] and return the number of trans[] entries written. If hasAlpha
959 is false, the return value will always be 0.
rmistry@google.comd6176b02012-08-23 18:14:13 +0000960
reed@android.com8a1c16f2008-12-17 15:59:43 +0000961 Note: this routine takes care of unpremultiplying the RGB values when we
962 have alpha in the colortable, since png doesn't support premul colors
963*/
reed@android.com6f252972009-01-14 16:46:16 +0000964static inline int pack_palette(SkColorTable* ctable,
reed@android.comb50a60c2009-01-14 17:51:08 +0000965 png_color* SK_RESTRICT palette,
966 png_byte* SK_RESTRICT trans, bool hasAlpha) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000967 SkAutoLockColors alc(ctable);
968 const SkPMColor* SK_RESTRICT colors = alc.colors();
969 const int ctCount = ctable->count();
970 int i, num_trans = 0;
971
972 if (hasAlpha) {
973 /* first see if we have some number of fully opaque at the end of the
974 ctable. PNG allows num_trans < num_palette, but all of the trans
975 entries must come first in the palette. If I was smarter, I'd
976 reorder the indices and ctable so that all non-opaque colors came
977 first in the palette. But, since that would slow down the encode,
978 I'm leaving the indices and ctable order as is, and just looking
979 at the tail of the ctable for opaqueness.
980 */
981 num_trans = ctCount;
982 for (i = ctCount - 1; i >= 0; --i) {
983 if (SkGetPackedA32(colors[i]) != 0xFF) {
984 break;
985 }
986 num_trans -= 1;
987 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000988
reed@android.com8a1c16f2008-12-17 15:59:43 +0000989 const SkUnPreMultiply::Scale* SK_RESTRICT table =
990 SkUnPreMultiply::GetScaleTable();
991
992 for (i = 0; i < num_trans; i++) {
993 const SkPMColor c = *colors++;
994 const unsigned a = SkGetPackedA32(c);
995 const SkUnPreMultiply::Scale s = table[a];
996 trans[i] = a;
997 palette[i].red = SkUnPreMultiply::ApplyScale(s, SkGetPackedR32(c));
998 palette[i].green = SkUnPreMultiply::ApplyScale(s,SkGetPackedG32(c));
999 palette[i].blue = SkUnPreMultiply::ApplyScale(s, SkGetPackedB32(c));
rmistry@google.comd6176b02012-08-23 18:14:13 +00001000 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001001 // now fall out of this if-block to use common code for the trailing
1002 // opaque entries
1003 }
rmistry@google.comd6176b02012-08-23 18:14:13 +00001004
reed@android.com8a1c16f2008-12-17 15:59:43 +00001005 // these (remaining) entries are opaque
1006 for (i = num_trans; i < ctCount; i++) {
1007 SkPMColor c = *colors++;
1008 palette[i].red = SkGetPackedR32(c);
1009 palette[i].green = SkGetPackedG32(c);
1010 palette[i].blue = SkGetPackedB32(c);
1011 }
1012 return num_trans;
1013}
1014
1015class SkPNGImageEncoder : public SkImageEncoder {
1016protected:
commit-bot@chromium.orga936e372013-03-14 14:42:18 +00001017 virtual bool onEncode(SkWStream* stream, const SkBitmap& bm, int quality) SK_OVERRIDE;
tomhudson@google.com5c210c72011-07-28 21:06:40 +00001018private:
1019 bool doEncode(SkWStream* stream, const SkBitmap& bm,
1020 const bool& hasAlpha, int colorType,
1021 int bitDepth, SkBitmap::Config config,
1022 png_color_8& sig_bit);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +00001023
1024 typedef SkImageEncoder INHERITED;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001025};
1026
1027bool SkPNGImageEncoder::onEncode(SkWStream* stream, const SkBitmap& bitmap,
1028 int /*quality*/) {
1029 SkBitmap::Config config = bitmap.getConfig();
1030
1031 const bool hasAlpha = !bitmap.isOpaque();
1032 int colorType = PNG_COLOR_MASK_COLOR;
1033 int bitDepth = 8; // default for color
1034 png_color_8 sig_bit;
1035
1036 switch (config) {
1037 case SkBitmap::kIndex8_Config:
1038 colorType |= PNG_COLOR_MASK_PALETTE;
1039 // fall through to the ARGB_8888 case
1040 case SkBitmap::kARGB_8888_Config:
1041 sig_bit.red = 8;
1042 sig_bit.green = 8;
1043 sig_bit.blue = 8;
1044 sig_bit.alpha = 8;
1045 break;
1046 case SkBitmap::kARGB_4444_Config:
1047 sig_bit.red = 4;
1048 sig_bit.green = 4;
1049 sig_bit.blue = 4;
1050 sig_bit.alpha = 4;
1051 break;
1052 case SkBitmap::kRGB_565_Config:
1053 sig_bit.red = 5;
1054 sig_bit.green = 6;
1055 sig_bit.blue = 5;
1056 sig_bit.alpha = 0;
1057 break;
1058 default:
1059 return false;
1060 }
rmistry@google.comd6176b02012-08-23 18:14:13 +00001061
reed@android.com8a1c16f2008-12-17 15:59:43 +00001062 if (hasAlpha) {
1063 // don't specify alpha if we're a palette, even if our ctable has alpha
1064 if (!(colorType & PNG_COLOR_MASK_PALETTE)) {
1065 colorType |= PNG_COLOR_MASK_ALPHA;
1066 }
1067 } else {
1068 sig_bit.alpha = 0;
1069 }
rmistry@google.comd6176b02012-08-23 18:14:13 +00001070
reed@android.com8a1c16f2008-12-17 15:59:43 +00001071 SkAutoLockPixels alp(bitmap);
1072 // readyToDraw checks for pixels (and colortable if that is required)
1073 if (!bitmap.readyToDraw()) {
1074 return false;
1075 }
1076
1077 // we must do this after we have locked the pixels
1078 SkColorTable* ctable = bitmap.getColorTable();
1079 if (NULL != ctable) {
1080 if (ctable->count() == 0) {
1081 return false;
1082 }
1083 // check if we can store in fewer than 8 bits
1084 bitDepth = computeBitDepth(ctable->count());
1085 }
1086
tomhudson@google.com5c210c72011-07-28 21:06:40 +00001087 return doEncode(stream, bitmap, hasAlpha, colorType,
1088 bitDepth, config, sig_bit);
1089}
1090
1091bool SkPNGImageEncoder::doEncode(SkWStream* stream, const SkBitmap& bitmap,
1092 const bool& hasAlpha, int colorType,
1093 int bitDepth, SkBitmap::Config config,
1094 png_color_8& sig_bit) {
1095
reed@android.com8a1c16f2008-12-17 15:59:43 +00001096 png_structp png_ptr;
1097 png_infop info_ptr;
1098
1099 png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, sk_error_fn,
1100 NULL);
1101 if (NULL == png_ptr) {
1102 return false;
1103 }
1104
1105 info_ptr = png_create_info_struct(png_ptr);
1106 if (NULL == info_ptr) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +00001107 png_destroy_write_struct(&png_ptr, png_infopp_NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001108 return false;
1109 }
1110
1111 /* Set error handling. REQUIRED if you aren't supplying your own
1112 * error handling functions in the png_create_write_struct() call.
1113 */
1114 if (setjmp(png_jmpbuf(png_ptr))) {
1115 png_destroy_write_struct(&png_ptr, &info_ptr);
1116 return false;
1117 }
1118
commit-bot@chromium.orga936e372013-03-14 14:42:18 +00001119 png_set_write_fn(png_ptr, (void*)stream, sk_write_fn, png_flush_ptr_NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001120
1121 /* Set the image information here. Width and height are up to 2^31,
1122 * bit_depth is one of 1, 2, 4, 8, or 16, but valid values also depend on
1123 * the color_type selected. color_type is one of PNG_COLOR_TYPE_GRAY,
1124 * PNG_COLOR_TYPE_GRAY_ALPHA, PNG_COLOR_TYPE_PALETTE, PNG_COLOR_TYPE_RGB,
1125 * or PNG_COLOR_TYPE_RGB_ALPHA. interlace is either PNG_INTERLACE_NONE or
1126 * PNG_INTERLACE_ADAM7, and the compression_type and filter_type MUST
1127 * currently be PNG_COMPRESSION_TYPE_BASE and PNG_FILTER_TYPE_BASE. REQUIRED
1128 */
1129
1130 png_set_IHDR(png_ptr, info_ptr, bitmap.width(), bitmap.height(),
1131 bitDepth, colorType,
1132 PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE,
1133 PNG_FILTER_TYPE_BASE);
1134
reed@android.com61898772009-07-07 19:38:01 +00001135 // set our colortable/trans arrays if needed
1136 png_color paletteColors[256];
1137 png_byte trans[256];
1138 if (SkBitmap::kIndex8_Config == config) {
1139 SkColorTable* ct = bitmap.getColorTable();
1140 int numTrans = pack_palette(ct, paletteColors, trans, hasAlpha);
1141 png_set_PLTE(png_ptr, info_ptr, paletteColors, ct->count());
1142 if (numTrans > 0) {
1143 png_set_tRNS(png_ptr, info_ptr, trans, numTrans, NULL);
1144 }
1145 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001146
1147 png_set_sBIT(png_ptr, info_ptr, &sig_bit);
1148 png_write_info(png_ptr, info_ptr);
1149
1150 const char* srcImage = (const char*)bitmap.getPixels();
1151 SkAutoSMalloc<1024> rowStorage(bitmap.width() << 2);
1152 char* storage = (char*)rowStorage.get();
1153 transform_scanline_proc proc = choose_proc(config, hasAlpha);
1154
1155 for (int y = 0; y < bitmap.height(); y++) {
1156 png_bytep row_ptr = (png_bytep)storage;
1157 proc(srcImage, bitmap.width(), storage);
1158 png_write_rows(png_ptr, &row_ptr, 1);
1159 srcImage += bitmap.rowBytes();
1160 }
1161
1162 png_write_end(png_ptr, info_ptr);
1163
1164 /* clean up after the write, and free any memory allocated */
1165 png_destroy_write_struct(&png_ptr, &info_ptr);
1166 return true;
1167}
1168
reed@android.com00bf85a2009-01-22 13:04:56 +00001169///////////////////////////////////////////////////////////////////////////////
robertphillips@google.comec51cb82012-03-23 18:13:47 +00001170DEFINE_DECODER_CREATOR(PNGImageDecoder);
1171DEFINE_ENCODER_CREATOR(PNGImageEncoder);
1172///////////////////////////////////////////////////////////////////////////////
reed@android.com00bf85a2009-01-22 13:04:56 +00001173
1174#include "SkTRegistry.h"
1175
scroggo@google.com39edf4c2013-04-25 17:33:51 +00001176static bool is_png(SkStream* stream) {
reed@android.com00bf85a2009-01-22 13:04:56 +00001177 char buf[PNG_BYTES_TO_CHECK];
1178 if (stream->read(buf, PNG_BYTES_TO_CHECK) == PNG_BYTES_TO_CHECK &&
1179 !png_sig_cmp((png_bytep) buf, (png_size_t)0, PNG_BYTES_TO_CHECK)) {
scroggo@google.com39edf4c2013-04-25 17:33:51 +00001180 return true;
1181 }
1182 return false;
1183}
1184
1185SkImageDecoder* sk_libpng_dfactory(SkStream* stream) {
1186 if (is_png(stream)) {
reed@android.com00bf85a2009-01-22 13:04:56 +00001187 return SkNEW(SkPNGImageDecoder);
1188 }
1189 return NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001190}
1191
scroggo@google.com39edf4c2013-04-25 17:33:51 +00001192static SkImageDecoder::Format get_format_png(SkStream* stream) {
1193 if (is_png(stream)) {
1194 return SkImageDecoder::kPNG_Format;
1195 }
1196 return SkImageDecoder::kUnknown_Format;
1197}
1198
reed@android.comdfee5792010-04-15 14:24:50 +00001199SkImageEncoder* sk_libpng_efactory(SkImageEncoder::Type t) {
reed@android.com00bf85a2009-01-22 13:04:56 +00001200 return (SkImageEncoder::kPNG_Type == t) ? SkNEW(SkPNGImageEncoder) : NULL;
1201}
1202
reed@android.comdfee5792010-04-15 14:24:50 +00001203static SkTRegistry<SkImageEncoder*, SkImageEncoder::Type> gEReg(sk_libpng_efactory);
scroggo@google.com39edf4c2013-04-25 17:33:51 +00001204static SkTRegistry<SkImageDecoder::Format, SkStream*> gFormatReg(get_format_png);
reed@android.comdfee5792010-04-15 14:24:50 +00001205static SkTRegistry<SkImageDecoder*, SkStream*> gDReg(sk_libpng_dfactory);