blob: 729f87169a435cceb2ffa3ebd0be4ebd6f485314 [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);
scroggo@google.com2bbc2c92013-06-14 15:33:20 +000083 bool decodePalette(png_structp png_ptr, png_infop info_ptr,
84 bool * SK_RESTRICT hasAlphap, bool *reallyHasAlphap,
85 SkColorTable **colorTablep);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +000086 bool getBitmapConfig(png_structp png_ptr, png_infop info_ptr,
87 SkBitmap::Config *config, bool *hasAlpha,
88 bool *doDither, SkPMColor *theTranspColor);
89
90 typedef SkImageDecoder INHERITED;
reed@android.com8a1c16f2008-12-17 15:59:43 +000091};
92
93#ifndef png_jmpbuf
94# define png_jmpbuf(png_ptr) ((png_ptr)->jmpbuf)
95#endif
96
97#define PNG_BYTES_TO_CHECK 4
98
99/* Automatically clean up after throwing an exception */
100struct PNGAutoClean {
101 PNGAutoClean(png_structp p, png_infop i): png_ptr(p), info_ptr(i) {}
102 ~PNGAutoClean() {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000103 png_destroy_read_struct(&png_ptr, &info_ptr, png_infopp_NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000104 }
105private:
106 png_structp png_ptr;
107 png_infop info_ptr;
108};
109
reed@android.com8a1c16f2008-12-17 15:59:43 +0000110static void sk_read_fn(png_structp png_ptr, png_bytep data, png_size_t length) {
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000111 SkStream* sk_stream = (SkStream*) png_get_io_ptr(png_ptr);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000112 size_t bytes = sk_stream->read(data, length);
113 if (bytes != length) {
114 png_error(png_ptr, "Read Error!");
115 }
116}
117
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000118#ifdef SK_BUILD_FOR_ANDROID
119static void sk_seek_fn(png_structp png_ptr, png_uint_32 offset) {
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000120 SkStream* sk_stream = (SkStream*) png_get_io_ptr(png_ptr);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000121 sk_stream->rewind();
122 (void)sk_stream->skip(offset);
123}
124#endif
125
reed@android.com8a1c16f2008-12-17 15:59:43 +0000126static int sk_read_user_chunk(png_structp png_ptr, png_unknown_chunkp chunk) {
127 SkImageDecoder::Peeker* peeker =
128 (SkImageDecoder::Peeker*)png_get_user_chunk_ptr(png_ptr);
129 // peek() returning true means continue decoding
130 return peeker->peek((const char*)chunk->name, chunk->data, chunk->size) ?
131 1 : -1;
132}
133
134static void sk_error_fn(png_structp png_ptr, png_const_charp msg) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000135 SkDEBUGF(("------ png error %s\n", msg));
reed@android.com8a1c16f2008-12-17 15:59:43 +0000136 longjmp(png_jmpbuf(png_ptr), 1);
137}
138
139static void skip_src_rows(png_structp png_ptr, uint8_t storage[], int count) {
140 for (int i = 0; i < count; i++) {
141 uint8_t* tmp = storage;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000142 png_read_rows(png_ptr, &tmp, png_bytepp_NULL, 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000143 }
144}
145
146static bool pos_le(int value, int max) {
147 return value > 0 && value <= max;
148}
149
150static bool substituteTranspColor(SkBitmap* bm, SkPMColor match) {
151 SkASSERT(bm->config() == SkBitmap::kARGB_8888_Config);
rmistry@google.comd6176b02012-08-23 18:14:13 +0000152
reed@android.com8a1c16f2008-12-17 15:59:43 +0000153 bool reallyHasAlpha = false;
154
155 for (int y = bm->height() - 1; y >= 0; --y) {
156 SkPMColor* p = bm->getAddr32(0, y);
157 for (int x = bm->width() - 1; x >= 0; --x) {
158 if (match == *p) {
159 *p = 0;
160 reallyHasAlpha = true;
161 }
162 p += 1;
163 }
164 }
165 return reallyHasAlpha;
166}
167
reed@android.com3f1f06a2010-03-03 21:04:12 +0000168static bool canUpscalePaletteToConfig(SkBitmap::Config dstConfig,
reed@android.comb6137c32009-07-29 20:56:52 +0000169 bool srcHasAlpha) {
reed@android.com3f1f06a2010-03-03 21:04:12 +0000170 switch (dstConfig) {
reed@android.comb6137c32009-07-29 20:56:52 +0000171 case SkBitmap::kARGB_8888_Config:
172 case SkBitmap::kARGB_4444_Config:
173 return true;
174 case SkBitmap::kRGB_565_Config:
175 // only return true if the src is opaque (since 565 is opaque)
176 return !srcHasAlpha;
177 default:
178 return false;
179 }
180}
181
182// call only if color_type is PALETTE. Returns true if the ctable has alpha
183static bool hasTransparencyInPalette(png_structp png_ptr, png_infop info_ptr) {
184 png_bytep trans;
185 int num_trans;
186
187 if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
188 png_get_tRNS(png_ptr, info_ptr, &trans, &num_trans, NULL);
189 return num_trans > 0;
190 }
191 return false;
reed@android.com11344262009-07-08 20:09:23 +0000192}
193
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000194bool SkPNGImageDecoder::onDecodeInit(SkStream* sk_stream, png_structp *png_ptrp,
195 png_infop *info_ptrp) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000196 /* Create and initialize the png_struct with the desired error handler
197 * functions. If you want to use the default stderr and longjump method,
198 * you can supply NULL for the last three parameters. We also supply the
199 * the compiler header file version, so that we know if the application
200 * was compiled with a compatible version of the library. */
201 png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
202 NULL, sk_error_fn, NULL);
203 // png_voidp user_error_ptr, user_error_fn, user_warning_fn);
204 if (png_ptr == NULL) {
205 return false;
206 }
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000207 *png_ptrp = png_ptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000208
209 /* Allocate/initialize the memory for image information. */
210 png_infop info_ptr = png_create_info_struct(png_ptr);
211 if (info_ptr == NULL) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000212 png_destroy_read_struct(&png_ptr, png_infopp_NULL, png_infopp_NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000213 return false;
214 }
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000215 *info_ptrp = info_ptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000216
217 /* Set error handling if you are using the setjmp/longjmp method (this is
218 * the normal method of doing things with libpng). REQUIRED unless you
219 * set up your own error handlers in the png_create_read_struct() earlier.
220 */
221 if (setjmp(png_jmpbuf(png_ptr))) {
222 return false;
223 }
224
225 /* If you are using replacement read functions, instead of calling
226 * png_init_io() here you would call:
227 */
228 png_set_read_fn(png_ptr, (void *)sk_stream, sk_read_fn);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000229#ifdef SK_BUILD_FOR_ANDROID
230 png_set_seek_fn(png_ptr, sk_seek_fn);
231#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000232 /* where user_io_ptr is a structure you want available to the callbacks */
233 /* If we have already read some of the signature */
234// png_set_sig_bytes(png_ptr, 0 /* sig_read */ );
235
236 // hookup our peeker so we can see any user-chunks the caller may be interested in
237 png_set_keep_unknown_chunks(png_ptr, PNG_HANDLE_CHUNK_ALWAYS, (png_byte*)"", 0);
238 if (this->getPeeker()) {
239 png_set_read_user_chunk_fn(png_ptr, (png_voidp)this->getPeeker(), sk_read_user_chunk);
240 }
241
242 /* The call to png_read_info() gives us all of the information from the
243 * PNG file before the first IDAT (image data chunk). */
244 png_read_info(png_ptr, info_ptr);
245 png_uint_32 origWidth, origHeight;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000246 int bitDepth, colorType;
247 png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
248 &colorType, int_p_NULL, int_p_NULL, int_p_NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000249
250 /* tell libpng to strip 16 bit/color files down to 8 bits/color */
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000251 if (bitDepth == 16) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000252 png_set_strip_16(png_ptr);
253 }
254 /* Extract multiple pixels with bit depths of 1, 2, and 4 from a single
255 * byte into separate bytes (useful for paletted and grayscale images). */
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000256 if (bitDepth < 8) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000257 png_set_packing(png_ptr);
258 }
259 /* Expand grayscale images to the full 8 bits from 1, 2, or 4 bits/pixel */
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000260 if (colorType == PNG_COLOR_TYPE_GRAY && bitDepth < 8) {
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000261 png_set_expand_gray_1_2_4_to_8(png_ptr);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000262 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000263
reed@android.com8a1c16f2008-12-17 15:59:43 +0000264 /* Make a grayscale image into RGB. */
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000265 if (colorType == PNG_COLOR_TYPE_GRAY || colorType == PNG_COLOR_TYPE_GRAY_ALPHA) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000266 png_set_gray_to_rgb(png_ptr);
267 }
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000268 return true;
269}
270
271bool SkPNGImageDecoder::onDecode(SkStream* sk_stream, SkBitmap* decodedBitmap,
272 Mode mode) {
273 png_structp png_ptr;
274 png_infop info_ptr;
275
276 if (!onDecodeInit(sk_stream, &png_ptr, &info_ptr)) {
277 return false;
278 }
279
280 if (setjmp(png_jmpbuf(png_ptr))) {
281 return false;
282 }
283
284 PNGAutoClean autoClean(png_ptr, info_ptr);
285
286 png_uint_32 origWidth, origHeight;
287 int bitDepth, colorType, interlaceType;
288 png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
289 &colorType, &interlaceType, int_p_NULL, int_p_NULL);
rmistry@google.comd6176b02012-08-23 18:14:13 +0000290
reed@android.com8a1c16f2008-12-17 15:59:43 +0000291 SkBitmap::Config config;
292 bool hasAlpha = false;
293 bool doDither = this->getDitherImage();
294 SkPMColor theTranspColor = 0; // 0 tells us not to try to match
rmistry@google.comd6176b02012-08-23 18:14:13 +0000295
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000296 if (!getBitmapConfig(png_ptr, info_ptr, &config, &hasAlpha, &doDither, &theTranspColor)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000297 return false;
298 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000299
reed@android.com8a1c16f2008-12-17 15:59:43 +0000300 const int sampleSize = this->getSampleSize();
301 SkScaledBitmapSampler sampler(origWidth, origHeight, sampleSize);
302
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000303 decodedBitmap->lockPixels();
304 void* rowptr = (void*) decodedBitmap->getPixels();
305 bool reuseBitmap = (rowptr != NULL);
306 decodedBitmap->unlockPixels();
307 if (reuseBitmap && (sampler.scaledWidth() != decodedBitmap->width() ||
308 sampler.scaledHeight() != decodedBitmap->height())) {
309 // Dimensions must match
310 return false;
311 }
312
313 if (!reuseBitmap) {
314 decodedBitmap->setConfig(config, sampler.scaledWidth(),
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000315 sampler.scaledHeight());
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000316 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000317 if (SkImageDecoder::kDecodeBounds_Mode == mode) {
318 return true;
319 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000320
reed@android.com8a1c16f2008-12-17 15:59:43 +0000321 // from here down we are concerned with colortables and pixels
322
323 // we track if we actually see a non-opaque pixels, since sometimes a PNG sets its colortype
324 // to |= PNG_COLOR_MASK_ALPHA, but all of its pixels are in fact opaque. We care, since we
325 // draw lots faster if we can flag the bitmap has being opaque
326 bool reallyHasAlpha = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000327 SkColorTable* colorTable = NULL;
328
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000329 if (colorType == PNG_COLOR_TYPE_PALETTE) {
330 decodePalette(png_ptr, info_ptr, &hasAlpha, &reallyHasAlpha, &colorTable);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000331 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000332
reed@android.com8a1c16f2008-12-17 15:59:43 +0000333 SkAutoUnref aur(colorTable);
334
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000335 if (!reuseBitmap) {
336 if (!this->allocPixelRef(decodedBitmap,
337 SkBitmap::kIndex8_Config == config ? colorTable : NULL)) {
338 return false;
339 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000340 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000341
reed@android.com8a1c16f2008-12-17 15:59:43 +0000342 SkAutoLockPixels alp(*decodedBitmap);
343
reed@android.com8a1c16f2008-12-17 15:59:43 +0000344 /* Add filler (or alpha) byte (before/after each RGB triplet) */
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000345 if (colorType == PNG_COLOR_TYPE_RGB || colorType == PNG_COLOR_TYPE_GRAY) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000346 png_set_filler(png_ptr, 0xff, PNG_FILLER_AFTER);
347 }
348
349 /* Turn on interlace handling. REQUIRED if you are not using
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000350 * png_read_image(). To see how to handle interlacing passes,
351 * see the png_read_row() method below:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000352 */
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000353 const int number_passes = (interlaceType != PNG_INTERLACE_NONE) ?
354 png_set_interlace_handling(png_ptr) : 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000355
356 /* Optional call to gamma correct and add the background to the palette
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000357 * and update info structure. REQUIRED if you are expecting libpng to
358 * update the palette for you (ie you selected such a transform above).
reed@android.com8a1c16f2008-12-17 15:59:43 +0000359 */
360 png_read_update_info(png_ptr, info_ptr);
361
362 if (SkBitmap::kIndex8_Config == config && 1 == sampleSize) {
363 for (int i = 0; i < number_passes; i++) {
364 for (png_uint_32 y = 0; y < origHeight; y++) {
365 uint8_t* bmRow = decodedBitmap->getAddr8(0, y);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000366 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000367 }
368 }
369 } else {
370 SkScaledBitmapSampler::SrcConfig sc;
371 int srcBytesPerPixel = 4;
rmistry@google.comd6176b02012-08-23 18:14:13 +0000372
reed@android.com11344262009-07-08 20:09:23 +0000373 if (colorTable != NULL) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000374 sc = SkScaledBitmapSampler::kIndex;
375 srcBytesPerPixel = 1;
376 } else if (hasAlpha) {
377 sc = SkScaledBitmapSampler::kRGBA;
378 } else {
379 sc = SkScaledBitmapSampler::kRGBX;
380 }
reed@android.com11344262009-07-08 20:09:23 +0000381
382 /* We have to pass the colortable explicitly, since we may have one
383 even if our decodedBitmap doesn't, due to the request that we
384 upscale png's palette to a direct model
385 */
386 SkAutoLockColors ctLock(colorTable);
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000387 if (!sampler.begin(decodedBitmap, sc, doDither, ctLock.colors(),
388 this->getRequireUnpremultipliedColors())) {
reed@android.com862e91b2009-04-28 15:27:07 +0000389 return false;
390 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000391 const int height = decodedBitmap->height();
392
reed@android.com862e91b2009-04-28 15:27:07 +0000393 if (number_passes > 1) {
394 SkAutoMalloc storage(origWidth * origHeight * srcBytesPerPixel);
395 uint8_t* base = (uint8_t*)storage.get();
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000396 size_t rowBytes = origWidth * srcBytesPerPixel;
reed@android.coma8a8b8b2009-05-04 15:00:11 +0000397
reed@android.com862e91b2009-04-28 15:27:07 +0000398 for (int i = 0; i < number_passes; i++) {
399 uint8_t* row = base;
400 for (png_uint_32 y = 0; y < origHeight; y++) {
401 uint8_t* bmRow = row;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000402 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
403 row += rowBytes;
reed@android.com862e91b2009-04-28 15:27:07 +0000404 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000405 }
reed@android.com862e91b2009-04-28 15:27:07 +0000406 // now sample it
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000407 base += sampler.srcY0() * rowBytes;
reed@android.com862e91b2009-04-28 15:27:07 +0000408 for (int y = 0; y < height; y++) {
409 reallyHasAlpha |= sampler.next(base);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000410 base += sampler.srcDY() * rowBytes;
reed@android.com862e91b2009-04-28 15:27:07 +0000411 }
412 } else {
413 SkAutoMalloc storage(origWidth * srcBytesPerPixel);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000414 uint8_t* srcRow = (uint8_t*)storage.get();
415 skip_src_rows(png_ptr, srcRow, sampler.srcY0());
416
417 for (int y = 0; y < height; y++) {
418 uint8_t* tmp = srcRow;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000419 png_read_rows(png_ptr, &tmp, png_bytepp_NULL, 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000420 reallyHasAlpha |= sampler.next(srcRow);
421 if (y < height - 1) {
422 skip_src_rows(png_ptr, srcRow, sampler.srcDY() - 1);
423 }
424 }
reed@android.com862e91b2009-04-28 15:27:07 +0000425
reed@android.com8a1c16f2008-12-17 15:59:43 +0000426 // skip the rest of the rows (if any)
427 png_uint_32 read = (height - 1) * sampler.srcDY() +
428 sampler.srcY0() + 1;
429 SkASSERT(read <= origHeight);
430 skip_src_rows(png_ptr, srcRow, origHeight - read);
431 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000432 }
433
434 /* read rest of file, and get additional chunks in info_ptr - REQUIRED */
435 png_read_end(png_ptr, info_ptr);
436
437 if (0 != theTranspColor) {
438 reallyHasAlpha |= substituteTranspColor(decodedBitmap, theTranspColor);
439 }
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000440 if (reallyHasAlpha && this->getRequireUnpremultipliedColors() &&
441 SkBitmap::kARGB_8888_Config != decodedBitmap->config()) {
442 // If the caller wants an unpremultiplied bitmap, and we let them get
443 // away with a config other than 8888, and it has alpha after all,
444 // return false, since the result will have premultiplied colors.
445 return false;
446 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000447 decodedBitmap->setIsOpaque(!reallyHasAlpha);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000448 if (reuseBitmap) {
449 decodedBitmap->notifyPixelsChanged();
450 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000451 return true;
452}
453
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000454
455
456bool SkPNGImageDecoder::getBitmapConfig(png_structp png_ptr, png_infop info_ptr,
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000457 SkBitmap::Config *configp, bool * SK_RESTRICT hasAlphap,
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000458 bool *doDitherp, SkPMColor *theTranspColorp) {
459 png_uint_32 origWidth, origHeight;
460 int bitDepth, colorType;
461 png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
462 &colorType, int_p_NULL, int_p_NULL, int_p_NULL);
463
464 // check for sBIT chunk data, in case we should disable dithering because
465 // our data is not truely 8bits per component
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000466 png_color_8p sig_bit;
467 if (*doDitherp && png_get_sBIT(png_ptr, info_ptr, &sig_bit)) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000468#if 0
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000469 SkDebugf("----- sBIT %d %d %d %d\n", sig_bit->red, sig_bit->green,
470 sig_bit->blue, sig_bit->alpha);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000471#endif
472 // 0 seems to indicate no information available
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000473 if (pos_le(sig_bit->red, SK_R16_BITS) &&
474 pos_le(sig_bit->green, SK_G16_BITS) &&
475 pos_le(sig_bit->blue, SK_B16_BITS)) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000476 *doDitherp = false;
477 }
478 }
479
480 if (colorType == PNG_COLOR_TYPE_PALETTE) {
481 bool paletteHasAlpha = hasTransparencyInPalette(png_ptr, info_ptr);
482 *configp = this->getPrefConfig(kIndex_SrcDepth, paletteHasAlpha);
483 // now see if we can upscale to their requested config
484 if (!canUpscalePaletteToConfig(*configp, paletteHasAlpha)) {
485 *configp = SkBitmap::kIndex8_Config;
486 }
487 } else {
488 png_color_16p transpColor = NULL;
489 int numTransp = 0;
490
491 png_get_tRNS(png_ptr, info_ptr, NULL, &numTransp, &transpColor);
492
493 bool valid = png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS);
494
495 if (valid && numTransp == 1 && transpColor != NULL) {
496 /* Compute our transparent color, which we'll match against later.
497 We don't really handle 16bit components properly here, since we
498 do our compare *after* the values have been knocked down to 8bit
499 which means we will find more matches than we should. The real
500 fix seems to be to see the actual 16bit components, do the
501 compare, and then knock it down to 8bits ourselves.
502 */
503 if (colorType & PNG_COLOR_MASK_COLOR) {
504 if (16 == bitDepth) {
505 *theTranspColorp = SkPackARGB32(0xFF, transpColor->red >> 8,
506 transpColor->green >> 8,
507 transpColor->blue >> 8);
508 } else {
509 *theTranspColorp = SkPackARGB32(0xFF, transpColor->red,
510 transpColor->green,
511 transpColor->blue);
512 }
513 } else { // gray
514 if (16 == bitDepth) {
515 *theTranspColorp = SkPackARGB32(0xFF, transpColor->gray >> 8,
516 transpColor->gray >> 8,
517 transpColor->gray >> 8);
518 } else {
519 *theTranspColorp = SkPackARGB32(0xFF, transpColor->gray,
520 transpColor->gray,
521 transpColor->gray);
522 }
523 }
524 }
525
526 if (valid ||
527 PNG_COLOR_TYPE_RGB_ALPHA == colorType ||
528 PNG_COLOR_TYPE_GRAY_ALPHA == colorType) {
529 *hasAlphap = true;
530 }
531 *configp = this->getPrefConfig(k32Bit_SrcDepth, *hasAlphap);
532 // now match the request against our capabilities
533 if (*hasAlphap) {
534 if (*configp != SkBitmap::kARGB_4444_Config) {
535 *configp = SkBitmap::kARGB_8888_Config;
536 }
537 } else {
538 if (*configp != SkBitmap::kRGB_565_Config &&
539 *configp != SkBitmap::kARGB_4444_Config) {
540 *configp = SkBitmap::kARGB_8888_Config;
541 }
542 }
543 }
544
545 // sanity check for size
546 {
547 Sk64 size;
548 size.setMul(origWidth, origHeight);
549 if (size.isNeg() || !size.is32()) {
550 return false;
551 }
552 // now check that if we are 4-bytes per pixel, we also don't overflow
553 if (size.get32() > (0x7FFFFFFF >> 2)) {
554 return false;
555 }
556 }
557
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000558 if (!this->chooseFromOneChoice(*configp, origWidth, origHeight)) {
559 return false;
560 }
561
562 // If the image has alpha and the decoder wants unpremultiplied
563 // colors, the only supported config is 8888.
564 if (this->getRequireUnpremultipliedColors() && *hasAlphap) {
565 *configp = SkBitmap::kARGB_8888_Config;
566 }
567 return true;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000568}
569
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000570typedef uint32_t (*PackColorProc)(U8CPU a, U8CPU r, U8CPU g, U8CPU b);
571
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000572bool SkPNGImageDecoder::decodePalette(png_structp png_ptr, png_infop info_ptr,
573 bool *hasAlphap, bool *reallyHasAlphap,
574 SkColorTable **colorTablep) {
575 int numPalette;
576 png_colorp palette;
577 png_bytep trans;
578 int numTrans;
579 bool reallyHasAlpha = false;
580 SkColorTable* colorTable = NULL;
581
582 png_get_PLTE(png_ptr, info_ptr, &palette, &numPalette);
583
584 /* BUGGY IMAGE WORKAROUND
585
586 We hit some images (e.g. fruit_.png) who contain bytes that are == colortable_count
587 which is a problem since we use the byte as an index. To work around this we grow
588 the colortable by 1 (if its < 256) and duplicate the last color into that slot.
589 */
590 int colorCount = numPalette + (numPalette < 256);
591
592 colorTable = SkNEW_ARGS(SkColorTable, (colorCount));
593
594 SkPMColor* colorPtr = colorTable->lockColors();
595 if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
596 png_get_tRNS(png_ptr, info_ptr, &trans, &numTrans, NULL);
597 *hasAlphap = (numTrans > 0);
598 } else {
599 numTrans = 0;
600 colorTable->setFlags(colorTable->getFlags() | SkColorTable::kColorsAreOpaque_Flag);
601 }
602 // check for bad images that might make us crash
603 if (numTrans > numPalette) {
604 numTrans = numPalette;
605 }
606
607 int index = 0;
608 int transLessThanFF = 0;
609
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000610 // Choose which function to use to create the color table. If the final destination's
611 // config is unpremultiplied, the color table will store unpremultiplied colors.
612 PackColorProc proc;
613 if (this->getRequireUnpremultipliedColors()) {
614 proc = &SkPackARGB32NoCheck;
615 } else {
616 proc = &SkPreMultiplyARGB;
617 }
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000618 for (; index < numTrans; index++) {
619 transLessThanFF |= (int)*trans - 0xFF;
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000620 *colorPtr++ = proc(*trans++, palette->red, palette->green, palette->blue);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000621 palette++;
622 }
623 reallyHasAlpha |= (transLessThanFF < 0);
624
625 for (; index < numPalette; index++) {
626 *colorPtr++ = SkPackARGB32(0xFF, palette->red, palette->green, palette->blue);
627 palette++;
628 }
629
630 // see BUGGY IMAGE WORKAROUND comment above
631 if (numPalette < 256) {
632 *colorPtr = colorPtr[-1];
633 }
634 colorTable->unlockColors(true);
635 *colorTablep = colorTable;
636 *reallyHasAlphap = reallyHasAlpha;
637 return true;
638}
639
640#ifdef SK_BUILD_FOR_ANDROID
641
642bool SkPNGImageDecoder::onBuildTileIndex(SkStream* sk_stream, int *width, int *height) {
643 png_structp png_ptr;
644 png_infop info_ptr;
645
646 if (!onDecodeInit(sk_stream, &png_ptr, &info_ptr)) {
647 return false;
648 }
649
650 if (setjmp(png_jmpbuf(png_ptr)) != 0) {
651 png_destroy_read_struct(&png_ptr, &info_ptr, png_infopp_NULL);
652 return false;
653 }
654
655 png_uint_32 origWidth, origHeight;
656 int bitDepth, colorType;
657 png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
658 &colorType, int_p_NULL, int_p_NULL, int_p_NULL);
659
660 *width = origWidth;
661 *height = origHeight;
662
663 png_build_index(png_ptr);
664
665 if (fImageIndex) {
666 SkDELETE(fImageIndex);
667 }
668 fImageIndex = SkNEW_ARGS(SkPNGImageIndex, (png_ptr, info_ptr));
669
670 return true;
671}
672
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000673bool SkPNGImageDecoder::onDecodeSubset(SkBitmap* bm, const SkIRect& region) {
674 if (NULL == fImageIndex) {
675 return false;
676 }
677
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000678 png_structp png_ptr = fImageIndex->png_ptr;
679 png_infop info_ptr = fImageIndex->info_ptr;
680 if (setjmp(png_jmpbuf(png_ptr))) {
681 return false;
682 }
683
684 png_uint_32 origWidth, origHeight;
685 int bitDepth, colorType, interlaceType;
686 png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
687 &colorType, &interlaceType, int_p_NULL, int_p_NULL);
688
689 SkIRect rect = SkIRect::MakeWH(origWidth, origHeight);
690
691 if (!rect.intersect(region)) {
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000692 // If the requested region is entirely outside the image, just
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000693 // returns false
694 return false;
695 }
696
697 SkBitmap::Config config;
698 bool hasAlpha = false;
699 bool doDither = this->getDitherImage();
700 SkPMColor theTranspColor = 0; // 0 tells us not to try to match
701
702 if (!getBitmapConfig(png_ptr, info_ptr, &config, &hasAlpha, &doDither, &theTranspColor)) {
703 return false;
704 }
705
706 const int sampleSize = this->getSampleSize();
707 SkScaledBitmapSampler sampler(origWidth, rect.height(), sampleSize);
708
709 SkBitmap decodedBitmap;
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000710 decodedBitmap.setConfig(config, sampler.scaledWidth(), sampler.scaledHeight());
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000711
712 // from here down we are concerned with colortables and pixels
713
714 // we track if we actually see a non-opaque pixels, since sometimes a PNG sets its colortype
715 // to |= PNG_COLOR_MASK_ALPHA, but all of its pixels are in fact opaque. We care, since we
716 // draw lots faster if we can flag the bitmap has being opaque
717 bool reallyHasAlpha = false;
718 SkColorTable* colorTable = NULL;
719
720 if (colorType == PNG_COLOR_TYPE_PALETTE) {
721 decodePalette(png_ptr, info_ptr, &hasAlpha, &reallyHasAlpha, &colorTable);
722 }
723
724 SkAutoUnref aur(colorTable);
725
726 // Check ahead of time if the swap(dest, src) is possible.
727 // If yes, then we will stick to AllocPixelRef since it's cheaper with the swap happening.
728 // If no, then we will use alloc to allocate pixels to prevent garbage collection.
729 int w = rect.width() / sampleSize;
730 int h = rect.height() / sampleSize;
731 const bool swapOnly = (rect == region) && (w == decodedBitmap.width()) &&
732 (h == decodedBitmap.height()) && bm->isNull();
733 const bool needColorTable = SkBitmap::kIndex8_Config == config;
734 if (swapOnly) {
735 if (!this->allocPixelRef(&decodedBitmap, needColorTable ? colorTable : NULL)) {
736 return false;
737 }
738 } else {
739 if (!decodedBitmap.allocPixels(NULL, needColorTable ? colorTable : NULL)) {
740 return false;
741 }
742 }
743 SkAutoLockPixels alp(decodedBitmap);
744
745 /* Add filler (or alpha) byte (before/after each RGB triplet) */
746 if (colorType == PNG_COLOR_TYPE_RGB || colorType == PNG_COLOR_TYPE_GRAY) {
747 png_set_filler(png_ptr, 0xff, PNG_FILLER_AFTER);
748 }
749
750 /* Turn on interlace handling. REQUIRED if you are not using
751 * png_read_image(). To see how to handle interlacing passes,
752 * see the png_read_row() method below:
753 */
754 const int number_passes = (interlaceType != PNG_INTERLACE_NONE) ?
755 png_set_interlace_handling(png_ptr) : 1;
756
757 /* Optional call to gamma correct and add the background to the palette
758 * and update info structure. REQUIRED if you are expecting libpng to
759 * update the palette for you (ie you selected such a transform above).
760 */
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000761
762 // Direct access to png_ptr fields is deprecated in libpng > 1.2.
763#if defined(PNG_1_0_X) || defined (PNG_1_2_X)
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000764 png_ptr->pass = 0;
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000765#else
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000766 // FIXME: This sets pass as desired, but also sets iwidth. Is that ok?
767 png_set_interlaced_pass(png_ptr, 0);
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000768#endif
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000769 png_read_update_info(png_ptr, info_ptr);
770
771 int actualTop = rect.fTop;
772
773 if (SkBitmap::kIndex8_Config == config && 1 == sampleSize) {
774 for (int i = 0; i < number_passes; i++) {
775 png_configure_decoder(png_ptr, &actualTop, i);
776 for (int j = 0; j < rect.fTop - actualTop; j++) {
777 uint8_t* bmRow = decodedBitmap.getAddr8(0, 0);
778 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
779 }
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000780 png_uint_32 bitmapHeight = (png_uint_32) decodedBitmap.height();
781 for (png_uint_32 y = 0; y < bitmapHeight; y++) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000782 uint8_t* bmRow = decodedBitmap.getAddr8(0, y);
783 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
784 }
785 }
786 } else {
787 SkScaledBitmapSampler::SrcConfig sc;
788 int srcBytesPerPixel = 4;
789
790 if (colorTable != NULL) {
791 sc = SkScaledBitmapSampler::kIndex;
792 srcBytesPerPixel = 1;
793 } else if (hasAlpha) {
794 sc = SkScaledBitmapSampler::kRGBA;
795 } else {
796 sc = SkScaledBitmapSampler::kRGBX;
797 }
798
799 /* We have to pass the colortable explicitly, since we may have one
800 even if our decodedBitmap doesn't, due to the request that we
801 upscale png's palette to a direct model
802 */
803 SkAutoLockColors ctLock(colorTable);
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000804 if (!sampler.begin(&decodedBitmap, sc, doDither, ctLock.colors(),
805 this->getRequireUnpremultipliedColors())) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000806 return false;
807 }
808 const int height = decodedBitmap.height();
809
810 if (number_passes > 1) {
811 SkAutoMalloc storage(origWidth * origHeight * srcBytesPerPixel);
812 uint8_t* base = (uint8_t*)storage.get();
813 size_t rb = origWidth * srcBytesPerPixel;
814
815 for (int i = 0; i < number_passes; i++) {
816 png_configure_decoder(png_ptr, &actualTop, i);
817 for (int j = 0; j < rect.fTop - actualTop; j++) {
818 uint8_t* bmRow = (uint8_t*)decodedBitmap.getPixels();
819 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
820 }
821 uint8_t* row = base;
822 for (int32_t y = 0; y < rect.height(); y++) {
823 uint8_t* bmRow = row;
824 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
825 row += rb;
826 }
827 }
828 // now sample it
829 base += sampler.srcY0() * rb;
830 for (int y = 0; y < height; y++) {
831 reallyHasAlpha |= sampler.next(base);
832 base += sampler.srcDY() * rb;
833 }
834 } else {
835 SkAutoMalloc storage(origWidth * srcBytesPerPixel);
836 uint8_t* srcRow = (uint8_t*)storage.get();
837
838 png_configure_decoder(png_ptr, &actualTop, 0);
839 skip_src_rows(png_ptr, srcRow, sampler.srcY0());
840
841 for (int i = 0; i < rect.fTop - actualTop; i++) {
842 uint8_t* bmRow = (uint8_t*)decodedBitmap.getPixels();
843 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
844 }
845 for (int y = 0; y < height; y++) {
846 uint8_t* tmp = srcRow;
847 png_read_rows(png_ptr, &tmp, png_bytepp_NULL, 1);
848 reallyHasAlpha |= sampler.next(srcRow);
849 if (y < height - 1) {
850 skip_src_rows(png_ptr, srcRow, sampler.srcDY() - 1);
851 }
852 }
853 }
854 }
855
856 if (0 != theTranspColor) {
857 reallyHasAlpha |= substituteTranspColor(&decodedBitmap, theTranspColor);
858 }
859 decodedBitmap.setIsOpaque(!reallyHasAlpha);
860
861 if (swapOnly) {
862 bm->swap(decodedBitmap);
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000863 return true;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000864 }
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000865 return this->cropBitmap(bm, &decodedBitmap, sampleSize, region.x(), region.y(),
866 region.width(), region.height(), 0, rect.y());
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000867}
868#endif
869
reed@android.com8a1c16f2008-12-17 15:59:43 +0000870///////////////////////////////////////////////////////////////////////////////
871
reed@android.com8a1c16f2008-12-17 15:59:43 +0000872#include "SkColorPriv.h"
873#include "SkUnPreMultiply.h"
874
875static void sk_write_fn(png_structp png_ptr, png_bytep data, png_size_t len) {
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000876 SkWStream* sk_stream = (SkWStream*)png_get_io_ptr(png_ptr);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000877 if (!sk_stream->write(data, len)) {
878 png_error(png_ptr, "sk_write_fn Error!");
879 }
880}
881
reed@android.com8a1c16f2008-12-17 15:59:43 +0000882static transform_scanline_proc choose_proc(SkBitmap::Config config,
883 bool hasAlpha) {
884 // we don't care about search on alpha if we're kIndex8, since only the
885 // colortable packing cares about that distinction, not the pixels
886 if (SkBitmap::kIndex8_Config == config) {
887 hasAlpha = false; // we store false in the table entries for kIndex8
888 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000889
reed@android.com8a1c16f2008-12-17 15:59:43 +0000890 static const struct {
891 SkBitmap::Config fConfig;
892 bool fHasAlpha;
893 transform_scanline_proc fProc;
894 } gMap[] = {
895 { SkBitmap::kRGB_565_Config, false, transform_scanline_565 },
896 { SkBitmap::kARGB_8888_Config, false, transform_scanline_888 },
897 { SkBitmap::kARGB_8888_Config, true, transform_scanline_8888 },
898 { SkBitmap::kARGB_4444_Config, false, transform_scanline_444 },
899 { SkBitmap::kARGB_4444_Config, true, transform_scanline_4444 },
epoger@google.com4ce738b2012-11-16 18:44:18 +0000900 { SkBitmap::kIndex8_Config, false, transform_scanline_memcpy },
reed@android.com8a1c16f2008-12-17 15:59:43 +0000901 };
902
903 for (int i = SK_ARRAY_COUNT(gMap) - 1; i >= 0; --i) {
904 if (gMap[i].fConfig == config && gMap[i].fHasAlpha == hasAlpha) {
905 return gMap[i].fProc;
906 }
907 }
908 sk_throw();
909 return NULL;
910}
911
912// return the minimum legal bitdepth (by png standards) for this many colortable
913// entries. SkBitmap always stores in 8bits per pixel, but for colorcount <= 16,
914// we can use fewer bits per in png
915static int computeBitDepth(int colorCount) {
916#if 0
917 int bits = SkNextLog2(colorCount);
918 SkASSERT(bits >= 1 && bits <= 8);
919 // now we need bits itself to be a power of 2 (e.g. 1, 2, 4, 8)
920 return SkNextPow2(bits);
921#else
922 // for the moment, we don't know how to pack bitdepth < 8
923 return 8;
924#endif
925}
926
927/* Pack palette[] with the corresponding colors, and if hasAlpha is true, also
928 pack trans[] and return the number of trans[] entries written. If hasAlpha
929 is false, the return value will always be 0.
rmistry@google.comd6176b02012-08-23 18:14:13 +0000930
reed@android.com8a1c16f2008-12-17 15:59:43 +0000931 Note: this routine takes care of unpremultiplying the RGB values when we
932 have alpha in the colortable, since png doesn't support premul colors
933*/
reed@android.com6f252972009-01-14 16:46:16 +0000934static inline int pack_palette(SkColorTable* ctable,
reed@android.comb50a60c2009-01-14 17:51:08 +0000935 png_color* SK_RESTRICT palette,
936 png_byte* SK_RESTRICT trans, bool hasAlpha) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000937 SkAutoLockColors alc(ctable);
938 const SkPMColor* SK_RESTRICT colors = alc.colors();
939 const int ctCount = ctable->count();
940 int i, num_trans = 0;
941
942 if (hasAlpha) {
943 /* first see if we have some number of fully opaque at the end of the
944 ctable. PNG allows num_trans < num_palette, but all of the trans
945 entries must come first in the palette. If I was smarter, I'd
946 reorder the indices and ctable so that all non-opaque colors came
947 first in the palette. But, since that would slow down the encode,
948 I'm leaving the indices and ctable order as is, and just looking
949 at the tail of the ctable for opaqueness.
950 */
951 num_trans = ctCount;
952 for (i = ctCount - 1; i >= 0; --i) {
953 if (SkGetPackedA32(colors[i]) != 0xFF) {
954 break;
955 }
956 num_trans -= 1;
957 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000958
reed@android.com8a1c16f2008-12-17 15:59:43 +0000959 const SkUnPreMultiply::Scale* SK_RESTRICT table =
960 SkUnPreMultiply::GetScaleTable();
961
962 for (i = 0; i < num_trans; i++) {
963 const SkPMColor c = *colors++;
964 const unsigned a = SkGetPackedA32(c);
965 const SkUnPreMultiply::Scale s = table[a];
966 trans[i] = a;
967 palette[i].red = SkUnPreMultiply::ApplyScale(s, SkGetPackedR32(c));
968 palette[i].green = SkUnPreMultiply::ApplyScale(s,SkGetPackedG32(c));
969 palette[i].blue = SkUnPreMultiply::ApplyScale(s, SkGetPackedB32(c));
rmistry@google.comd6176b02012-08-23 18:14:13 +0000970 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000971 // now fall out of this if-block to use common code for the trailing
972 // opaque entries
973 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000974
reed@android.com8a1c16f2008-12-17 15:59:43 +0000975 // these (remaining) entries are opaque
976 for (i = num_trans; i < ctCount; i++) {
977 SkPMColor c = *colors++;
978 palette[i].red = SkGetPackedR32(c);
979 palette[i].green = SkGetPackedG32(c);
980 palette[i].blue = SkGetPackedB32(c);
981 }
982 return num_trans;
983}
984
985class SkPNGImageEncoder : public SkImageEncoder {
986protected:
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000987 virtual bool onEncode(SkWStream* stream, const SkBitmap& bm, int quality) SK_OVERRIDE;
tomhudson@google.com5c210c72011-07-28 21:06:40 +0000988private:
989 bool doEncode(SkWStream* stream, const SkBitmap& bm,
990 const bool& hasAlpha, int colorType,
991 int bitDepth, SkBitmap::Config config,
992 png_color_8& sig_bit);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000993
994 typedef SkImageEncoder INHERITED;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000995};
996
997bool SkPNGImageEncoder::onEncode(SkWStream* stream, const SkBitmap& bitmap,
998 int /*quality*/) {
999 SkBitmap::Config config = bitmap.getConfig();
1000
1001 const bool hasAlpha = !bitmap.isOpaque();
1002 int colorType = PNG_COLOR_MASK_COLOR;
1003 int bitDepth = 8; // default for color
1004 png_color_8 sig_bit;
1005
1006 switch (config) {
1007 case SkBitmap::kIndex8_Config:
1008 colorType |= PNG_COLOR_MASK_PALETTE;
1009 // fall through to the ARGB_8888 case
1010 case SkBitmap::kARGB_8888_Config:
1011 sig_bit.red = 8;
1012 sig_bit.green = 8;
1013 sig_bit.blue = 8;
1014 sig_bit.alpha = 8;
1015 break;
1016 case SkBitmap::kARGB_4444_Config:
1017 sig_bit.red = 4;
1018 sig_bit.green = 4;
1019 sig_bit.blue = 4;
1020 sig_bit.alpha = 4;
1021 break;
1022 case SkBitmap::kRGB_565_Config:
1023 sig_bit.red = 5;
1024 sig_bit.green = 6;
1025 sig_bit.blue = 5;
1026 sig_bit.alpha = 0;
1027 break;
1028 default:
1029 return false;
1030 }
rmistry@google.comd6176b02012-08-23 18:14:13 +00001031
reed@android.com8a1c16f2008-12-17 15:59:43 +00001032 if (hasAlpha) {
1033 // don't specify alpha if we're a palette, even if our ctable has alpha
1034 if (!(colorType & PNG_COLOR_MASK_PALETTE)) {
1035 colorType |= PNG_COLOR_MASK_ALPHA;
1036 }
1037 } else {
1038 sig_bit.alpha = 0;
1039 }
rmistry@google.comd6176b02012-08-23 18:14:13 +00001040
reed@android.com8a1c16f2008-12-17 15:59:43 +00001041 SkAutoLockPixels alp(bitmap);
1042 // readyToDraw checks for pixels (and colortable if that is required)
1043 if (!bitmap.readyToDraw()) {
1044 return false;
1045 }
1046
1047 // we must do this after we have locked the pixels
1048 SkColorTable* ctable = bitmap.getColorTable();
1049 if (NULL != ctable) {
1050 if (ctable->count() == 0) {
1051 return false;
1052 }
1053 // check if we can store in fewer than 8 bits
1054 bitDepth = computeBitDepth(ctable->count());
1055 }
1056
tomhudson@google.com5c210c72011-07-28 21:06:40 +00001057 return doEncode(stream, bitmap, hasAlpha, colorType,
1058 bitDepth, config, sig_bit);
1059}
1060
1061bool SkPNGImageEncoder::doEncode(SkWStream* stream, const SkBitmap& bitmap,
1062 const bool& hasAlpha, int colorType,
1063 int bitDepth, SkBitmap::Config config,
1064 png_color_8& sig_bit) {
1065
reed@android.com8a1c16f2008-12-17 15:59:43 +00001066 png_structp png_ptr;
1067 png_infop info_ptr;
1068
1069 png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, sk_error_fn,
1070 NULL);
1071 if (NULL == png_ptr) {
1072 return false;
1073 }
1074
1075 info_ptr = png_create_info_struct(png_ptr);
1076 if (NULL == info_ptr) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +00001077 png_destroy_write_struct(&png_ptr, png_infopp_NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001078 return false;
1079 }
1080
1081 /* Set error handling. REQUIRED if you aren't supplying your own
1082 * error handling functions in the png_create_write_struct() call.
1083 */
1084 if (setjmp(png_jmpbuf(png_ptr))) {
1085 png_destroy_write_struct(&png_ptr, &info_ptr);
1086 return false;
1087 }
1088
commit-bot@chromium.orga936e372013-03-14 14:42:18 +00001089 png_set_write_fn(png_ptr, (void*)stream, sk_write_fn, png_flush_ptr_NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001090
1091 /* Set the image information here. Width and height are up to 2^31,
1092 * bit_depth is one of 1, 2, 4, 8, or 16, but valid values also depend on
1093 * the color_type selected. color_type is one of PNG_COLOR_TYPE_GRAY,
1094 * PNG_COLOR_TYPE_GRAY_ALPHA, PNG_COLOR_TYPE_PALETTE, PNG_COLOR_TYPE_RGB,
1095 * or PNG_COLOR_TYPE_RGB_ALPHA. interlace is either PNG_INTERLACE_NONE or
1096 * PNG_INTERLACE_ADAM7, and the compression_type and filter_type MUST
1097 * currently be PNG_COMPRESSION_TYPE_BASE and PNG_FILTER_TYPE_BASE. REQUIRED
1098 */
1099
1100 png_set_IHDR(png_ptr, info_ptr, bitmap.width(), bitmap.height(),
1101 bitDepth, colorType,
1102 PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE,
1103 PNG_FILTER_TYPE_BASE);
1104
reed@android.com61898772009-07-07 19:38:01 +00001105 // set our colortable/trans arrays if needed
1106 png_color paletteColors[256];
1107 png_byte trans[256];
1108 if (SkBitmap::kIndex8_Config == config) {
1109 SkColorTable* ct = bitmap.getColorTable();
1110 int numTrans = pack_palette(ct, paletteColors, trans, hasAlpha);
1111 png_set_PLTE(png_ptr, info_ptr, paletteColors, ct->count());
1112 if (numTrans > 0) {
1113 png_set_tRNS(png_ptr, info_ptr, trans, numTrans, NULL);
1114 }
1115 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001116
1117 png_set_sBIT(png_ptr, info_ptr, &sig_bit);
1118 png_write_info(png_ptr, info_ptr);
1119
1120 const char* srcImage = (const char*)bitmap.getPixels();
1121 SkAutoSMalloc<1024> rowStorage(bitmap.width() << 2);
1122 char* storage = (char*)rowStorage.get();
1123 transform_scanline_proc proc = choose_proc(config, hasAlpha);
1124
1125 for (int y = 0; y < bitmap.height(); y++) {
1126 png_bytep row_ptr = (png_bytep)storage;
1127 proc(srcImage, bitmap.width(), storage);
1128 png_write_rows(png_ptr, &row_ptr, 1);
1129 srcImage += bitmap.rowBytes();
1130 }
1131
1132 png_write_end(png_ptr, info_ptr);
1133
1134 /* clean up after the write, and free any memory allocated */
1135 png_destroy_write_struct(&png_ptr, &info_ptr);
1136 return true;
1137}
1138
reed@android.com00bf85a2009-01-22 13:04:56 +00001139///////////////////////////////////////////////////////////////////////////////
robertphillips@google.comec51cb82012-03-23 18:13:47 +00001140DEFINE_DECODER_CREATOR(PNGImageDecoder);
1141DEFINE_ENCODER_CREATOR(PNGImageEncoder);
1142///////////////////////////////////////////////////////////////////////////////
reed@android.com00bf85a2009-01-22 13:04:56 +00001143
1144#include "SkTRegistry.h"
1145
scroggo@google.com39edf4c2013-04-25 17:33:51 +00001146static bool is_png(SkStream* stream) {
reed@android.com00bf85a2009-01-22 13:04:56 +00001147 char buf[PNG_BYTES_TO_CHECK];
1148 if (stream->read(buf, PNG_BYTES_TO_CHECK) == PNG_BYTES_TO_CHECK &&
1149 !png_sig_cmp((png_bytep) buf, (png_size_t)0, PNG_BYTES_TO_CHECK)) {
scroggo@google.com39edf4c2013-04-25 17:33:51 +00001150 return true;
1151 }
1152 return false;
1153}
1154
1155SkImageDecoder* sk_libpng_dfactory(SkStream* stream) {
1156 if (is_png(stream)) {
reed@android.com00bf85a2009-01-22 13:04:56 +00001157 return SkNEW(SkPNGImageDecoder);
1158 }
1159 return NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001160}
1161
scroggo@google.com39edf4c2013-04-25 17:33:51 +00001162static SkImageDecoder::Format get_format_png(SkStream* stream) {
1163 if (is_png(stream)) {
1164 return SkImageDecoder::kPNG_Format;
1165 }
1166 return SkImageDecoder::kUnknown_Format;
1167}
1168
reed@android.comdfee5792010-04-15 14:24:50 +00001169SkImageEncoder* sk_libpng_efactory(SkImageEncoder::Type t) {
reed@android.com00bf85a2009-01-22 13:04:56 +00001170 return (SkImageEncoder::kPNG_Type == t) ? SkNEW(SkPNGImageEncoder) : NULL;
1171}
1172
reed@android.comdfee5792010-04-15 14:24:50 +00001173static SkTRegistry<SkImageEncoder*, SkImageEncoder::Type> gEReg(sk_libpng_efactory);
scroggo@google.com39edf4c2013-04-25 17:33:51 +00001174static SkTRegistry<SkImageDecoder::Format, SkStream*> gFormatReg(get_format_png);
reed@android.comdfee5792010-04-15 14:24:50 +00001175static SkTRegistry<SkImageDecoder*, SkStream*> gDReg(sk_libpng_dfactory);