blob: d967a69e84668c502ecf1bbd2c37dead68776447 [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:
45 SkPNGImageIndex(png_structp png_ptr, png_infop info_ptr) {
46 this->png_ptr = png_ptr;
47 this->info_ptr = info_ptr;
48 }
49 ~SkPNGImageIndex() {
50 if (NULL != png_ptr) {
51 png_destroy_read_struct(&png_ptr, &info_ptr, png_infopp_NULL);
52 }
53 }
54
55 png_structp png_ptr;
56 png_infop info_ptr;
57};
58
reed@android.com8a1c16f2008-12-17 15:59:43 +000059class SkPNGImageDecoder : public SkImageDecoder {
60public:
commit-bot@chromium.orga936e372013-03-14 14:42:18 +000061 SkPNGImageDecoder() {
62 fImageIndex = NULL;
63 }
64 virtual Format getFormat() const SK_OVERRIDE {
reed@android.com8a1c16f2008-12-17 15:59:43 +000065 return kPNG_Format;
66 }
scroggo@google.com39edf4c2013-04-25 17:33:51 +000067
commit-bot@chromium.orga936e372013-03-14 14:42:18 +000068 virtual ~SkPNGImageDecoder() {
69 SkDELETE(fImageIndex);
70 }
rmistry@google.comd6176b02012-08-23 18:14:13 +000071
reed@android.com8a1c16f2008-12-17 15:59:43 +000072protected:
commit-bot@chromium.orga936e372013-03-14 14:42:18 +000073#ifdef SK_BUILD_FOR_ANDROID
74 virtual bool onBuildTileIndex(SkStream *stream, int *width, int *height) SK_OVERRIDE;
scroggo@google.com7e6fcee2013-05-03 20:14:28 +000075 virtual bool onDecodeSubset(SkBitmap* bitmap, const SkIRect& region) SK_OVERRIDE;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +000076#endif
77 virtual bool onDecode(SkStream* stream, SkBitmap* bm, Mode) SK_OVERRIDE;
78
79private:
80 SkPNGImageIndex* fImageIndex;
81
82 bool onDecodeInit(SkStream* stream, png_structp *png_ptrp, png_infop *info_ptrp);
83 bool decodePalette(png_structp png_ptr, png_infop info_ptr, bool *hasAlphap,
84 bool *reallyHasAlphap, SkColorTable **colorTablep);
85 bool getBitmapConfig(png_structp png_ptr, png_infop info_ptr,
86 SkBitmap::Config *config, bool *hasAlpha,
87 bool *doDither, SkPMColor *theTranspColor);
88
89 typedef SkImageDecoder INHERITED;
reed@android.com8a1c16f2008-12-17 15:59:43 +000090};
91
92#ifndef png_jmpbuf
93# define png_jmpbuf(png_ptr) ((png_ptr)->jmpbuf)
94#endif
95
96#define PNG_BYTES_TO_CHECK 4
97
98/* Automatically clean up after throwing an exception */
99struct PNGAutoClean {
100 PNGAutoClean(png_structp p, png_infop i): png_ptr(p), info_ptr(i) {}
101 ~PNGAutoClean() {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000102 png_destroy_read_struct(&png_ptr, &info_ptr, png_infopp_NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000103 }
104private:
105 png_structp png_ptr;
106 png_infop info_ptr;
107};
108
reed@android.com8a1c16f2008-12-17 15:59:43 +0000109static void sk_read_fn(png_structp png_ptr, png_bytep data, png_size_t length) {
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000110 SkStream* sk_stream = (SkStream*) png_get_io_ptr(png_ptr);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000111 size_t bytes = sk_stream->read(data, length);
112 if (bytes != length) {
113 png_error(png_ptr, "Read Error!");
114 }
115}
116
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000117#ifdef SK_BUILD_FOR_ANDROID
118static void sk_seek_fn(png_structp png_ptr, png_uint_32 offset) {
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000119 SkStream* sk_stream = (SkStream*) png_get_io_ptr(png_ptr);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000120 sk_stream->rewind();
121 (void)sk_stream->skip(offset);
122}
123#endif
124
reed@android.com8a1c16f2008-12-17 15:59:43 +0000125static int sk_read_user_chunk(png_structp png_ptr, png_unknown_chunkp chunk) {
126 SkImageDecoder::Peeker* peeker =
127 (SkImageDecoder::Peeker*)png_get_user_chunk_ptr(png_ptr);
128 // peek() returning true means continue decoding
129 return peeker->peek((const char*)chunk->name, chunk->data, chunk->size) ?
130 1 : -1;
131}
132
133static void sk_error_fn(png_structp png_ptr, png_const_charp msg) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000134 SkDEBUGF(("------ png error %s\n", msg));
reed@android.com8a1c16f2008-12-17 15:59:43 +0000135 longjmp(png_jmpbuf(png_ptr), 1);
136}
137
138static void skip_src_rows(png_structp png_ptr, uint8_t storage[], int count) {
139 for (int i = 0; i < count; i++) {
140 uint8_t* tmp = storage;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000141 png_read_rows(png_ptr, &tmp, png_bytepp_NULL, 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000142 }
143}
144
145static bool pos_le(int value, int max) {
146 return value > 0 && value <= max;
147}
148
149static bool substituteTranspColor(SkBitmap* bm, SkPMColor match) {
150 SkASSERT(bm->config() == SkBitmap::kARGB_8888_Config);
rmistry@google.comd6176b02012-08-23 18:14:13 +0000151
reed@android.com8a1c16f2008-12-17 15:59:43 +0000152 bool reallyHasAlpha = false;
153
154 for (int y = bm->height() - 1; y >= 0; --y) {
155 SkPMColor* p = bm->getAddr32(0, y);
156 for (int x = bm->width() - 1; x >= 0; --x) {
157 if (match == *p) {
158 *p = 0;
159 reallyHasAlpha = true;
160 }
161 p += 1;
162 }
163 }
164 return reallyHasAlpha;
165}
166
reed@android.com3f1f06a2010-03-03 21:04:12 +0000167static bool canUpscalePaletteToConfig(SkBitmap::Config dstConfig,
reed@android.comb6137c32009-07-29 20:56:52 +0000168 bool srcHasAlpha) {
reed@android.com3f1f06a2010-03-03 21:04:12 +0000169 switch (dstConfig) {
reed@android.comb6137c32009-07-29 20:56:52 +0000170 case SkBitmap::kARGB_8888_Config:
171 case SkBitmap::kARGB_4444_Config:
172 return true;
173 case SkBitmap::kRGB_565_Config:
174 // only return true if the src is opaque (since 565 is opaque)
175 return !srcHasAlpha;
176 default:
177 return false;
178 }
179}
180
181// call only if color_type is PALETTE. Returns true if the ctable has alpha
182static bool hasTransparencyInPalette(png_structp png_ptr, png_infop info_ptr) {
183 png_bytep trans;
184 int num_trans;
185
186 if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
187 png_get_tRNS(png_ptr, info_ptr, &trans, &num_trans, NULL);
188 return num_trans > 0;
189 }
190 return false;
reed@android.com11344262009-07-08 20:09:23 +0000191}
192
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000193bool SkPNGImageDecoder::onDecodeInit(SkStream* sk_stream, png_structp *png_ptrp,
194 png_infop *info_ptrp) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000195 /* Create and initialize the png_struct with the desired error handler
196 * functions. If you want to use the default stderr and longjump method,
197 * you can supply NULL for the last three parameters. We also supply the
198 * the compiler header file version, so that we know if the application
199 * was compiled with a compatible version of the library. */
200 png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
201 NULL, sk_error_fn, NULL);
202 // png_voidp user_error_ptr, user_error_fn, user_warning_fn);
203 if (png_ptr == NULL) {
204 return false;
205 }
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000206 *png_ptrp = png_ptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000207
208 /* Allocate/initialize the memory for image information. */
209 png_infop info_ptr = png_create_info_struct(png_ptr);
210 if (info_ptr == NULL) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000211 png_destroy_read_struct(&png_ptr, png_infopp_NULL, png_infopp_NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000212 return false;
213 }
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000214 *info_ptrp = info_ptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000215
216 /* Set error handling if you are using the setjmp/longjmp method (this is
217 * the normal method of doing things with libpng). REQUIRED unless you
218 * set up your own error handlers in the png_create_read_struct() earlier.
219 */
220 if (setjmp(png_jmpbuf(png_ptr))) {
221 return false;
222 }
223
224 /* If you are using replacement read functions, instead of calling
225 * png_init_io() here you would call:
226 */
227 png_set_read_fn(png_ptr, (void *)sk_stream, sk_read_fn);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000228#ifdef SK_BUILD_FOR_ANDROID
229 png_set_seek_fn(png_ptr, sk_seek_fn);
230#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000231 /* where user_io_ptr is a structure you want available to the callbacks */
232 /* If we have already read some of the signature */
233// png_set_sig_bytes(png_ptr, 0 /* sig_read */ );
234
235 // hookup our peeker so we can see any user-chunks the caller may be interested in
236 png_set_keep_unknown_chunks(png_ptr, PNG_HANDLE_CHUNK_ALWAYS, (png_byte*)"", 0);
237 if (this->getPeeker()) {
238 png_set_read_user_chunk_fn(png_ptr, (png_voidp)this->getPeeker(), sk_read_user_chunk);
239 }
240
241 /* The call to png_read_info() gives us all of the information from the
242 * PNG file before the first IDAT (image data chunk). */
243 png_read_info(png_ptr, info_ptr);
244 png_uint_32 origWidth, origHeight;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000245 int bitDepth, colorType;
246 png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
247 &colorType, int_p_NULL, int_p_NULL, int_p_NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000248
249 /* tell libpng to strip 16 bit/color files down to 8 bits/color */
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000250 if (bitDepth == 16) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000251 png_set_strip_16(png_ptr);
252 }
253 /* Extract multiple pixels with bit depths of 1, 2, and 4 from a single
254 * byte into separate bytes (useful for paletted and grayscale images). */
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000255 if (bitDepth < 8) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000256 png_set_packing(png_ptr);
257 }
258 /* Expand grayscale images to the full 8 bits from 1, 2, or 4 bits/pixel */
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000259 if (colorType == PNG_COLOR_TYPE_GRAY && bitDepth < 8) {
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000260 png_set_expand_gray_1_2_4_to_8(png_ptr);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000261 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000262
reed@android.com8a1c16f2008-12-17 15:59:43 +0000263 /* Make a grayscale image into RGB. */
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000264 if (colorType == PNG_COLOR_TYPE_GRAY || colorType == PNG_COLOR_TYPE_GRAY_ALPHA) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000265 png_set_gray_to_rgb(png_ptr);
266 }
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000267 return true;
268}
269
270bool SkPNGImageDecoder::onDecode(SkStream* sk_stream, SkBitmap* decodedBitmap,
271 Mode mode) {
272 png_structp png_ptr;
273 png_infop info_ptr;
274
275 if (!onDecodeInit(sk_stream, &png_ptr, &info_ptr)) {
276 return false;
277 }
278
279 if (setjmp(png_jmpbuf(png_ptr))) {
280 return false;
281 }
282
283 PNGAutoClean autoClean(png_ptr, info_ptr);
284
285 png_uint_32 origWidth, origHeight;
286 int bitDepth, colorType, interlaceType;
287 png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
288 &colorType, &interlaceType, int_p_NULL, int_p_NULL);
rmistry@google.comd6176b02012-08-23 18:14:13 +0000289
reed@android.com8a1c16f2008-12-17 15:59:43 +0000290 SkBitmap::Config config;
291 bool hasAlpha = false;
292 bool doDither = this->getDitherImage();
293 SkPMColor theTranspColor = 0; // 0 tells us not to try to match
rmistry@google.comd6176b02012-08-23 18:14:13 +0000294
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000295 if (!getBitmapConfig(png_ptr, info_ptr, &config, &hasAlpha, &doDither, &theTranspColor)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000296 return false;
297 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000298
reed@android.com8a1c16f2008-12-17 15:59:43 +0000299 const int sampleSize = this->getSampleSize();
300 SkScaledBitmapSampler sampler(origWidth, origHeight, sampleSize);
301
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000302 decodedBitmap->lockPixels();
303 void* rowptr = (void*) decodedBitmap->getPixels();
304 bool reuseBitmap = (rowptr != NULL);
305 decodedBitmap->unlockPixels();
306 if (reuseBitmap && (sampler.scaledWidth() != decodedBitmap->width() ||
307 sampler.scaledHeight() != decodedBitmap->height())) {
308 // Dimensions must match
309 return false;
310 }
311
312 if (!reuseBitmap) {
313 decodedBitmap->setConfig(config, sampler.scaledWidth(),
314 sampler.scaledHeight(), 0);
315 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000316 if (SkImageDecoder::kDecodeBounds_Mode == mode) {
317 return true;
318 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000319
reed@android.com8a1c16f2008-12-17 15:59:43 +0000320 // from here down we are concerned with colortables and pixels
321
322 // we track if we actually see a non-opaque pixels, since sometimes a PNG sets its colortype
323 // to |= PNG_COLOR_MASK_ALPHA, but all of its pixels are in fact opaque. We care, since we
324 // draw lots faster if we can flag the bitmap has being opaque
325 bool reallyHasAlpha = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000326 SkColorTable* colorTable = NULL;
327
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000328 if (colorType == PNG_COLOR_TYPE_PALETTE) {
329 decodePalette(png_ptr, info_ptr, &hasAlpha, &reallyHasAlpha, &colorTable);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000330 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000331
reed@android.com8a1c16f2008-12-17 15:59:43 +0000332 SkAutoUnref aur(colorTable);
333
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000334 if (!reuseBitmap) {
335 if (!this->allocPixelRef(decodedBitmap,
336 SkBitmap::kIndex8_Config == config ? colorTable : NULL)) {
337 return false;
338 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000339 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000340
reed@android.com8a1c16f2008-12-17 15:59:43 +0000341 SkAutoLockPixels alp(*decodedBitmap);
342
reed@android.com8a1c16f2008-12-17 15:59:43 +0000343 /* Add filler (or alpha) byte (before/after each RGB triplet) */
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000344 if (colorType == PNG_COLOR_TYPE_RGB || colorType == PNG_COLOR_TYPE_GRAY) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000345 png_set_filler(png_ptr, 0xff, PNG_FILLER_AFTER);
346 }
347
348 /* Turn on interlace handling. REQUIRED if you are not using
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000349 * png_read_image(). To see how to handle interlacing passes,
350 * see the png_read_row() method below:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000351 */
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000352 const int number_passes = (interlaceType != PNG_INTERLACE_NONE) ?
353 png_set_interlace_handling(png_ptr) : 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000354
355 /* Optional call to gamma correct and add the background to the palette
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000356 * and update info structure. REQUIRED if you are expecting libpng to
357 * update the palette for you (ie you selected such a transform above).
reed@android.com8a1c16f2008-12-17 15:59:43 +0000358 */
359 png_read_update_info(png_ptr, info_ptr);
360
361 if (SkBitmap::kIndex8_Config == config && 1 == sampleSize) {
362 for (int i = 0; i < number_passes; i++) {
363 for (png_uint_32 y = 0; y < origHeight; y++) {
364 uint8_t* bmRow = decodedBitmap->getAddr8(0, y);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000365 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000366 }
367 }
368 } else {
369 SkScaledBitmapSampler::SrcConfig sc;
370 int srcBytesPerPixel = 4;
rmistry@google.comd6176b02012-08-23 18:14:13 +0000371
reed@android.com11344262009-07-08 20:09:23 +0000372 if (colorTable != NULL) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000373 sc = SkScaledBitmapSampler::kIndex;
374 srcBytesPerPixel = 1;
375 } else if (hasAlpha) {
376 sc = SkScaledBitmapSampler::kRGBA;
377 } else {
378 sc = SkScaledBitmapSampler::kRGBX;
379 }
reed@android.com11344262009-07-08 20:09:23 +0000380
381 /* We have to pass the colortable explicitly, since we may have one
382 even if our decodedBitmap doesn't, due to the request that we
383 upscale png's palette to a direct model
384 */
385 SkAutoLockColors ctLock(colorTable);
386 if (!sampler.begin(decodedBitmap, sc, doDither, ctLock.colors())) {
reed@android.com862e91b2009-04-28 15:27:07 +0000387 return false;
388 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000389 const int height = decodedBitmap->height();
390
reed@android.com862e91b2009-04-28 15:27:07 +0000391 if (number_passes > 1) {
392 SkAutoMalloc storage(origWidth * origHeight * srcBytesPerPixel);
393 uint8_t* base = (uint8_t*)storage.get();
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000394 size_t rowBytes = origWidth * srcBytesPerPixel;
reed@android.coma8a8b8b2009-05-04 15:00:11 +0000395
reed@android.com862e91b2009-04-28 15:27:07 +0000396 for (int i = 0; i < number_passes; i++) {
397 uint8_t* row = base;
398 for (png_uint_32 y = 0; y < origHeight; y++) {
399 uint8_t* bmRow = row;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000400 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
401 row += rowBytes;
reed@android.com862e91b2009-04-28 15:27:07 +0000402 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000403 }
reed@android.com862e91b2009-04-28 15:27:07 +0000404 // now sample it
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000405 base += sampler.srcY0() * rowBytes;
reed@android.com862e91b2009-04-28 15:27:07 +0000406 for (int y = 0; y < height; y++) {
407 reallyHasAlpha |= sampler.next(base);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000408 base += sampler.srcDY() * rowBytes;
reed@android.com862e91b2009-04-28 15:27:07 +0000409 }
410 } else {
411 SkAutoMalloc storage(origWidth * srcBytesPerPixel);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000412 uint8_t* srcRow = (uint8_t*)storage.get();
413 skip_src_rows(png_ptr, srcRow, sampler.srcY0());
414
415 for (int y = 0; y < height; y++) {
416 uint8_t* tmp = srcRow;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000417 png_read_rows(png_ptr, &tmp, png_bytepp_NULL, 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000418 reallyHasAlpha |= sampler.next(srcRow);
419 if (y < height - 1) {
420 skip_src_rows(png_ptr, srcRow, sampler.srcDY() - 1);
421 }
422 }
reed@android.com862e91b2009-04-28 15:27:07 +0000423
reed@android.com8a1c16f2008-12-17 15:59:43 +0000424 // skip the rest of the rows (if any)
425 png_uint_32 read = (height - 1) * sampler.srcDY() +
426 sampler.srcY0() + 1;
427 SkASSERT(read <= origHeight);
428 skip_src_rows(png_ptr, srcRow, origHeight - read);
429 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000430 }
431
432 /* read rest of file, and get additional chunks in info_ptr - REQUIRED */
433 png_read_end(png_ptr, info_ptr);
434
435 if (0 != theTranspColor) {
436 reallyHasAlpha |= substituteTranspColor(decodedBitmap, theTranspColor);
437 }
438 decodedBitmap->setIsOpaque(!reallyHasAlpha);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000439 if (reuseBitmap) {
440 decodedBitmap->notifyPixelsChanged();
441 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000442 return true;
443}
444
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000445
446
447bool SkPNGImageDecoder::getBitmapConfig(png_structp png_ptr, png_infop info_ptr,
448 SkBitmap::Config *configp, bool *hasAlphap,
449 bool *doDitherp, SkPMColor *theTranspColorp) {
450 png_uint_32 origWidth, origHeight;
451 int bitDepth, colorType;
452 png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
453 &colorType, int_p_NULL, int_p_NULL, int_p_NULL);
454
455 // check for sBIT chunk data, in case we should disable dithering because
456 // our data is not truely 8bits per component
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000457 png_color_8p sig_bit;
458 if (*doDitherp && png_get_sBIT(png_ptr, info_ptr, &sig_bit)) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000459#if 0
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000460 SkDebugf("----- sBIT %d %d %d %d\n", sig_bit->red, sig_bit->green,
461 sig_bit->blue, sig_bit->alpha);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000462#endif
463 // 0 seems to indicate no information available
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000464 if (pos_le(sig_bit->red, SK_R16_BITS) &&
465 pos_le(sig_bit->green, SK_G16_BITS) &&
466 pos_le(sig_bit->blue, SK_B16_BITS)) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000467 *doDitherp = false;
468 }
469 }
470
471 if (colorType == PNG_COLOR_TYPE_PALETTE) {
472 bool paletteHasAlpha = hasTransparencyInPalette(png_ptr, info_ptr);
473 *configp = this->getPrefConfig(kIndex_SrcDepth, paletteHasAlpha);
474 // now see if we can upscale to their requested config
475 if (!canUpscalePaletteToConfig(*configp, paletteHasAlpha)) {
476 *configp = SkBitmap::kIndex8_Config;
477 }
478 } else {
479 png_color_16p transpColor = NULL;
480 int numTransp = 0;
481
482 png_get_tRNS(png_ptr, info_ptr, NULL, &numTransp, &transpColor);
483
484 bool valid = png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS);
485
486 if (valid && numTransp == 1 && transpColor != NULL) {
487 /* Compute our transparent color, which we'll match against later.
488 We don't really handle 16bit components properly here, since we
489 do our compare *after* the values have been knocked down to 8bit
490 which means we will find more matches than we should. The real
491 fix seems to be to see the actual 16bit components, do the
492 compare, and then knock it down to 8bits ourselves.
493 */
494 if (colorType & PNG_COLOR_MASK_COLOR) {
495 if (16 == bitDepth) {
496 *theTranspColorp = SkPackARGB32(0xFF, transpColor->red >> 8,
497 transpColor->green >> 8,
498 transpColor->blue >> 8);
499 } else {
500 *theTranspColorp = SkPackARGB32(0xFF, transpColor->red,
501 transpColor->green,
502 transpColor->blue);
503 }
504 } else { // gray
505 if (16 == bitDepth) {
506 *theTranspColorp = SkPackARGB32(0xFF, transpColor->gray >> 8,
507 transpColor->gray >> 8,
508 transpColor->gray >> 8);
509 } else {
510 *theTranspColorp = SkPackARGB32(0xFF, transpColor->gray,
511 transpColor->gray,
512 transpColor->gray);
513 }
514 }
515 }
516
517 if (valid ||
518 PNG_COLOR_TYPE_RGB_ALPHA == colorType ||
519 PNG_COLOR_TYPE_GRAY_ALPHA == colorType) {
520 *hasAlphap = true;
521 }
522 *configp = this->getPrefConfig(k32Bit_SrcDepth, *hasAlphap);
523 // now match the request against our capabilities
524 if (*hasAlphap) {
525 if (*configp != SkBitmap::kARGB_4444_Config) {
526 *configp = SkBitmap::kARGB_8888_Config;
527 }
528 } else {
529 if (*configp != SkBitmap::kRGB_565_Config &&
530 *configp != SkBitmap::kARGB_4444_Config) {
531 *configp = SkBitmap::kARGB_8888_Config;
532 }
533 }
534 }
535
536 // sanity check for size
537 {
538 Sk64 size;
539 size.setMul(origWidth, origHeight);
540 if (size.isNeg() || !size.is32()) {
541 return false;
542 }
543 // now check that if we are 4-bytes per pixel, we also don't overflow
544 if (size.get32() > (0x7FFFFFFF >> 2)) {
545 return false;
546 }
547 }
548
549 return this->chooseFromOneChoice(*configp, origWidth, origHeight);
550}
551
552bool SkPNGImageDecoder::decodePalette(png_structp png_ptr, png_infop info_ptr,
553 bool *hasAlphap, bool *reallyHasAlphap,
554 SkColorTable **colorTablep) {
555 int numPalette;
556 png_colorp palette;
557 png_bytep trans;
558 int numTrans;
559 bool reallyHasAlpha = false;
560 SkColorTable* colorTable = NULL;
561
562 png_get_PLTE(png_ptr, info_ptr, &palette, &numPalette);
563
564 /* BUGGY IMAGE WORKAROUND
565
566 We hit some images (e.g. fruit_.png) who contain bytes that are == colortable_count
567 which is a problem since we use the byte as an index. To work around this we grow
568 the colortable by 1 (if its < 256) and duplicate the last color into that slot.
569 */
570 int colorCount = numPalette + (numPalette < 256);
571
572 colorTable = SkNEW_ARGS(SkColorTable, (colorCount));
573
574 SkPMColor* colorPtr = colorTable->lockColors();
575 if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
576 png_get_tRNS(png_ptr, info_ptr, &trans, &numTrans, NULL);
577 *hasAlphap = (numTrans > 0);
578 } else {
579 numTrans = 0;
580 colorTable->setFlags(colorTable->getFlags() | SkColorTable::kColorsAreOpaque_Flag);
581 }
582 // check for bad images that might make us crash
583 if (numTrans > numPalette) {
584 numTrans = numPalette;
585 }
586
587 int index = 0;
588 int transLessThanFF = 0;
589
590 for (; index < numTrans; index++) {
591 transLessThanFF |= (int)*trans - 0xFF;
592 *colorPtr++ = SkPreMultiplyARGB(*trans++, palette->red, palette->green, palette->blue);
593 palette++;
594 }
595 reallyHasAlpha |= (transLessThanFF < 0);
596
597 for (; index < numPalette; index++) {
598 *colorPtr++ = SkPackARGB32(0xFF, palette->red, palette->green, palette->blue);
599 palette++;
600 }
601
602 // see BUGGY IMAGE WORKAROUND comment above
603 if (numPalette < 256) {
604 *colorPtr = colorPtr[-1];
605 }
606 colorTable->unlockColors(true);
607 *colorTablep = colorTable;
608 *reallyHasAlphap = reallyHasAlpha;
609 return true;
610}
611
612#ifdef SK_BUILD_FOR_ANDROID
613
614bool SkPNGImageDecoder::onBuildTileIndex(SkStream* sk_stream, int *width, int *height) {
615 png_structp png_ptr;
616 png_infop info_ptr;
617
618 if (!onDecodeInit(sk_stream, &png_ptr, &info_ptr)) {
619 return false;
620 }
621
622 if (setjmp(png_jmpbuf(png_ptr)) != 0) {
623 png_destroy_read_struct(&png_ptr, &info_ptr, png_infopp_NULL);
624 return false;
625 }
626
627 png_uint_32 origWidth, origHeight;
628 int bitDepth, colorType;
629 png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
630 &colorType, int_p_NULL, int_p_NULL, int_p_NULL);
631
632 *width = origWidth;
633 *height = origHeight;
634
635 png_build_index(png_ptr);
636
637 if (fImageIndex) {
638 SkDELETE(fImageIndex);
639 }
640 fImageIndex = SkNEW_ARGS(SkPNGImageIndex, (png_ptr, info_ptr));
641
642 return true;
643}
644
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000645bool SkPNGImageDecoder::onDecodeSubset(SkBitmap* bm, const SkIRect& region) {
646 if (NULL == fImageIndex) {
647 return false;
648 }
649
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000650 png_structp png_ptr = fImageIndex->png_ptr;
651 png_infop info_ptr = fImageIndex->info_ptr;
652 if (setjmp(png_jmpbuf(png_ptr))) {
653 return false;
654 }
655
656 png_uint_32 origWidth, origHeight;
657 int bitDepth, colorType, interlaceType;
658 png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
659 &colorType, &interlaceType, int_p_NULL, int_p_NULL);
660
661 SkIRect rect = SkIRect::MakeWH(origWidth, origHeight);
662
663 if (!rect.intersect(region)) {
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000664 // If the requested region is entirely outside the image, just
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000665 // returns false
666 return false;
667 }
668
669 SkBitmap::Config config;
670 bool hasAlpha = false;
671 bool doDither = this->getDitherImage();
672 SkPMColor theTranspColor = 0; // 0 tells us not to try to match
673
674 if (!getBitmapConfig(png_ptr, info_ptr, &config, &hasAlpha, &doDither, &theTranspColor)) {
675 return false;
676 }
677
678 const int sampleSize = this->getSampleSize();
679 SkScaledBitmapSampler sampler(origWidth, rect.height(), sampleSize);
680
681 SkBitmap decodedBitmap;
682 decodedBitmap.setConfig(config, sampler.scaledWidth(), sampler.scaledHeight(), 0);
683
684 // from here down we are concerned with colortables and pixels
685
686 // we track if we actually see a non-opaque pixels, since sometimes a PNG sets its colortype
687 // to |= PNG_COLOR_MASK_ALPHA, but all of its pixels are in fact opaque. We care, since we
688 // draw lots faster if we can flag the bitmap has being opaque
689 bool reallyHasAlpha = false;
690 SkColorTable* colorTable = NULL;
691
692 if (colorType == PNG_COLOR_TYPE_PALETTE) {
693 decodePalette(png_ptr, info_ptr, &hasAlpha, &reallyHasAlpha, &colorTable);
694 }
695
696 SkAutoUnref aur(colorTable);
697
698 // Check ahead of time if the swap(dest, src) is possible.
699 // If yes, then we will stick to AllocPixelRef since it's cheaper with the swap happening.
700 // If no, then we will use alloc to allocate pixels to prevent garbage collection.
701 int w = rect.width() / sampleSize;
702 int h = rect.height() / sampleSize;
703 const bool swapOnly = (rect == region) && (w == decodedBitmap.width()) &&
704 (h == decodedBitmap.height()) && bm->isNull();
705 const bool needColorTable = SkBitmap::kIndex8_Config == config;
706 if (swapOnly) {
707 if (!this->allocPixelRef(&decodedBitmap, needColorTable ? colorTable : NULL)) {
708 return false;
709 }
710 } else {
711 if (!decodedBitmap.allocPixels(NULL, needColorTable ? colorTable : NULL)) {
712 return false;
713 }
714 }
715 SkAutoLockPixels alp(decodedBitmap);
716
717 /* Add filler (or alpha) byte (before/after each RGB triplet) */
718 if (colorType == PNG_COLOR_TYPE_RGB || colorType == PNG_COLOR_TYPE_GRAY) {
719 png_set_filler(png_ptr, 0xff, PNG_FILLER_AFTER);
720 }
721
722 /* Turn on interlace handling. REQUIRED if you are not using
723 * png_read_image(). To see how to handle interlacing passes,
724 * see the png_read_row() method below:
725 */
726 const int number_passes = (interlaceType != PNG_INTERLACE_NONE) ?
727 png_set_interlace_handling(png_ptr) : 1;
728
729 /* Optional call to gamma correct and add the background to the palette
730 * and update info structure. REQUIRED if you are expecting libpng to
731 * update the palette for you (ie you selected such a transform above).
732 */
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000733
734 // Direct access to png_ptr fields is deprecated in libpng > 1.2.
735#if defined(PNG_1_0_X) || defined (PNG_1_2_X)
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000736 png_ptr->pass = 0;
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000737#else
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000738 // FIXME: This sets pass as desired, but also sets iwidth. Is that ok?
739 png_set_interlaced_pass(png_ptr, 0);
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000740#endif
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000741 png_read_update_info(png_ptr, info_ptr);
742
743 int actualTop = rect.fTop;
744
745 if (SkBitmap::kIndex8_Config == config && 1 == sampleSize) {
746 for (int i = 0; i < number_passes; i++) {
747 png_configure_decoder(png_ptr, &actualTop, i);
748 for (int j = 0; j < rect.fTop - actualTop; j++) {
749 uint8_t* bmRow = decodedBitmap.getAddr8(0, 0);
750 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
751 }
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000752 png_uint_32 bitmapHeight = (png_uint_32) decodedBitmap.height();
753 for (png_uint_32 y = 0; y < bitmapHeight; y++) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000754 uint8_t* bmRow = decodedBitmap.getAddr8(0, y);
755 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
756 }
757 }
758 } else {
759 SkScaledBitmapSampler::SrcConfig sc;
760 int srcBytesPerPixel = 4;
761
762 if (colorTable != NULL) {
763 sc = SkScaledBitmapSampler::kIndex;
764 srcBytesPerPixel = 1;
765 } else if (hasAlpha) {
766 sc = SkScaledBitmapSampler::kRGBA;
767 } else {
768 sc = SkScaledBitmapSampler::kRGBX;
769 }
770
771 /* We have to pass the colortable explicitly, since we may have one
772 even if our decodedBitmap doesn't, due to the request that we
773 upscale png's palette to a direct model
774 */
775 SkAutoLockColors ctLock(colorTable);
776 if (!sampler.begin(&decodedBitmap, sc, doDither, ctLock.colors())) {
777 return false;
778 }
779 const int height = decodedBitmap.height();
780
781 if (number_passes > 1) {
782 SkAutoMalloc storage(origWidth * origHeight * srcBytesPerPixel);
783 uint8_t* base = (uint8_t*)storage.get();
784 size_t rb = origWidth * srcBytesPerPixel;
785
786 for (int i = 0; i < number_passes; i++) {
787 png_configure_decoder(png_ptr, &actualTop, i);
788 for (int j = 0; j < rect.fTop - actualTop; j++) {
789 uint8_t* bmRow = (uint8_t*)decodedBitmap.getPixels();
790 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
791 }
792 uint8_t* row = base;
793 for (int32_t y = 0; y < rect.height(); y++) {
794 uint8_t* bmRow = row;
795 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
796 row += rb;
797 }
798 }
799 // now sample it
800 base += sampler.srcY0() * rb;
801 for (int y = 0; y < height; y++) {
802 reallyHasAlpha |= sampler.next(base);
803 base += sampler.srcDY() * rb;
804 }
805 } else {
806 SkAutoMalloc storage(origWidth * srcBytesPerPixel);
807 uint8_t* srcRow = (uint8_t*)storage.get();
808
809 png_configure_decoder(png_ptr, &actualTop, 0);
810 skip_src_rows(png_ptr, srcRow, sampler.srcY0());
811
812 for (int i = 0; i < rect.fTop - actualTop; i++) {
813 uint8_t* bmRow = (uint8_t*)decodedBitmap.getPixels();
814 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
815 }
816 for (int y = 0; y < height; y++) {
817 uint8_t* tmp = srcRow;
818 png_read_rows(png_ptr, &tmp, png_bytepp_NULL, 1);
819 reallyHasAlpha |= sampler.next(srcRow);
820 if (y < height - 1) {
821 skip_src_rows(png_ptr, srcRow, sampler.srcDY() - 1);
822 }
823 }
824 }
825 }
826
827 if (0 != theTranspColor) {
828 reallyHasAlpha |= substituteTranspColor(&decodedBitmap, theTranspColor);
829 }
830 decodedBitmap.setIsOpaque(!reallyHasAlpha);
831
832 if (swapOnly) {
833 bm->swap(decodedBitmap);
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000834 return true;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000835 }
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000836 return this->cropBitmap(bm, &decodedBitmap, sampleSize, region.x(), region.y(),
837 region.width(), region.height(), 0, rect.y());
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000838}
839#endif
840
reed@android.com8a1c16f2008-12-17 15:59:43 +0000841///////////////////////////////////////////////////////////////////////////////
842
reed@android.com8a1c16f2008-12-17 15:59:43 +0000843#include "SkColorPriv.h"
844#include "SkUnPreMultiply.h"
845
846static void sk_write_fn(png_structp png_ptr, png_bytep data, png_size_t len) {
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000847 SkWStream* sk_stream = (SkWStream*)png_get_io_ptr(png_ptr);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000848 if (!sk_stream->write(data, len)) {
849 png_error(png_ptr, "sk_write_fn Error!");
850 }
851}
852
reed@android.com8a1c16f2008-12-17 15:59:43 +0000853static transform_scanline_proc choose_proc(SkBitmap::Config config,
854 bool hasAlpha) {
855 // we don't care about search on alpha if we're kIndex8, since only the
856 // colortable packing cares about that distinction, not the pixels
857 if (SkBitmap::kIndex8_Config == config) {
858 hasAlpha = false; // we store false in the table entries for kIndex8
859 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000860
reed@android.com8a1c16f2008-12-17 15:59:43 +0000861 static const struct {
862 SkBitmap::Config fConfig;
863 bool fHasAlpha;
864 transform_scanline_proc fProc;
865 } gMap[] = {
866 { SkBitmap::kRGB_565_Config, false, transform_scanline_565 },
867 { SkBitmap::kARGB_8888_Config, false, transform_scanline_888 },
868 { SkBitmap::kARGB_8888_Config, true, transform_scanline_8888 },
869 { SkBitmap::kARGB_4444_Config, false, transform_scanline_444 },
870 { SkBitmap::kARGB_4444_Config, true, transform_scanline_4444 },
epoger@google.com4ce738b2012-11-16 18:44:18 +0000871 { SkBitmap::kIndex8_Config, false, transform_scanline_memcpy },
reed@android.com8a1c16f2008-12-17 15:59:43 +0000872 };
873
874 for (int i = SK_ARRAY_COUNT(gMap) - 1; i >= 0; --i) {
875 if (gMap[i].fConfig == config && gMap[i].fHasAlpha == hasAlpha) {
876 return gMap[i].fProc;
877 }
878 }
879 sk_throw();
880 return NULL;
881}
882
883// return the minimum legal bitdepth (by png standards) for this many colortable
884// entries. SkBitmap always stores in 8bits per pixel, but for colorcount <= 16,
885// we can use fewer bits per in png
886static int computeBitDepth(int colorCount) {
887#if 0
888 int bits = SkNextLog2(colorCount);
889 SkASSERT(bits >= 1 && bits <= 8);
890 // now we need bits itself to be a power of 2 (e.g. 1, 2, 4, 8)
891 return SkNextPow2(bits);
892#else
893 // for the moment, we don't know how to pack bitdepth < 8
894 return 8;
895#endif
896}
897
898/* Pack palette[] with the corresponding colors, and if hasAlpha is true, also
899 pack trans[] and return the number of trans[] entries written. If hasAlpha
900 is false, the return value will always be 0.
rmistry@google.comd6176b02012-08-23 18:14:13 +0000901
reed@android.com8a1c16f2008-12-17 15:59:43 +0000902 Note: this routine takes care of unpremultiplying the RGB values when we
903 have alpha in the colortable, since png doesn't support premul colors
904*/
reed@android.com6f252972009-01-14 16:46:16 +0000905static inline int pack_palette(SkColorTable* ctable,
reed@android.comb50a60c2009-01-14 17:51:08 +0000906 png_color* SK_RESTRICT palette,
907 png_byte* SK_RESTRICT trans, bool hasAlpha) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000908 SkAutoLockColors alc(ctable);
909 const SkPMColor* SK_RESTRICT colors = alc.colors();
910 const int ctCount = ctable->count();
911 int i, num_trans = 0;
912
913 if (hasAlpha) {
914 /* first see if we have some number of fully opaque at the end of the
915 ctable. PNG allows num_trans < num_palette, but all of the trans
916 entries must come first in the palette. If I was smarter, I'd
917 reorder the indices and ctable so that all non-opaque colors came
918 first in the palette. But, since that would slow down the encode,
919 I'm leaving the indices and ctable order as is, and just looking
920 at the tail of the ctable for opaqueness.
921 */
922 num_trans = ctCount;
923 for (i = ctCount - 1; i >= 0; --i) {
924 if (SkGetPackedA32(colors[i]) != 0xFF) {
925 break;
926 }
927 num_trans -= 1;
928 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000929
reed@android.com8a1c16f2008-12-17 15:59:43 +0000930 const SkUnPreMultiply::Scale* SK_RESTRICT table =
931 SkUnPreMultiply::GetScaleTable();
932
933 for (i = 0; i < num_trans; i++) {
934 const SkPMColor c = *colors++;
935 const unsigned a = SkGetPackedA32(c);
936 const SkUnPreMultiply::Scale s = table[a];
937 trans[i] = a;
938 palette[i].red = SkUnPreMultiply::ApplyScale(s, SkGetPackedR32(c));
939 palette[i].green = SkUnPreMultiply::ApplyScale(s,SkGetPackedG32(c));
940 palette[i].blue = SkUnPreMultiply::ApplyScale(s, SkGetPackedB32(c));
rmistry@google.comd6176b02012-08-23 18:14:13 +0000941 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000942 // now fall out of this if-block to use common code for the trailing
943 // opaque entries
944 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000945
reed@android.com8a1c16f2008-12-17 15:59:43 +0000946 // these (remaining) entries are opaque
947 for (i = num_trans; i < ctCount; i++) {
948 SkPMColor c = *colors++;
949 palette[i].red = SkGetPackedR32(c);
950 palette[i].green = SkGetPackedG32(c);
951 palette[i].blue = SkGetPackedB32(c);
952 }
953 return num_trans;
954}
955
956class SkPNGImageEncoder : public SkImageEncoder {
957protected:
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000958 virtual bool onEncode(SkWStream* stream, const SkBitmap& bm, int quality) SK_OVERRIDE;
tomhudson@google.com5c210c72011-07-28 21:06:40 +0000959private:
960 bool doEncode(SkWStream* stream, const SkBitmap& bm,
961 const bool& hasAlpha, int colorType,
962 int bitDepth, SkBitmap::Config config,
963 png_color_8& sig_bit);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000964
965 typedef SkImageEncoder INHERITED;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000966};
967
968bool SkPNGImageEncoder::onEncode(SkWStream* stream, const SkBitmap& bitmap,
969 int /*quality*/) {
970 SkBitmap::Config config = bitmap.getConfig();
971
972 const bool hasAlpha = !bitmap.isOpaque();
973 int colorType = PNG_COLOR_MASK_COLOR;
974 int bitDepth = 8; // default for color
975 png_color_8 sig_bit;
976
977 switch (config) {
978 case SkBitmap::kIndex8_Config:
979 colorType |= PNG_COLOR_MASK_PALETTE;
980 // fall through to the ARGB_8888 case
981 case SkBitmap::kARGB_8888_Config:
982 sig_bit.red = 8;
983 sig_bit.green = 8;
984 sig_bit.blue = 8;
985 sig_bit.alpha = 8;
986 break;
987 case SkBitmap::kARGB_4444_Config:
988 sig_bit.red = 4;
989 sig_bit.green = 4;
990 sig_bit.blue = 4;
991 sig_bit.alpha = 4;
992 break;
993 case SkBitmap::kRGB_565_Config:
994 sig_bit.red = 5;
995 sig_bit.green = 6;
996 sig_bit.blue = 5;
997 sig_bit.alpha = 0;
998 break;
999 default:
1000 return false;
1001 }
rmistry@google.comd6176b02012-08-23 18:14:13 +00001002
reed@android.com8a1c16f2008-12-17 15:59:43 +00001003 if (hasAlpha) {
1004 // don't specify alpha if we're a palette, even if our ctable has alpha
1005 if (!(colorType & PNG_COLOR_MASK_PALETTE)) {
1006 colorType |= PNG_COLOR_MASK_ALPHA;
1007 }
1008 } else {
1009 sig_bit.alpha = 0;
1010 }
rmistry@google.comd6176b02012-08-23 18:14:13 +00001011
reed@android.com8a1c16f2008-12-17 15:59:43 +00001012 SkAutoLockPixels alp(bitmap);
1013 // readyToDraw checks for pixels (and colortable if that is required)
1014 if (!bitmap.readyToDraw()) {
1015 return false;
1016 }
1017
1018 // we must do this after we have locked the pixels
1019 SkColorTable* ctable = bitmap.getColorTable();
1020 if (NULL != ctable) {
1021 if (ctable->count() == 0) {
1022 return false;
1023 }
1024 // check if we can store in fewer than 8 bits
1025 bitDepth = computeBitDepth(ctable->count());
1026 }
1027
tomhudson@google.com5c210c72011-07-28 21:06:40 +00001028 return doEncode(stream, bitmap, hasAlpha, colorType,
1029 bitDepth, config, sig_bit);
1030}
1031
1032bool SkPNGImageEncoder::doEncode(SkWStream* stream, const SkBitmap& bitmap,
1033 const bool& hasAlpha, int colorType,
1034 int bitDepth, SkBitmap::Config config,
1035 png_color_8& sig_bit) {
1036
reed@android.com8a1c16f2008-12-17 15:59:43 +00001037 png_structp png_ptr;
1038 png_infop info_ptr;
1039
1040 png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, sk_error_fn,
1041 NULL);
1042 if (NULL == png_ptr) {
1043 return false;
1044 }
1045
1046 info_ptr = png_create_info_struct(png_ptr);
1047 if (NULL == info_ptr) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +00001048 png_destroy_write_struct(&png_ptr, png_infopp_NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001049 return false;
1050 }
1051
1052 /* Set error handling. REQUIRED if you aren't supplying your own
1053 * error handling functions in the png_create_write_struct() call.
1054 */
1055 if (setjmp(png_jmpbuf(png_ptr))) {
1056 png_destroy_write_struct(&png_ptr, &info_ptr);
1057 return false;
1058 }
1059
commit-bot@chromium.orga936e372013-03-14 14:42:18 +00001060 png_set_write_fn(png_ptr, (void*)stream, sk_write_fn, png_flush_ptr_NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001061
1062 /* Set the image information here. Width and height are up to 2^31,
1063 * bit_depth is one of 1, 2, 4, 8, or 16, but valid values also depend on
1064 * the color_type selected. color_type is one of PNG_COLOR_TYPE_GRAY,
1065 * PNG_COLOR_TYPE_GRAY_ALPHA, PNG_COLOR_TYPE_PALETTE, PNG_COLOR_TYPE_RGB,
1066 * or PNG_COLOR_TYPE_RGB_ALPHA. interlace is either PNG_INTERLACE_NONE or
1067 * PNG_INTERLACE_ADAM7, and the compression_type and filter_type MUST
1068 * currently be PNG_COMPRESSION_TYPE_BASE and PNG_FILTER_TYPE_BASE. REQUIRED
1069 */
1070
1071 png_set_IHDR(png_ptr, info_ptr, bitmap.width(), bitmap.height(),
1072 bitDepth, colorType,
1073 PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE,
1074 PNG_FILTER_TYPE_BASE);
1075
reed@android.com61898772009-07-07 19:38:01 +00001076 // set our colortable/trans arrays if needed
1077 png_color paletteColors[256];
1078 png_byte trans[256];
1079 if (SkBitmap::kIndex8_Config == config) {
1080 SkColorTable* ct = bitmap.getColorTable();
1081 int numTrans = pack_palette(ct, paletteColors, trans, hasAlpha);
1082 png_set_PLTE(png_ptr, info_ptr, paletteColors, ct->count());
1083 if (numTrans > 0) {
1084 png_set_tRNS(png_ptr, info_ptr, trans, numTrans, NULL);
1085 }
1086 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001087
1088 png_set_sBIT(png_ptr, info_ptr, &sig_bit);
1089 png_write_info(png_ptr, info_ptr);
1090
1091 const char* srcImage = (const char*)bitmap.getPixels();
1092 SkAutoSMalloc<1024> rowStorage(bitmap.width() << 2);
1093 char* storage = (char*)rowStorage.get();
1094 transform_scanline_proc proc = choose_proc(config, hasAlpha);
1095
1096 for (int y = 0; y < bitmap.height(); y++) {
1097 png_bytep row_ptr = (png_bytep)storage;
1098 proc(srcImage, bitmap.width(), storage);
1099 png_write_rows(png_ptr, &row_ptr, 1);
1100 srcImage += bitmap.rowBytes();
1101 }
1102
1103 png_write_end(png_ptr, info_ptr);
1104
1105 /* clean up after the write, and free any memory allocated */
1106 png_destroy_write_struct(&png_ptr, &info_ptr);
1107 return true;
1108}
1109
reed@android.com00bf85a2009-01-22 13:04:56 +00001110///////////////////////////////////////////////////////////////////////////////
robertphillips@google.comec51cb82012-03-23 18:13:47 +00001111DEFINE_DECODER_CREATOR(PNGImageDecoder);
1112DEFINE_ENCODER_CREATOR(PNGImageEncoder);
1113///////////////////////////////////////////////////////////////////////////////
reed@android.com00bf85a2009-01-22 13:04:56 +00001114
1115#include "SkTRegistry.h"
1116
scroggo@google.com39edf4c2013-04-25 17:33:51 +00001117static bool is_png(SkStream* stream) {
reed@android.com00bf85a2009-01-22 13:04:56 +00001118 char buf[PNG_BYTES_TO_CHECK];
1119 if (stream->read(buf, PNG_BYTES_TO_CHECK) == PNG_BYTES_TO_CHECK &&
1120 !png_sig_cmp((png_bytep) buf, (png_size_t)0, PNG_BYTES_TO_CHECK)) {
scroggo@google.com39edf4c2013-04-25 17:33:51 +00001121 return true;
1122 }
1123 return false;
1124}
1125
1126SkImageDecoder* sk_libpng_dfactory(SkStream* stream) {
1127 if (is_png(stream)) {
reed@android.com00bf85a2009-01-22 13:04:56 +00001128 return SkNEW(SkPNGImageDecoder);
1129 }
1130 return NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001131}
1132
scroggo@google.com39edf4c2013-04-25 17:33:51 +00001133static SkImageDecoder::Format get_format_png(SkStream* stream) {
1134 if (is_png(stream)) {
1135 return SkImageDecoder::kPNG_Format;
1136 }
1137 return SkImageDecoder::kUnknown_Format;
1138}
1139
reed@android.comdfee5792010-04-15 14:24:50 +00001140SkImageEncoder* sk_libpng_efactory(SkImageEncoder::Type t) {
reed@android.com00bf85a2009-01-22 13:04:56 +00001141 return (SkImageEncoder::kPNG_Type == t) ? SkNEW(SkPNGImageEncoder) : NULL;
1142}
1143
reed@android.comdfee5792010-04-15 14:24:50 +00001144static SkTRegistry<SkImageEncoder*, SkImageEncoder::Type> gEReg(sk_libpng_efactory);
scroggo@google.com39edf4c2013-04-25 17:33:51 +00001145static SkTRegistry<SkImageDecoder::Format, SkStream*> gFormatReg(get_format_png);
reed@android.comdfee5792010-04-15 14:24:50 +00001146static SkTRegistry<SkImageDecoder*, SkStream*> gDReg(sk_libpng_dfactory);