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