blob: d59a67bf8ef8090e4268de40a788c04086e7d087 [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);
scroggo@google.combc69ce92013-07-09 15:45:14 +0000302 decodedBitmap->setConfig(config, sampler.scaledWidth(), sampler.scaledHeight());
reed@android.com8a1c16f2008-12-17 15:59:43 +0000303
reed@android.com8a1c16f2008-12-17 15:59:43 +0000304 if (SkImageDecoder::kDecodeBounds_Mode == mode) {
305 return true;
306 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000307
reed@android.com8a1c16f2008-12-17 15:59:43 +0000308 // from here down we are concerned with colortables and pixels
309
310 // we track if we actually see a non-opaque pixels, since sometimes a PNG sets its colortype
311 // to |= PNG_COLOR_MASK_ALPHA, but all of its pixels are in fact opaque. We care, since we
312 // draw lots faster if we can flag the bitmap has being opaque
313 bool reallyHasAlpha = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000314 SkColorTable* colorTable = NULL;
315
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000316 if (colorType == PNG_COLOR_TYPE_PALETTE) {
317 decodePalette(png_ptr, info_ptr, &hasAlpha, &reallyHasAlpha, &colorTable);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000318 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000319
reed@android.com8a1c16f2008-12-17 15:59:43 +0000320 SkAutoUnref aur(colorTable);
321
scroggo@google.combc69ce92013-07-09 15:45:14 +0000322 if (!this->allocPixelRef(decodedBitmap,
323 SkBitmap::kIndex8_Config == config ? colorTable : NULL)) {
324 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000325 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000326
reed@android.com8a1c16f2008-12-17 15:59:43 +0000327 SkAutoLockPixels alp(*decodedBitmap);
328
reed@android.com8a1c16f2008-12-17 15:59:43 +0000329 /* Add filler (or alpha) byte (before/after each RGB triplet) */
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000330 if (colorType == PNG_COLOR_TYPE_RGB || colorType == PNG_COLOR_TYPE_GRAY) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000331 png_set_filler(png_ptr, 0xff, PNG_FILLER_AFTER);
332 }
333
334 /* Turn on interlace handling. REQUIRED if you are not using
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000335 * png_read_image(). To see how to handle interlacing passes,
336 * see the png_read_row() method below:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000337 */
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000338 const int number_passes = (interlaceType != PNG_INTERLACE_NONE) ?
339 png_set_interlace_handling(png_ptr) : 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000340
341 /* Optional call to gamma correct and add the background to the palette
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000342 * and update info structure. REQUIRED if you are expecting libpng to
343 * update the palette for you (ie you selected such a transform above).
reed@android.com8a1c16f2008-12-17 15:59:43 +0000344 */
345 png_read_update_info(png_ptr, info_ptr);
346
347 if (SkBitmap::kIndex8_Config == config && 1 == sampleSize) {
348 for (int i = 0; i < number_passes; i++) {
349 for (png_uint_32 y = 0; y < origHeight; y++) {
350 uint8_t* bmRow = decodedBitmap->getAddr8(0, y);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000351 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000352 }
353 }
354 } else {
355 SkScaledBitmapSampler::SrcConfig sc;
356 int srcBytesPerPixel = 4;
rmistry@google.comd6176b02012-08-23 18:14:13 +0000357
reed@android.com11344262009-07-08 20:09:23 +0000358 if (colorTable != NULL) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000359 sc = SkScaledBitmapSampler::kIndex;
360 srcBytesPerPixel = 1;
361 } else if (hasAlpha) {
362 sc = SkScaledBitmapSampler::kRGBA;
363 } else {
364 sc = SkScaledBitmapSampler::kRGBX;
365 }
reed@android.com11344262009-07-08 20:09:23 +0000366
367 /* We have to pass the colortable explicitly, since we may have one
368 even if our decodedBitmap doesn't, due to the request that we
369 upscale png's palette to a direct model
370 */
371 SkAutoLockColors ctLock(colorTable);
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000372 if (!sampler.begin(decodedBitmap, sc, doDither, ctLock.colors(),
373 this->getRequireUnpremultipliedColors())) {
reed@android.com862e91b2009-04-28 15:27:07 +0000374 return false;
375 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000376 const int height = decodedBitmap->height();
377
reed@android.com862e91b2009-04-28 15:27:07 +0000378 if (number_passes > 1) {
379 SkAutoMalloc storage(origWidth * origHeight * srcBytesPerPixel);
380 uint8_t* base = (uint8_t*)storage.get();
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000381 size_t rowBytes = origWidth * srcBytesPerPixel;
reed@android.coma8a8b8b2009-05-04 15:00:11 +0000382
reed@android.com862e91b2009-04-28 15:27:07 +0000383 for (int i = 0; i < number_passes; i++) {
384 uint8_t* row = base;
385 for (png_uint_32 y = 0; y < origHeight; y++) {
386 uint8_t* bmRow = row;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000387 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
388 row += rowBytes;
reed@android.com862e91b2009-04-28 15:27:07 +0000389 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000390 }
reed@android.com862e91b2009-04-28 15:27:07 +0000391 // now sample it
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000392 base += sampler.srcY0() * rowBytes;
reed@android.com862e91b2009-04-28 15:27:07 +0000393 for (int y = 0; y < height; y++) {
394 reallyHasAlpha |= sampler.next(base);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000395 base += sampler.srcDY() * rowBytes;
reed@android.com862e91b2009-04-28 15:27:07 +0000396 }
397 } else {
398 SkAutoMalloc storage(origWidth * srcBytesPerPixel);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000399 uint8_t* srcRow = (uint8_t*)storage.get();
400 skip_src_rows(png_ptr, srcRow, sampler.srcY0());
401
402 for (int y = 0; y < height; y++) {
403 uint8_t* tmp = srcRow;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000404 png_read_rows(png_ptr, &tmp, png_bytepp_NULL, 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000405 reallyHasAlpha |= sampler.next(srcRow);
406 if (y < height - 1) {
407 skip_src_rows(png_ptr, srcRow, sampler.srcDY() - 1);
408 }
409 }
reed@android.com862e91b2009-04-28 15:27:07 +0000410
reed@android.com8a1c16f2008-12-17 15:59:43 +0000411 // skip the rest of the rows (if any)
412 png_uint_32 read = (height - 1) * sampler.srcDY() +
413 sampler.srcY0() + 1;
414 SkASSERT(read <= origHeight);
415 skip_src_rows(png_ptr, srcRow, origHeight - read);
416 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000417 }
418
419 /* read rest of file, and get additional chunks in info_ptr - REQUIRED */
420 png_read_end(png_ptr, info_ptr);
421
422 if (0 != theTranspColor) {
423 reallyHasAlpha |= substituteTranspColor(decodedBitmap, theTranspColor);
424 }
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000425 if (reallyHasAlpha && this->getRequireUnpremultipliedColors() &&
426 SkBitmap::kARGB_8888_Config != decodedBitmap->config()) {
427 // If the caller wants an unpremultiplied bitmap, and we let them get
428 // away with a config other than 8888, and it has alpha after all,
429 // return false, since the result will have premultiplied colors.
430 return false;
431 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000432 decodedBitmap->setIsOpaque(!reallyHasAlpha);
433 return true;
434}
435
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000436
437
438bool SkPNGImageDecoder::getBitmapConfig(png_structp png_ptr, png_infop info_ptr,
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000439 SkBitmap::Config *configp, bool * SK_RESTRICT hasAlphap,
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000440 bool *doDitherp, SkPMColor *theTranspColorp) {
441 png_uint_32 origWidth, origHeight;
442 int bitDepth, colorType;
443 png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
444 &colorType, int_p_NULL, int_p_NULL, int_p_NULL);
445
446 // check for sBIT chunk data, in case we should disable dithering because
447 // our data is not truely 8bits per component
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000448 png_color_8p sig_bit;
449 if (*doDitherp && png_get_sBIT(png_ptr, info_ptr, &sig_bit)) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000450#if 0
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000451 SkDebugf("----- sBIT %d %d %d %d\n", sig_bit->red, sig_bit->green,
452 sig_bit->blue, sig_bit->alpha);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000453#endif
454 // 0 seems to indicate no information available
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000455 if (pos_le(sig_bit->red, SK_R16_BITS) &&
456 pos_le(sig_bit->green, SK_G16_BITS) &&
457 pos_le(sig_bit->blue, SK_B16_BITS)) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000458 *doDitherp = false;
459 }
460 }
461
462 if (colorType == PNG_COLOR_TYPE_PALETTE) {
463 bool paletteHasAlpha = hasTransparencyInPalette(png_ptr, info_ptr);
464 *configp = this->getPrefConfig(kIndex_SrcDepth, paletteHasAlpha);
465 // now see if we can upscale to their requested config
466 if (!canUpscalePaletteToConfig(*configp, paletteHasAlpha)) {
467 *configp = SkBitmap::kIndex8_Config;
468 }
469 } else {
470 png_color_16p transpColor = NULL;
471 int numTransp = 0;
472
473 png_get_tRNS(png_ptr, info_ptr, NULL, &numTransp, &transpColor);
474
475 bool valid = png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS);
476
477 if (valid && numTransp == 1 && transpColor != NULL) {
478 /* Compute our transparent color, which we'll match against later.
479 We don't really handle 16bit components properly here, since we
480 do our compare *after* the values have been knocked down to 8bit
481 which means we will find more matches than we should. The real
482 fix seems to be to see the actual 16bit components, do the
483 compare, and then knock it down to 8bits ourselves.
484 */
485 if (colorType & PNG_COLOR_MASK_COLOR) {
486 if (16 == bitDepth) {
487 *theTranspColorp = SkPackARGB32(0xFF, transpColor->red >> 8,
488 transpColor->green >> 8,
489 transpColor->blue >> 8);
490 } else {
491 *theTranspColorp = SkPackARGB32(0xFF, transpColor->red,
492 transpColor->green,
493 transpColor->blue);
494 }
495 } else { // gray
496 if (16 == bitDepth) {
497 *theTranspColorp = SkPackARGB32(0xFF, transpColor->gray >> 8,
498 transpColor->gray >> 8,
499 transpColor->gray >> 8);
500 } else {
501 *theTranspColorp = SkPackARGB32(0xFF, transpColor->gray,
502 transpColor->gray,
503 transpColor->gray);
504 }
505 }
506 }
507
508 if (valid ||
509 PNG_COLOR_TYPE_RGB_ALPHA == colorType ||
510 PNG_COLOR_TYPE_GRAY_ALPHA == colorType) {
511 *hasAlphap = true;
512 }
513 *configp = this->getPrefConfig(k32Bit_SrcDepth, *hasAlphap);
514 // now match the request against our capabilities
515 if (*hasAlphap) {
516 if (*configp != SkBitmap::kARGB_4444_Config) {
517 *configp = SkBitmap::kARGB_8888_Config;
518 }
519 } else {
520 if (*configp != SkBitmap::kRGB_565_Config &&
521 *configp != SkBitmap::kARGB_4444_Config) {
522 *configp = SkBitmap::kARGB_8888_Config;
523 }
524 }
525 }
526
527 // sanity check for size
528 {
529 Sk64 size;
530 size.setMul(origWidth, origHeight);
531 if (size.isNeg() || !size.is32()) {
532 return false;
533 }
534 // now check that if we are 4-bytes per pixel, we also don't overflow
535 if (size.get32() > (0x7FFFFFFF >> 2)) {
536 return false;
537 }
538 }
539
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000540 if (!this->chooseFromOneChoice(*configp, origWidth, origHeight)) {
541 return false;
542 }
543
544 // If the image has alpha and the decoder wants unpremultiplied
545 // colors, the only supported config is 8888.
546 if (this->getRequireUnpremultipliedColors() && *hasAlphap) {
547 *configp = SkBitmap::kARGB_8888_Config;
548 }
549 return true;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000550}
551
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000552typedef uint32_t (*PackColorProc)(U8CPU a, U8CPU r, U8CPU g, U8CPU b);
553
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000554bool SkPNGImageDecoder::decodePalette(png_structp png_ptr, png_infop info_ptr,
555 bool *hasAlphap, bool *reallyHasAlphap,
556 SkColorTable **colorTablep) {
557 int numPalette;
558 png_colorp palette;
559 png_bytep trans;
560 int numTrans;
561 bool reallyHasAlpha = false;
562 SkColorTable* colorTable = NULL;
563
564 png_get_PLTE(png_ptr, info_ptr, &palette, &numPalette);
565
566 /* BUGGY IMAGE WORKAROUND
567
568 We hit some images (e.g. fruit_.png) who contain bytes that are == colortable_count
569 which is a problem since we use the byte as an index. To work around this we grow
570 the colortable by 1 (if its < 256) and duplicate the last color into that slot.
571 */
572 int colorCount = numPalette + (numPalette < 256);
573
574 colorTable = SkNEW_ARGS(SkColorTable, (colorCount));
575
576 SkPMColor* colorPtr = colorTable->lockColors();
577 if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
578 png_get_tRNS(png_ptr, info_ptr, &trans, &numTrans, NULL);
579 *hasAlphap = (numTrans > 0);
580 } else {
581 numTrans = 0;
582 colorTable->setFlags(colorTable->getFlags() | SkColorTable::kColorsAreOpaque_Flag);
583 }
584 // check for bad images that might make us crash
585 if (numTrans > numPalette) {
586 numTrans = numPalette;
587 }
588
589 int index = 0;
590 int transLessThanFF = 0;
591
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000592 // Choose which function to use to create the color table. If the final destination's
593 // config is unpremultiplied, the color table will store unpremultiplied colors.
594 PackColorProc proc;
595 if (this->getRequireUnpremultipliedColors()) {
596 proc = &SkPackARGB32NoCheck;
597 } else {
598 proc = &SkPreMultiplyARGB;
599 }
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000600 for (; index < numTrans; index++) {
601 transLessThanFF |= (int)*trans - 0xFF;
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000602 *colorPtr++ = proc(*trans++, palette->red, palette->green, palette->blue);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000603 palette++;
604 }
605 reallyHasAlpha |= (transLessThanFF < 0);
606
607 for (; index < numPalette; index++) {
608 *colorPtr++ = SkPackARGB32(0xFF, palette->red, palette->green, palette->blue);
609 palette++;
610 }
611
612 // see BUGGY IMAGE WORKAROUND comment above
613 if (numPalette < 256) {
614 *colorPtr = colorPtr[-1];
615 }
616 colorTable->unlockColors(true);
617 *colorTablep = colorTable;
618 *reallyHasAlphap = reallyHasAlpha;
619 return true;
620}
621
622#ifdef SK_BUILD_FOR_ANDROID
623
624bool SkPNGImageDecoder::onBuildTileIndex(SkStream* sk_stream, int *width, int *height) {
625 png_structp png_ptr;
626 png_infop info_ptr;
627
628 if (!onDecodeInit(sk_stream, &png_ptr, &info_ptr)) {
629 return false;
630 }
631
632 if (setjmp(png_jmpbuf(png_ptr)) != 0) {
633 png_destroy_read_struct(&png_ptr, &info_ptr, png_infopp_NULL);
634 return false;
635 }
636
637 png_uint_32 origWidth, origHeight;
638 int bitDepth, colorType;
639 png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
640 &colorType, int_p_NULL, int_p_NULL, int_p_NULL);
641
642 *width = origWidth;
643 *height = origHeight;
644
645 png_build_index(png_ptr);
646
647 if (fImageIndex) {
648 SkDELETE(fImageIndex);
649 }
650 fImageIndex = SkNEW_ARGS(SkPNGImageIndex, (png_ptr, info_ptr));
651
652 return true;
653}
654
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000655bool SkPNGImageDecoder::onDecodeSubset(SkBitmap* bm, const SkIRect& region) {
656 if (NULL == fImageIndex) {
657 return false;
658 }
659
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000660 png_structp png_ptr = fImageIndex->png_ptr;
661 png_infop info_ptr = fImageIndex->info_ptr;
662 if (setjmp(png_jmpbuf(png_ptr))) {
663 return false;
664 }
665
666 png_uint_32 origWidth, origHeight;
667 int bitDepth, colorType, interlaceType;
668 png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
669 &colorType, &interlaceType, int_p_NULL, int_p_NULL);
670
671 SkIRect rect = SkIRect::MakeWH(origWidth, origHeight);
672
673 if (!rect.intersect(region)) {
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000674 // If the requested region is entirely outside the image, just
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000675 // returns false
676 return false;
677 }
678
679 SkBitmap::Config config;
680 bool hasAlpha = false;
681 bool doDither = this->getDitherImage();
682 SkPMColor theTranspColor = 0; // 0 tells us not to try to match
683
684 if (!getBitmapConfig(png_ptr, info_ptr, &config, &hasAlpha, &doDither, &theTranspColor)) {
685 return false;
686 }
687
688 const int sampleSize = this->getSampleSize();
689 SkScaledBitmapSampler sampler(origWidth, rect.height(), sampleSize);
690
691 SkBitmap decodedBitmap;
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000692 decodedBitmap.setConfig(config, sampler.scaledWidth(), sampler.scaledHeight());
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000693
694 // from here down we are concerned with colortables and pixels
695
696 // we track if we actually see a non-opaque pixels, since sometimes a PNG sets its colortype
697 // to |= PNG_COLOR_MASK_ALPHA, but all of its pixels are in fact opaque. We care, since we
698 // draw lots faster if we can flag the bitmap has being opaque
699 bool reallyHasAlpha = false;
700 SkColorTable* colorTable = NULL;
701
702 if (colorType == PNG_COLOR_TYPE_PALETTE) {
703 decodePalette(png_ptr, info_ptr, &hasAlpha, &reallyHasAlpha, &colorTable);
704 }
705
706 SkAutoUnref aur(colorTable);
707
708 // Check ahead of time if the swap(dest, src) is possible.
709 // If yes, then we will stick to AllocPixelRef since it's cheaper with the swap happening.
710 // If no, then we will use alloc to allocate pixels to prevent garbage collection.
711 int w = rect.width() / sampleSize;
712 int h = rect.height() / sampleSize;
713 const bool swapOnly = (rect == region) && (w == decodedBitmap.width()) &&
714 (h == decodedBitmap.height()) && bm->isNull();
715 const bool needColorTable = SkBitmap::kIndex8_Config == config;
716 if (swapOnly) {
717 if (!this->allocPixelRef(&decodedBitmap, needColorTable ? colorTable : NULL)) {
718 return false;
719 }
720 } else {
721 if (!decodedBitmap.allocPixels(NULL, needColorTable ? colorTable : NULL)) {
722 return false;
723 }
724 }
725 SkAutoLockPixels alp(decodedBitmap);
726
727 /* Add filler (or alpha) byte (before/after each RGB triplet) */
728 if (colorType == PNG_COLOR_TYPE_RGB || colorType == PNG_COLOR_TYPE_GRAY) {
729 png_set_filler(png_ptr, 0xff, PNG_FILLER_AFTER);
730 }
731
732 /* Turn on interlace handling. REQUIRED if you are not using
733 * png_read_image(). To see how to handle interlacing passes,
734 * see the png_read_row() method below:
735 */
736 const int number_passes = (interlaceType != PNG_INTERLACE_NONE) ?
737 png_set_interlace_handling(png_ptr) : 1;
738
739 /* Optional call to gamma correct and add the background to the palette
740 * and update info structure. REQUIRED if you are expecting libpng to
741 * update the palette for you (ie you selected such a transform above).
742 */
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000743
744 // Direct access to png_ptr fields is deprecated in libpng > 1.2.
745#if defined(PNG_1_0_X) || defined (PNG_1_2_X)
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000746 png_ptr->pass = 0;
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000747#else
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000748 // FIXME: This sets pass as desired, but also sets iwidth. Is that ok?
749 png_set_interlaced_pass(png_ptr, 0);
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000750#endif
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000751 png_read_update_info(png_ptr, info_ptr);
752
753 int actualTop = rect.fTop;
754
755 if (SkBitmap::kIndex8_Config == config && 1 == sampleSize) {
756 for (int i = 0; i < number_passes; i++) {
757 png_configure_decoder(png_ptr, &actualTop, i);
758 for (int j = 0; j < rect.fTop - actualTop; j++) {
759 uint8_t* bmRow = decodedBitmap.getAddr8(0, 0);
760 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
761 }
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000762 png_uint_32 bitmapHeight = (png_uint_32) decodedBitmap.height();
763 for (png_uint_32 y = 0; y < bitmapHeight; y++) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000764 uint8_t* bmRow = decodedBitmap.getAddr8(0, y);
765 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
766 }
767 }
768 } else {
769 SkScaledBitmapSampler::SrcConfig sc;
770 int srcBytesPerPixel = 4;
771
772 if (colorTable != NULL) {
773 sc = SkScaledBitmapSampler::kIndex;
774 srcBytesPerPixel = 1;
775 } else if (hasAlpha) {
776 sc = SkScaledBitmapSampler::kRGBA;
777 } else {
778 sc = SkScaledBitmapSampler::kRGBX;
779 }
780
781 /* We have to pass the colortable explicitly, since we may have one
782 even if our decodedBitmap doesn't, due to the request that we
783 upscale png's palette to a direct model
784 */
785 SkAutoLockColors ctLock(colorTable);
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000786 if (!sampler.begin(&decodedBitmap, sc, doDither, ctLock.colors(),
787 this->getRequireUnpremultipliedColors())) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000788 return false;
789 }
790 const int height = decodedBitmap.height();
791
792 if (number_passes > 1) {
793 SkAutoMalloc storage(origWidth * origHeight * srcBytesPerPixel);
794 uint8_t* base = (uint8_t*)storage.get();
795 size_t rb = origWidth * srcBytesPerPixel;
796
797 for (int i = 0; i < number_passes; i++) {
798 png_configure_decoder(png_ptr, &actualTop, i);
799 for (int j = 0; j < rect.fTop - actualTop; j++) {
800 uint8_t* bmRow = (uint8_t*)decodedBitmap.getPixels();
801 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
802 }
803 uint8_t* row = base;
804 for (int32_t y = 0; y < rect.height(); y++) {
805 uint8_t* bmRow = row;
806 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
807 row += rb;
808 }
809 }
810 // now sample it
811 base += sampler.srcY0() * rb;
812 for (int y = 0; y < height; y++) {
813 reallyHasAlpha |= sampler.next(base);
814 base += sampler.srcDY() * rb;
815 }
816 } else {
817 SkAutoMalloc storage(origWidth * srcBytesPerPixel);
818 uint8_t* srcRow = (uint8_t*)storage.get();
819
820 png_configure_decoder(png_ptr, &actualTop, 0);
821 skip_src_rows(png_ptr, srcRow, sampler.srcY0());
822
823 for (int i = 0; i < rect.fTop - actualTop; i++) {
824 uint8_t* bmRow = (uint8_t*)decodedBitmap.getPixels();
825 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
826 }
827 for (int y = 0; y < height; y++) {
828 uint8_t* tmp = srcRow;
829 png_read_rows(png_ptr, &tmp, png_bytepp_NULL, 1);
830 reallyHasAlpha |= sampler.next(srcRow);
831 if (y < height - 1) {
832 skip_src_rows(png_ptr, srcRow, sampler.srcDY() - 1);
833 }
834 }
835 }
836 }
837
838 if (0 != theTranspColor) {
839 reallyHasAlpha |= substituteTranspColor(&decodedBitmap, theTranspColor);
840 }
841 decodedBitmap.setIsOpaque(!reallyHasAlpha);
842
843 if (swapOnly) {
844 bm->swap(decodedBitmap);
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000845 return true;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000846 }
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000847 return this->cropBitmap(bm, &decodedBitmap, sampleSize, region.x(), region.y(),
848 region.width(), region.height(), 0, rect.y());
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000849}
850#endif
851
reed@android.com8a1c16f2008-12-17 15:59:43 +0000852///////////////////////////////////////////////////////////////////////////////
853
reed@android.com8a1c16f2008-12-17 15:59:43 +0000854#include "SkColorPriv.h"
855#include "SkUnPreMultiply.h"
856
857static void sk_write_fn(png_structp png_ptr, png_bytep data, png_size_t len) {
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000858 SkWStream* sk_stream = (SkWStream*)png_get_io_ptr(png_ptr);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000859 if (!sk_stream->write(data, len)) {
860 png_error(png_ptr, "sk_write_fn Error!");
861 }
862}
863
reed@android.com8a1c16f2008-12-17 15:59:43 +0000864static transform_scanline_proc choose_proc(SkBitmap::Config config,
865 bool hasAlpha) {
866 // we don't care about search on alpha if we're kIndex8, since only the
867 // colortable packing cares about that distinction, not the pixels
868 if (SkBitmap::kIndex8_Config == config) {
869 hasAlpha = false; // we store false in the table entries for kIndex8
870 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000871
reed@android.com8a1c16f2008-12-17 15:59:43 +0000872 static const struct {
873 SkBitmap::Config fConfig;
874 bool fHasAlpha;
875 transform_scanline_proc fProc;
876 } gMap[] = {
877 { SkBitmap::kRGB_565_Config, false, transform_scanline_565 },
878 { SkBitmap::kARGB_8888_Config, false, transform_scanline_888 },
879 { SkBitmap::kARGB_8888_Config, true, transform_scanline_8888 },
880 { SkBitmap::kARGB_4444_Config, false, transform_scanline_444 },
881 { SkBitmap::kARGB_4444_Config, true, transform_scanline_4444 },
epoger@google.com4ce738b2012-11-16 18:44:18 +0000882 { SkBitmap::kIndex8_Config, false, transform_scanline_memcpy },
reed@android.com8a1c16f2008-12-17 15:59:43 +0000883 };
884
885 for (int i = SK_ARRAY_COUNT(gMap) - 1; i >= 0; --i) {
886 if (gMap[i].fConfig == config && gMap[i].fHasAlpha == hasAlpha) {
887 return gMap[i].fProc;
888 }
889 }
890 sk_throw();
891 return NULL;
892}
893
894// return the minimum legal bitdepth (by png standards) for this many colortable
895// entries. SkBitmap always stores in 8bits per pixel, but for colorcount <= 16,
896// we can use fewer bits per in png
897static int computeBitDepth(int colorCount) {
898#if 0
899 int bits = SkNextLog2(colorCount);
900 SkASSERT(bits >= 1 && bits <= 8);
901 // now we need bits itself to be a power of 2 (e.g. 1, 2, 4, 8)
902 return SkNextPow2(bits);
903#else
904 // for the moment, we don't know how to pack bitdepth < 8
905 return 8;
906#endif
907}
908
909/* Pack palette[] with the corresponding colors, and if hasAlpha is true, also
910 pack trans[] and return the number of trans[] entries written. If hasAlpha
911 is false, the return value will always be 0.
rmistry@google.comd6176b02012-08-23 18:14:13 +0000912
reed@android.com8a1c16f2008-12-17 15:59:43 +0000913 Note: this routine takes care of unpremultiplying the RGB values when we
914 have alpha in the colortable, since png doesn't support premul colors
915*/
reed@android.com6f252972009-01-14 16:46:16 +0000916static inline int pack_palette(SkColorTable* ctable,
reed@android.comb50a60c2009-01-14 17:51:08 +0000917 png_color* SK_RESTRICT palette,
918 png_byte* SK_RESTRICT trans, bool hasAlpha) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000919 SkAutoLockColors alc(ctable);
920 const SkPMColor* SK_RESTRICT colors = alc.colors();
921 const int ctCount = ctable->count();
922 int i, num_trans = 0;
923
924 if (hasAlpha) {
925 /* first see if we have some number of fully opaque at the end of the
926 ctable. PNG allows num_trans < num_palette, but all of the trans
927 entries must come first in the palette. If I was smarter, I'd
928 reorder the indices and ctable so that all non-opaque colors came
929 first in the palette. But, since that would slow down the encode,
930 I'm leaving the indices and ctable order as is, and just looking
931 at the tail of the ctable for opaqueness.
932 */
933 num_trans = ctCount;
934 for (i = ctCount - 1; i >= 0; --i) {
935 if (SkGetPackedA32(colors[i]) != 0xFF) {
936 break;
937 }
938 num_trans -= 1;
939 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000940
reed@android.com8a1c16f2008-12-17 15:59:43 +0000941 const SkUnPreMultiply::Scale* SK_RESTRICT table =
942 SkUnPreMultiply::GetScaleTable();
943
944 for (i = 0; i < num_trans; i++) {
945 const SkPMColor c = *colors++;
946 const unsigned a = SkGetPackedA32(c);
947 const SkUnPreMultiply::Scale s = table[a];
948 trans[i] = a;
949 palette[i].red = SkUnPreMultiply::ApplyScale(s, SkGetPackedR32(c));
950 palette[i].green = SkUnPreMultiply::ApplyScale(s,SkGetPackedG32(c));
951 palette[i].blue = SkUnPreMultiply::ApplyScale(s, SkGetPackedB32(c));
rmistry@google.comd6176b02012-08-23 18:14:13 +0000952 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000953 // now fall out of this if-block to use common code for the trailing
954 // opaque entries
955 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000956
reed@android.com8a1c16f2008-12-17 15:59:43 +0000957 // these (remaining) entries are opaque
958 for (i = num_trans; i < ctCount; i++) {
959 SkPMColor c = *colors++;
960 palette[i].red = SkGetPackedR32(c);
961 palette[i].green = SkGetPackedG32(c);
962 palette[i].blue = SkGetPackedB32(c);
963 }
964 return num_trans;
965}
966
967class SkPNGImageEncoder : public SkImageEncoder {
968protected:
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000969 virtual bool onEncode(SkWStream* stream, const SkBitmap& bm, int quality) SK_OVERRIDE;
tomhudson@google.com5c210c72011-07-28 21:06:40 +0000970private:
971 bool doEncode(SkWStream* stream, const SkBitmap& bm,
972 const bool& hasAlpha, int colorType,
973 int bitDepth, SkBitmap::Config config,
974 png_color_8& sig_bit);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000975
976 typedef SkImageEncoder INHERITED;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000977};
978
979bool SkPNGImageEncoder::onEncode(SkWStream* stream, const SkBitmap& bitmap,
980 int /*quality*/) {
981 SkBitmap::Config config = bitmap.getConfig();
982
983 const bool hasAlpha = !bitmap.isOpaque();
984 int colorType = PNG_COLOR_MASK_COLOR;
985 int bitDepth = 8; // default for color
986 png_color_8 sig_bit;
987
988 switch (config) {
989 case SkBitmap::kIndex8_Config:
990 colorType |= PNG_COLOR_MASK_PALETTE;
991 // fall through to the ARGB_8888 case
992 case SkBitmap::kARGB_8888_Config:
993 sig_bit.red = 8;
994 sig_bit.green = 8;
995 sig_bit.blue = 8;
996 sig_bit.alpha = 8;
997 break;
998 case SkBitmap::kARGB_4444_Config:
999 sig_bit.red = 4;
1000 sig_bit.green = 4;
1001 sig_bit.blue = 4;
1002 sig_bit.alpha = 4;
1003 break;
1004 case SkBitmap::kRGB_565_Config:
1005 sig_bit.red = 5;
1006 sig_bit.green = 6;
1007 sig_bit.blue = 5;
1008 sig_bit.alpha = 0;
1009 break;
1010 default:
1011 return false;
1012 }
rmistry@google.comd6176b02012-08-23 18:14:13 +00001013
reed@android.com8a1c16f2008-12-17 15:59:43 +00001014 if (hasAlpha) {
1015 // don't specify alpha if we're a palette, even if our ctable has alpha
1016 if (!(colorType & PNG_COLOR_MASK_PALETTE)) {
1017 colorType |= PNG_COLOR_MASK_ALPHA;
1018 }
1019 } else {
1020 sig_bit.alpha = 0;
1021 }
rmistry@google.comd6176b02012-08-23 18:14:13 +00001022
reed@android.com8a1c16f2008-12-17 15:59:43 +00001023 SkAutoLockPixels alp(bitmap);
1024 // readyToDraw checks for pixels (and colortable if that is required)
1025 if (!bitmap.readyToDraw()) {
1026 return false;
1027 }
1028
1029 // we must do this after we have locked the pixels
1030 SkColorTable* ctable = bitmap.getColorTable();
1031 if (NULL != ctable) {
1032 if (ctable->count() == 0) {
1033 return false;
1034 }
1035 // check if we can store in fewer than 8 bits
1036 bitDepth = computeBitDepth(ctable->count());
1037 }
1038
tomhudson@google.com5c210c72011-07-28 21:06:40 +00001039 return doEncode(stream, bitmap, hasAlpha, colorType,
1040 bitDepth, config, sig_bit);
1041}
1042
1043bool SkPNGImageEncoder::doEncode(SkWStream* stream, const SkBitmap& bitmap,
1044 const bool& hasAlpha, int colorType,
1045 int bitDepth, SkBitmap::Config config,
1046 png_color_8& sig_bit) {
1047
reed@android.com8a1c16f2008-12-17 15:59:43 +00001048 png_structp png_ptr;
1049 png_infop info_ptr;
1050
1051 png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, sk_error_fn,
1052 NULL);
1053 if (NULL == png_ptr) {
1054 return false;
1055 }
1056
1057 info_ptr = png_create_info_struct(png_ptr);
1058 if (NULL == info_ptr) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +00001059 png_destroy_write_struct(&png_ptr, png_infopp_NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001060 return false;
1061 }
1062
1063 /* Set error handling. REQUIRED if you aren't supplying your own
1064 * error handling functions in the png_create_write_struct() call.
1065 */
1066 if (setjmp(png_jmpbuf(png_ptr))) {
1067 png_destroy_write_struct(&png_ptr, &info_ptr);
1068 return false;
1069 }
1070
commit-bot@chromium.orga936e372013-03-14 14:42:18 +00001071 png_set_write_fn(png_ptr, (void*)stream, sk_write_fn, png_flush_ptr_NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001072
1073 /* Set the image information here. Width and height are up to 2^31,
1074 * bit_depth is one of 1, 2, 4, 8, or 16, but valid values also depend on
1075 * the color_type selected. color_type is one of PNG_COLOR_TYPE_GRAY,
1076 * PNG_COLOR_TYPE_GRAY_ALPHA, PNG_COLOR_TYPE_PALETTE, PNG_COLOR_TYPE_RGB,
1077 * or PNG_COLOR_TYPE_RGB_ALPHA. interlace is either PNG_INTERLACE_NONE or
1078 * PNG_INTERLACE_ADAM7, and the compression_type and filter_type MUST
1079 * currently be PNG_COMPRESSION_TYPE_BASE and PNG_FILTER_TYPE_BASE. REQUIRED
1080 */
1081
1082 png_set_IHDR(png_ptr, info_ptr, bitmap.width(), bitmap.height(),
1083 bitDepth, colorType,
1084 PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE,
1085 PNG_FILTER_TYPE_BASE);
1086
reed@android.com61898772009-07-07 19:38:01 +00001087 // set our colortable/trans arrays if needed
1088 png_color paletteColors[256];
1089 png_byte trans[256];
1090 if (SkBitmap::kIndex8_Config == config) {
1091 SkColorTable* ct = bitmap.getColorTable();
1092 int numTrans = pack_palette(ct, paletteColors, trans, hasAlpha);
1093 png_set_PLTE(png_ptr, info_ptr, paletteColors, ct->count());
1094 if (numTrans > 0) {
1095 png_set_tRNS(png_ptr, info_ptr, trans, numTrans, NULL);
1096 }
1097 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001098
1099 png_set_sBIT(png_ptr, info_ptr, &sig_bit);
1100 png_write_info(png_ptr, info_ptr);
1101
1102 const char* srcImage = (const char*)bitmap.getPixels();
1103 SkAutoSMalloc<1024> rowStorage(bitmap.width() << 2);
1104 char* storage = (char*)rowStorage.get();
1105 transform_scanline_proc proc = choose_proc(config, hasAlpha);
1106
1107 for (int y = 0; y < bitmap.height(); y++) {
1108 png_bytep row_ptr = (png_bytep)storage;
1109 proc(srcImage, bitmap.width(), storage);
1110 png_write_rows(png_ptr, &row_ptr, 1);
1111 srcImage += bitmap.rowBytes();
1112 }
1113
1114 png_write_end(png_ptr, info_ptr);
1115
1116 /* clean up after the write, and free any memory allocated */
1117 png_destroy_write_struct(&png_ptr, &info_ptr);
1118 return true;
1119}
1120
reed@android.com00bf85a2009-01-22 13:04:56 +00001121///////////////////////////////////////////////////////////////////////////////
robertphillips@google.comec51cb82012-03-23 18:13:47 +00001122DEFINE_DECODER_CREATOR(PNGImageDecoder);
1123DEFINE_ENCODER_CREATOR(PNGImageEncoder);
1124///////////////////////////////////////////////////////////////////////////////
reed@android.com00bf85a2009-01-22 13:04:56 +00001125
1126#include "SkTRegistry.h"
1127
scroggo@google.com39edf4c2013-04-25 17:33:51 +00001128static bool is_png(SkStream* stream) {
reed@android.com00bf85a2009-01-22 13:04:56 +00001129 char buf[PNG_BYTES_TO_CHECK];
1130 if (stream->read(buf, PNG_BYTES_TO_CHECK) == PNG_BYTES_TO_CHECK &&
1131 !png_sig_cmp((png_bytep) buf, (png_size_t)0, PNG_BYTES_TO_CHECK)) {
scroggo@google.com39edf4c2013-04-25 17:33:51 +00001132 return true;
1133 }
1134 return false;
1135}
1136
1137SkImageDecoder* sk_libpng_dfactory(SkStream* stream) {
1138 if (is_png(stream)) {
reed@android.com00bf85a2009-01-22 13:04:56 +00001139 return SkNEW(SkPNGImageDecoder);
1140 }
1141 return NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001142}
1143
scroggo@google.com39edf4c2013-04-25 17:33:51 +00001144static SkImageDecoder::Format get_format_png(SkStream* stream) {
1145 if (is_png(stream)) {
1146 return SkImageDecoder::kPNG_Format;
1147 }
1148 return SkImageDecoder::kUnknown_Format;
1149}
1150
reed@android.comdfee5792010-04-15 14:24:50 +00001151SkImageEncoder* sk_libpng_efactory(SkImageEncoder::Type t) {
reed@android.com00bf85a2009-01-22 13:04:56 +00001152 return (SkImageEncoder::kPNG_Type == t) ? SkNEW(SkPNGImageEncoder) : NULL;
1153}
1154
reed@android.comdfee5792010-04-15 14:24:50 +00001155static SkTRegistry<SkImageEncoder*, SkImageEncoder::Type> gEReg(sk_libpng_efactory);
scroggo@google.com39edf4c2013-04-25 17:33:51 +00001156static SkTRegistry<SkImageDecoder::Format, SkStream*> gFormatReg(get_format_png);
reed@android.comdfee5792010-04-15 14:24:50 +00001157static SkTRegistry<SkImageDecoder*, SkStream*> gDReg(sk_libpng_dfactory);