blob: 56c19fd98cb6d9a8837dfa9fbec5ea6ab507c145 [file] [log] [blame]
epoger@google.comec3ed6a2011-07-28 14:26:00 +00001
2/*
3 * Copyright 2006 The Android Open Source Project
4 *
5 * Use of this source code is governed by a BSD-style license that can be
6 * found in the LICENSE file.
7 */
8
reed@android.com8a1c16f2008-12-17 15:59:43 +00009
10#include "SkImageDecoder.h"
reed@android.comb08eb2b2009-01-06 20:16:26 +000011#include "SkImageEncoder.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000012#include "SkColor.h"
13#include "SkColorPriv.h"
14#include "SkDither.h"
15#include "SkMath.h"
16#include "SkScaledBitmapSampler.h"
17#include "SkStream.h"
18#include "SkTemplates.h"
19#include "SkUtils.h"
epoger@google.com4ce738b2012-11-16 18:44:18 +000020#include "transform_scanline.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000021
22extern "C" {
23#include "png.h"
24}
25
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +000026/* These were dropped in libpng >= 1.4 */
27#ifndef png_infopp_NULL
28#define png_infopp_NULL NULL
29#endif
30
31#ifndef png_bytepp_NULL
32#define png_bytepp_NULL NULL
33#endif
34
35#ifndef int_p_NULL
36#define int_p_NULL NULL
37#endif
38
39#ifndef png_flush_ptr_NULL
40#define png_flush_ptr_NULL NULL
41#endif
42
commit-bot@chromium.orga936e372013-03-14 14:42:18 +000043class SkPNGImageIndex {
44public:
scroggo@google.comc70a3aa2013-07-18 20:03:15 +000045 SkPNGImageIndex(SkStream* stream, png_structp png_ptr, png_infop info_ptr)
46 : fStream(stream)
47 , fPng_ptr(png_ptr)
48 , fInfo_ptr(info_ptr)
49 , fConfig(SkBitmap::kNo_Config) {
50 SkASSERT(stream != NULL);
51 stream->ref();
commit-bot@chromium.orga936e372013-03-14 14:42:18 +000052 }
53 ~SkPNGImageIndex() {
scroggo@google.comc70a3aa2013-07-18 20:03:15 +000054 if (NULL != fPng_ptr) {
55 png_destroy_read_struct(&fPng_ptr, &fInfo_ptr, png_infopp_NULL);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +000056 }
57 }
58
scroggo@google.comc70a3aa2013-07-18 20:03:15 +000059 SkAutoTUnref<SkStream> fStream;
60 png_structp fPng_ptr;
61 png_infop fInfo_ptr;
62 SkBitmap::Config fConfig;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +000063};
64
reed@android.com8a1c16f2008-12-17 15:59:43 +000065class SkPNGImageDecoder : public SkImageDecoder {
66public:
commit-bot@chromium.orga936e372013-03-14 14:42:18 +000067 SkPNGImageDecoder() {
68 fImageIndex = NULL;
69 }
70 virtual Format getFormat() const SK_OVERRIDE {
reed@android.com8a1c16f2008-12-17 15:59:43 +000071 return kPNG_Format;
72 }
scroggo@google.com39edf4c2013-04-25 17:33:51 +000073
commit-bot@chromium.orga936e372013-03-14 14:42:18 +000074 virtual ~SkPNGImageDecoder() {
75 SkDELETE(fImageIndex);
76 }
rmistry@google.comd6176b02012-08-23 18:14:13 +000077
reed@android.com8a1c16f2008-12-17 15:59:43 +000078protected:
commit-bot@chromium.orga936e372013-03-14 14:42:18 +000079#ifdef SK_BUILD_FOR_ANDROID
80 virtual bool onBuildTileIndex(SkStream *stream, int *width, int *height) SK_OVERRIDE;
scroggo@google.com7e6fcee2013-05-03 20:14:28 +000081 virtual bool onDecodeSubset(SkBitmap* bitmap, const SkIRect& region) SK_OVERRIDE;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +000082#endif
83 virtual bool onDecode(SkStream* stream, SkBitmap* bm, Mode) SK_OVERRIDE;
84
85private:
86 SkPNGImageIndex* fImageIndex;
87
88 bool onDecodeInit(SkStream* stream, png_structp *png_ptrp, png_infop *info_ptrp);
scroggo@google.com2bbc2c92013-06-14 15:33:20 +000089 bool decodePalette(png_structp png_ptr, png_infop info_ptr,
90 bool * SK_RESTRICT hasAlphap, bool *reallyHasAlphap,
91 SkColorTable **colorTablep);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +000092 bool getBitmapConfig(png_structp png_ptr, png_infop info_ptr,
93 SkBitmap::Config *config, bool *hasAlpha,
94 bool *doDither, SkPMColor *theTranspColor);
95
96 typedef SkImageDecoder INHERITED;
reed@android.com8a1c16f2008-12-17 15:59:43 +000097};
98
99#ifndef png_jmpbuf
100# define png_jmpbuf(png_ptr) ((png_ptr)->jmpbuf)
101#endif
102
103#define PNG_BYTES_TO_CHECK 4
104
105/* Automatically clean up after throwing an exception */
106struct PNGAutoClean {
107 PNGAutoClean(png_structp p, png_infop i): png_ptr(p), info_ptr(i) {}
108 ~PNGAutoClean() {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000109 png_destroy_read_struct(&png_ptr, &info_ptr, png_infopp_NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000110 }
111private:
112 png_structp png_ptr;
113 png_infop info_ptr;
114};
115
reed@android.com8a1c16f2008-12-17 15:59:43 +0000116static void sk_read_fn(png_structp png_ptr, png_bytep data, png_size_t length) {
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000117 SkStream* sk_stream = (SkStream*) png_get_io_ptr(png_ptr);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000118 size_t bytes = sk_stream->read(data, length);
119 if (bytes != length) {
120 png_error(png_ptr, "Read Error!");
121 }
122}
123
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000124#ifdef SK_BUILD_FOR_ANDROID
125static void sk_seek_fn(png_structp png_ptr, png_uint_32 offset) {
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000126 SkStream* sk_stream = (SkStream*) png_get_io_ptr(png_ptr);
scroggo@google.com4d213ab2013-08-28 13:08:54 +0000127 if (!sk_stream->rewind()) {
128 png_error(png_ptr, "Failed to rewind stream!");
129 }
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000130 (void)sk_stream->skip(offset);
131}
132#endif
133
reed@android.com8a1c16f2008-12-17 15:59:43 +0000134static int sk_read_user_chunk(png_structp png_ptr, png_unknown_chunkp chunk) {
135 SkImageDecoder::Peeker* peeker =
136 (SkImageDecoder::Peeker*)png_get_user_chunk_ptr(png_ptr);
137 // peek() returning true means continue decoding
138 return peeker->peek((const char*)chunk->name, chunk->data, chunk->size) ?
139 1 : -1;
140}
141
142static void sk_error_fn(png_structp png_ptr, png_const_charp msg) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000143 SkDEBUGF(("------ png error %s\n", msg));
reed@android.com8a1c16f2008-12-17 15:59:43 +0000144 longjmp(png_jmpbuf(png_ptr), 1);
145}
146
147static void skip_src_rows(png_structp png_ptr, uint8_t storage[], int count) {
148 for (int i = 0; i < count; i++) {
149 uint8_t* tmp = storage;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000150 png_read_rows(png_ptr, &tmp, png_bytepp_NULL, 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000151 }
152}
153
154static bool pos_le(int value, int max) {
155 return value > 0 && value <= max;
156}
157
158static bool substituteTranspColor(SkBitmap* bm, SkPMColor match) {
159 SkASSERT(bm->config() == SkBitmap::kARGB_8888_Config);
rmistry@google.comd6176b02012-08-23 18:14:13 +0000160
reed@android.com8a1c16f2008-12-17 15:59:43 +0000161 bool reallyHasAlpha = false;
162
163 for (int y = bm->height() - 1; y >= 0; --y) {
164 SkPMColor* p = bm->getAddr32(0, y);
165 for (int x = bm->width() - 1; x >= 0; --x) {
166 if (match == *p) {
167 *p = 0;
168 reallyHasAlpha = true;
169 }
170 p += 1;
171 }
172 }
173 return reallyHasAlpha;
174}
175
reed@android.com3f1f06a2010-03-03 21:04:12 +0000176static bool canUpscalePaletteToConfig(SkBitmap::Config dstConfig,
reed@android.comb6137c32009-07-29 20:56:52 +0000177 bool srcHasAlpha) {
reed@android.com3f1f06a2010-03-03 21:04:12 +0000178 switch (dstConfig) {
reed@android.comb6137c32009-07-29 20:56:52 +0000179 case SkBitmap::kARGB_8888_Config:
180 case SkBitmap::kARGB_4444_Config:
181 return true;
182 case SkBitmap::kRGB_565_Config:
183 // only return true if the src is opaque (since 565 is opaque)
184 return !srcHasAlpha;
185 default:
186 return false;
187 }
188}
189
190// call only if color_type is PALETTE. Returns true if the ctable has alpha
191static bool hasTransparencyInPalette(png_structp png_ptr, png_infop info_ptr) {
192 png_bytep trans;
193 int num_trans;
194
195 if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
196 png_get_tRNS(png_ptr, info_ptr, &trans, &num_trans, NULL);
197 return num_trans > 0;
198 }
199 return false;
reed@android.com11344262009-07-08 20:09:23 +0000200}
201
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000202bool SkPNGImageDecoder::onDecodeInit(SkStream* sk_stream, png_structp *png_ptrp,
203 png_infop *info_ptrp) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000204 /* Create and initialize the png_struct with the desired error handler
205 * functions. If you want to use the default stderr and longjump method,
206 * you can supply NULL for the last three parameters. We also supply the
207 * the compiler header file version, so that we know if the application
208 * was compiled with a compatible version of the library. */
209 png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
210 NULL, sk_error_fn, NULL);
211 // png_voidp user_error_ptr, user_error_fn, user_warning_fn);
212 if (png_ptr == NULL) {
213 return false;
214 }
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000215 *png_ptrp = png_ptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000216
217 /* Allocate/initialize the memory for image information. */
218 png_infop info_ptr = png_create_info_struct(png_ptr);
219 if (info_ptr == NULL) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000220 png_destroy_read_struct(&png_ptr, png_infopp_NULL, png_infopp_NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000221 return false;
222 }
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000223 *info_ptrp = info_ptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000224
225 /* Set error handling if you are using the setjmp/longjmp method (this is
226 * the normal method of doing things with libpng). REQUIRED unless you
227 * set up your own error handlers in the png_create_read_struct() earlier.
228 */
229 if (setjmp(png_jmpbuf(png_ptr))) {
230 return false;
231 }
232
233 /* If you are using replacement read functions, instead of calling
234 * png_init_io() here you would call:
235 */
236 png_set_read_fn(png_ptr, (void *)sk_stream, sk_read_fn);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000237#ifdef SK_BUILD_FOR_ANDROID
238 png_set_seek_fn(png_ptr, sk_seek_fn);
239#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000240 /* where user_io_ptr is a structure you want available to the callbacks */
241 /* If we have already read some of the signature */
242// png_set_sig_bytes(png_ptr, 0 /* sig_read */ );
243
244 // hookup our peeker so we can see any user-chunks the caller may be interested in
245 png_set_keep_unknown_chunks(png_ptr, PNG_HANDLE_CHUNK_ALWAYS, (png_byte*)"", 0);
246 if (this->getPeeker()) {
247 png_set_read_user_chunk_fn(png_ptr, (png_voidp)this->getPeeker(), sk_read_user_chunk);
248 }
249
250 /* The call to png_read_info() gives us all of the information from the
251 * PNG file before the first IDAT (image data chunk). */
252 png_read_info(png_ptr, info_ptr);
253 png_uint_32 origWidth, origHeight;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000254 int bitDepth, colorType;
255 png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
256 &colorType, int_p_NULL, int_p_NULL, int_p_NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000257
258 /* tell libpng to strip 16 bit/color files down to 8 bits/color */
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000259 if (bitDepth == 16) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000260 png_set_strip_16(png_ptr);
261 }
262 /* Extract multiple pixels with bit depths of 1, 2, and 4 from a single
263 * byte into separate bytes (useful for paletted and grayscale images). */
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000264 if (bitDepth < 8) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000265 png_set_packing(png_ptr);
266 }
267 /* Expand grayscale images to the full 8 bits from 1, 2, or 4 bits/pixel */
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000268 if (colorType == PNG_COLOR_TYPE_GRAY && bitDepth < 8) {
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000269 png_set_expand_gray_1_2_4_to_8(png_ptr);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000270 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000271
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000272 return true;
273}
274
275bool SkPNGImageDecoder::onDecode(SkStream* sk_stream, SkBitmap* decodedBitmap,
276 Mode mode) {
277 png_structp png_ptr;
278 png_infop info_ptr;
279
280 if (!onDecodeInit(sk_stream, &png_ptr, &info_ptr)) {
281 return false;
282 }
283
284 if (setjmp(png_jmpbuf(png_ptr))) {
285 return false;
286 }
287
288 PNGAutoClean autoClean(png_ptr, info_ptr);
289
290 png_uint_32 origWidth, origHeight;
291 int bitDepth, colorType, interlaceType;
292 png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
293 &colorType, &interlaceType, int_p_NULL, int_p_NULL);
rmistry@google.comd6176b02012-08-23 18:14:13 +0000294
reed@android.com8a1c16f2008-12-17 15:59:43 +0000295 SkBitmap::Config config;
296 bool hasAlpha = false;
297 bool doDither = this->getDitherImage();
298 SkPMColor theTranspColor = 0; // 0 tells us not to try to match
rmistry@google.comd6176b02012-08-23 18:14:13 +0000299
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000300 if (!getBitmapConfig(png_ptr, info_ptr, &config, &hasAlpha, &doDither, &theTranspColor)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000301 return false;
302 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000303
reed@android.com8a1c16f2008-12-17 15:59:43 +0000304 const int sampleSize = this->getSampleSize();
305 SkScaledBitmapSampler sampler(origWidth, origHeight, sampleSize);
scroggo@google.combc69ce92013-07-09 15:45:14 +0000306 decodedBitmap->setConfig(config, sampler.scaledWidth(), sampler.scaledHeight());
reed@android.com8a1c16f2008-12-17 15:59:43 +0000307
reed@android.com8a1c16f2008-12-17 15:59:43 +0000308 if (SkImageDecoder::kDecodeBounds_Mode == mode) {
309 return true;
310 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000311
reed@android.com8a1c16f2008-12-17 15:59:43 +0000312 // from here down we are concerned with colortables and pixels
313
314 // we track if we actually see a non-opaque pixels, since sometimes a PNG sets its colortype
315 // to |= PNG_COLOR_MASK_ALPHA, but all of its pixels are in fact opaque. We care, since we
316 // draw lots faster if we can flag the bitmap has being opaque
317 bool reallyHasAlpha = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000318 SkColorTable* colorTable = NULL;
319
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000320 if (colorType == PNG_COLOR_TYPE_PALETTE) {
321 decodePalette(png_ptr, info_ptr, &hasAlpha, &reallyHasAlpha, &colorTable);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000322 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000323
reed@android.com8a1c16f2008-12-17 15:59:43 +0000324 SkAutoUnref aur(colorTable);
325
scroggo@google.combc69ce92013-07-09 15:45:14 +0000326 if (!this->allocPixelRef(decodedBitmap,
327 SkBitmap::kIndex8_Config == config ? colorTable : NULL)) {
328 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000329 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000330
reed@android.com8a1c16f2008-12-17 15:59:43 +0000331 SkAutoLockPixels alp(*decodedBitmap);
332
reed@android.com8a1c16f2008-12-17 15:59:43 +0000333 /* Turn on interlace handling. REQUIRED if you are not using
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000334 * png_read_image(). To see how to handle interlacing passes,
335 * see the png_read_row() method below:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000336 */
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000337 const int number_passes = (interlaceType != PNG_INTERLACE_NONE) ?
338 png_set_interlace_handling(png_ptr) : 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000339
340 /* Optional call to gamma correct and add the background to the palette
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000341 * and update info structure. REQUIRED if you are expecting libpng to
342 * update the palette for you (ie you selected such a transform above).
reed@android.com8a1c16f2008-12-17 15:59:43 +0000343 */
344 png_read_update_info(png_ptr, info_ptr);
345
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000346 if ((SkBitmap::kA8_Config == config || SkBitmap::kIndex8_Config == config)
347 && 1 == sampleSize) {
348 // A8 is only allowed if the original was GRAY.
349 SkASSERT(config != SkBitmap::kA8_Config
350 || PNG_COLOR_TYPE_GRAY == colorType);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000351 for (int i = 0; i < number_passes; i++) {
352 for (png_uint_32 y = 0; y < origHeight; y++) {
353 uint8_t* bmRow = decodedBitmap->getAddr8(0, y);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000354 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000355 }
356 }
357 } else {
358 SkScaledBitmapSampler::SrcConfig sc;
359 int srcBytesPerPixel = 4;
rmistry@google.comd6176b02012-08-23 18:14:13 +0000360
reed@android.com11344262009-07-08 20:09:23 +0000361 if (colorTable != NULL) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000362 sc = SkScaledBitmapSampler::kIndex;
363 srcBytesPerPixel = 1;
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000364 } else if (SkBitmap::kA8_Config == config) {
365 // A8 is only allowed if the original was GRAY.
366 SkASSERT(PNG_COLOR_TYPE_GRAY == colorType);
367 sc = SkScaledBitmapSampler::kGray;
368 srcBytesPerPixel = 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000369 } else if (hasAlpha) {
370 sc = SkScaledBitmapSampler::kRGBA;
371 } else {
372 sc = SkScaledBitmapSampler::kRGBX;
373 }
reed@android.com11344262009-07-08 20:09:23 +0000374
375 /* We have to pass the colortable explicitly, since we may have one
376 even if our decodedBitmap doesn't, due to the request that we
377 upscale png's palette to a direct model
378 */
379 SkAutoLockColors ctLock(colorTable);
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000380 if (!sampler.begin(decodedBitmap, sc, doDither, ctLock.colors(),
381 this->getRequireUnpremultipliedColors())) {
reed@android.com862e91b2009-04-28 15:27:07 +0000382 return false;
383 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000384 const int height = decodedBitmap->height();
385
reed@android.com862e91b2009-04-28 15:27:07 +0000386 if (number_passes > 1) {
387 SkAutoMalloc storage(origWidth * origHeight * srcBytesPerPixel);
388 uint8_t* base = (uint8_t*)storage.get();
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000389 size_t rowBytes = origWidth * srcBytesPerPixel;
reed@android.coma8a8b8b2009-05-04 15:00:11 +0000390
reed@android.com862e91b2009-04-28 15:27:07 +0000391 for (int i = 0; i < number_passes; i++) {
392 uint8_t* row = base;
393 for (png_uint_32 y = 0; y < origHeight; y++) {
394 uint8_t* bmRow = row;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000395 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
396 row += rowBytes;
reed@android.com862e91b2009-04-28 15:27:07 +0000397 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000398 }
reed@android.com862e91b2009-04-28 15:27:07 +0000399 // now sample it
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000400 base += sampler.srcY0() * rowBytes;
reed@android.com862e91b2009-04-28 15:27:07 +0000401 for (int y = 0; y < height; y++) {
402 reallyHasAlpha |= sampler.next(base);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000403 base += sampler.srcDY() * rowBytes;
reed@android.com862e91b2009-04-28 15:27:07 +0000404 }
405 } else {
406 SkAutoMalloc storage(origWidth * srcBytesPerPixel);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000407 uint8_t* srcRow = (uint8_t*)storage.get();
408 skip_src_rows(png_ptr, srcRow, sampler.srcY0());
409
410 for (int y = 0; y < height; y++) {
411 uint8_t* tmp = srcRow;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000412 png_read_rows(png_ptr, &tmp, png_bytepp_NULL, 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000413 reallyHasAlpha |= sampler.next(srcRow);
414 if (y < height - 1) {
415 skip_src_rows(png_ptr, srcRow, sampler.srcDY() - 1);
416 }
417 }
reed@android.com862e91b2009-04-28 15:27:07 +0000418
reed@android.com8a1c16f2008-12-17 15:59:43 +0000419 // skip the rest of the rows (if any)
420 png_uint_32 read = (height - 1) * sampler.srcDY() +
421 sampler.srcY0() + 1;
422 SkASSERT(read <= origHeight);
423 skip_src_rows(png_ptr, srcRow, origHeight - read);
424 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000425 }
426
427 /* read rest of file, and get additional chunks in info_ptr - REQUIRED */
428 png_read_end(png_ptr, info_ptr);
429
430 if (0 != theTranspColor) {
431 reallyHasAlpha |= substituteTranspColor(decodedBitmap, theTranspColor);
432 }
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000433 if (reallyHasAlpha && this->getRequireUnpremultipliedColors() &&
434 SkBitmap::kARGB_8888_Config != decodedBitmap->config()) {
435 // If the caller wants an unpremultiplied bitmap, and we let them get
436 // away with a config other than 8888, and it has alpha after all,
437 // return false, since the result will have premultiplied colors.
438 return false;
439 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000440 decodedBitmap->setIsOpaque(!reallyHasAlpha);
441 return true;
442}
443
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000444
445
446bool SkPNGImageDecoder::getBitmapConfig(png_structp png_ptr, png_infop info_ptr,
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000447 SkBitmap::Config* SK_RESTRICT configp,
448 bool* SK_RESTRICT hasAlphap,
449 bool* SK_RESTRICT doDitherp,
450 SkPMColor* SK_RESTRICT theTranspColorp) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000451 png_uint_32 origWidth, origHeight;
452 int bitDepth, colorType;
453 png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
454 &colorType, int_p_NULL, int_p_NULL, int_p_NULL);
455
456 // check for sBIT chunk data, in case we should disable dithering because
457 // our data is not truely 8bits per component
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000458 png_color_8p sig_bit;
459 if (*doDitherp && png_get_sBIT(png_ptr, info_ptr, &sig_bit)) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000460#if 0
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000461 SkDebugf("----- sBIT %d %d %d %d\n", sig_bit->red, sig_bit->green,
462 sig_bit->blue, sig_bit->alpha);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000463#endif
464 // 0 seems to indicate no information available
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000465 if (pos_le(sig_bit->red, SK_R16_BITS) &&
466 pos_le(sig_bit->green, SK_G16_BITS) &&
467 pos_le(sig_bit->blue, SK_B16_BITS)) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000468 *doDitherp = false;
469 }
470 }
471
472 if (colorType == PNG_COLOR_TYPE_PALETTE) {
473 bool paletteHasAlpha = hasTransparencyInPalette(png_ptr, info_ptr);
474 *configp = this->getPrefConfig(kIndex_SrcDepth, paletteHasAlpha);
475 // now see if we can upscale to their requested config
476 if (!canUpscalePaletteToConfig(*configp, paletteHasAlpha)) {
477 *configp = SkBitmap::kIndex8_Config;
478 }
479 } else {
480 png_color_16p transpColor = NULL;
481 int numTransp = 0;
482
483 png_get_tRNS(png_ptr, info_ptr, NULL, &numTransp, &transpColor);
484
485 bool valid = png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS);
486
487 if (valid && numTransp == 1 && transpColor != NULL) {
488 /* Compute our transparent color, which we'll match against later.
489 We don't really handle 16bit components properly here, since we
490 do our compare *after* the values have been knocked down to 8bit
491 which means we will find more matches than we should. The real
492 fix seems to be to see the actual 16bit components, do the
493 compare, and then knock it down to 8bits ourselves.
494 */
495 if (colorType & PNG_COLOR_MASK_COLOR) {
496 if (16 == bitDepth) {
497 *theTranspColorp = SkPackARGB32(0xFF, transpColor->red >> 8,
498 transpColor->green >> 8,
499 transpColor->blue >> 8);
500 } else {
501 *theTranspColorp = SkPackARGB32(0xFF, transpColor->red,
502 transpColor->green,
503 transpColor->blue);
504 }
505 } else { // gray
506 if (16 == bitDepth) {
507 *theTranspColorp = SkPackARGB32(0xFF, transpColor->gray >> 8,
508 transpColor->gray >> 8,
509 transpColor->gray >> 8);
510 } else {
511 *theTranspColorp = SkPackARGB32(0xFF, transpColor->gray,
512 transpColor->gray,
513 transpColor->gray);
514 }
515 }
516 }
517
518 if (valid ||
519 PNG_COLOR_TYPE_RGB_ALPHA == colorType ||
520 PNG_COLOR_TYPE_GRAY_ALPHA == colorType) {
521 *hasAlphap = true;
522 }
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000523
524 SrcDepth srcDepth = k32Bit_SrcDepth;
525 if (PNG_COLOR_TYPE_GRAY == colorType) {
526 srcDepth = k8BitGray_SrcDepth;
scroggo@google.com8e2ef012013-07-18 20:14:45 +0000527 // Remove this assert, which fails on desk_pokemonwiki.skp
528 //SkASSERT(!*hasAlphap);
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000529 }
530
531 *configp = this->getPrefConfig(srcDepth, *hasAlphap);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000532 // now match the request against our capabilities
533 if (*hasAlphap) {
534 if (*configp != SkBitmap::kARGB_4444_Config) {
535 *configp = SkBitmap::kARGB_8888_Config;
536 }
537 } else {
538 if (*configp != SkBitmap::kRGB_565_Config &&
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000539 *configp != SkBitmap::kARGB_4444_Config &&
540 *configp != SkBitmap::kA8_Config) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000541 *configp = SkBitmap::kARGB_8888_Config;
542 }
543 }
544 }
545
546 // sanity check for size
547 {
548 Sk64 size;
549 size.setMul(origWidth, origHeight);
550 if (size.isNeg() || !size.is32()) {
551 return false;
552 }
553 // now check that if we are 4-bytes per pixel, we also don't overflow
554 if (size.get32() > (0x7FFFFFFF >> 2)) {
555 return false;
556 }
557 }
558
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000559 if (!this->chooseFromOneChoice(*configp, origWidth, origHeight)) {
560 return false;
561 }
562
563 // If the image has alpha and the decoder wants unpremultiplied
564 // colors, the only supported config is 8888.
565 if (this->getRequireUnpremultipliedColors() && *hasAlphap) {
566 *configp = SkBitmap::kARGB_8888_Config;
567 }
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000568
569 if (fImageIndex != NULL) {
570 if (SkBitmap::kNo_Config == fImageIndex->fConfig) {
571 // This is the first time for this subset decode. From now on,
572 // all decodes must be in the same config.
573 fImageIndex->fConfig = *configp;
574 } else if (fImageIndex->fConfig != *configp) {
575 // Requesting a different config for a subsequent decode is not
576 // supported. Report failure before we make changes to png_ptr.
577 return false;
578 }
579 }
580
581 bool convertGrayToRGB = PNG_COLOR_TYPE_GRAY == colorType
582 && *configp != SkBitmap::kA8_Config;
583
584 // Unless the user is requesting A8, convert a grayscale image into RGB.
585 // GRAY_ALPHA will always be converted to RGB
586 if (convertGrayToRGB || colorType == PNG_COLOR_TYPE_GRAY_ALPHA) {
587 png_set_gray_to_rgb(png_ptr);
588 }
589
590 // Add filler (or alpha) byte (after each RGB triplet) if necessary.
591 if (colorType == PNG_COLOR_TYPE_RGB || convertGrayToRGB) {
592 png_set_filler(png_ptr, 0xff, PNG_FILLER_AFTER);
593 }
594
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000595 return true;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000596}
597
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000598typedef uint32_t (*PackColorProc)(U8CPU a, U8CPU r, U8CPU g, U8CPU b);
599
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000600bool SkPNGImageDecoder::decodePalette(png_structp png_ptr, png_infop info_ptr,
601 bool *hasAlphap, bool *reallyHasAlphap,
602 SkColorTable **colorTablep) {
603 int numPalette;
604 png_colorp palette;
605 png_bytep trans;
606 int numTrans;
607 bool reallyHasAlpha = false;
608 SkColorTable* colorTable = NULL;
609
610 png_get_PLTE(png_ptr, info_ptr, &palette, &numPalette);
611
612 /* BUGGY IMAGE WORKAROUND
613
614 We hit some images (e.g. fruit_.png) who contain bytes that are == colortable_count
615 which is a problem since we use the byte as an index. To work around this we grow
616 the colortable by 1 (if its < 256) and duplicate the last color into that slot.
617 */
618 int colorCount = numPalette + (numPalette < 256);
619
620 colorTable = SkNEW_ARGS(SkColorTable, (colorCount));
621
622 SkPMColor* colorPtr = colorTable->lockColors();
623 if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
624 png_get_tRNS(png_ptr, info_ptr, &trans, &numTrans, NULL);
625 *hasAlphap = (numTrans > 0);
626 } else {
627 numTrans = 0;
628 colorTable->setFlags(colorTable->getFlags() | SkColorTable::kColorsAreOpaque_Flag);
629 }
630 // check for bad images that might make us crash
631 if (numTrans > numPalette) {
632 numTrans = numPalette;
633 }
634
635 int index = 0;
636 int transLessThanFF = 0;
637
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000638 // Choose which function to use to create the color table. If the final destination's
639 // config is unpremultiplied, the color table will store unpremultiplied colors.
640 PackColorProc proc;
641 if (this->getRequireUnpremultipliedColors()) {
642 proc = &SkPackARGB32NoCheck;
643 } else {
644 proc = &SkPreMultiplyARGB;
645 }
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000646 for (; index < numTrans; index++) {
647 transLessThanFF |= (int)*trans - 0xFF;
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000648 *colorPtr++ = proc(*trans++, palette->red, palette->green, palette->blue);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000649 palette++;
650 }
651 reallyHasAlpha |= (transLessThanFF < 0);
652
653 for (; index < numPalette; index++) {
654 *colorPtr++ = SkPackARGB32(0xFF, palette->red, palette->green, palette->blue);
655 palette++;
656 }
657
658 // see BUGGY IMAGE WORKAROUND comment above
659 if (numPalette < 256) {
660 *colorPtr = colorPtr[-1];
661 }
662 colorTable->unlockColors(true);
663 *colorTablep = colorTable;
664 *reallyHasAlphap = reallyHasAlpha;
665 return true;
666}
667
668#ifdef SK_BUILD_FOR_ANDROID
669
670bool SkPNGImageDecoder::onBuildTileIndex(SkStream* sk_stream, int *width, int *height) {
671 png_structp png_ptr;
672 png_infop info_ptr;
673
674 if (!onDecodeInit(sk_stream, &png_ptr, &info_ptr)) {
675 return false;
676 }
677
678 if (setjmp(png_jmpbuf(png_ptr)) != 0) {
679 png_destroy_read_struct(&png_ptr, &info_ptr, png_infopp_NULL);
680 return false;
681 }
682
683 png_uint_32 origWidth, origHeight;
684 int bitDepth, colorType;
685 png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
686 &colorType, int_p_NULL, int_p_NULL, int_p_NULL);
687
688 *width = origWidth;
689 *height = origHeight;
690
691 png_build_index(png_ptr);
692
693 if (fImageIndex) {
694 SkDELETE(fImageIndex);
695 }
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000696 fImageIndex = SkNEW_ARGS(SkPNGImageIndex, (sk_stream, png_ptr, info_ptr));
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000697
698 return true;
699}
700
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000701bool SkPNGImageDecoder::onDecodeSubset(SkBitmap* bm, const SkIRect& region) {
702 if (NULL == fImageIndex) {
703 return false;
704 }
705
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000706 png_structp png_ptr = fImageIndex->fPng_ptr;
707 png_infop info_ptr = fImageIndex->fInfo_ptr;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000708 if (setjmp(png_jmpbuf(png_ptr))) {
709 return false;
710 }
711
712 png_uint_32 origWidth, origHeight;
713 int bitDepth, colorType, interlaceType;
714 png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
715 &colorType, &interlaceType, int_p_NULL, int_p_NULL);
716
717 SkIRect rect = SkIRect::MakeWH(origWidth, origHeight);
718
719 if (!rect.intersect(region)) {
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000720 // If the requested region is entirely outside the image, just
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000721 // returns false
722 return false;
723 }
724
725 SkBitmap::Config config;
726 bool hasAlpha = false;
727 bool doDither = this->getDitherImage();
728 SkPMColor theTranspColor = 0; // 0 tells us not to try to match
729
730 if (!getBitmapConfig(png_ptr, info_ptr, &config, &hasAlpha, &doDither, &theTranspColor)) {
731 return false;
732 }
733
734 const int sampleSize = this->getSampleSize();
735 SkScaledBitmapSampler sampler(origWidth, rect.height(), sampleSize);
736
737 SkBitmap decodedBitmap;
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000738 decodedBitmap.setConfig(config, sampler.scaledWidth(), sampler.scaledHeight());
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000739
740 // from here down we are concerned with colortables and pixels
741
742 // we track if we actually see a non-opaque pixels, since sometimes a PNG sets its colortype
743 // to |= PNG_COLOR_MASK_ALPHA, but all of its pixels are in fact opaque. We care, since we
744 // draw lots faster if we can flag the bitmap has being opaque
745 bool reallyHasAlpha = false;
746 SkColorTable* colorTable = NULL;
747
748 if (colorType == PNG_COLOR_TYPE_PALETTE) {
749 decodePalette(png_ptr, info_ptr, &hasAlpha, &reallyHasAlpha, &colorTable);
750 }
751
752 SkAutoUnref aur(colorTable);
753
754 // Check ahead of time if the swap(dest, src) is possible.
755 // If yes, then we will stick to AllocPixelRef since it's cheaper with the swap happening.
756 // If no, then we will use alloc to allocate pixels to prevent garbage collection.
757 int w = rect.width() / sampleSize;
758 int h = rect.height() / sampleSize;
759 const bool swapOnly = (rect == region) && (w == decodedBitmap.width()) &&
760 (h == decodedBitmap.height()) && bm->isNull();
761 const bool needColorTable = SkBitmap::kIndex8_Config == config;
762 if (swapOnly) {
763 if (!this->allocPixelRef(&decodedBitmap, needColorTable ? colorTable : NULL)) {
764 return false;
765 }
766 } else {
767 if (!decodedBitmap.allocPixels(NULL, needColorTable ? colorTable : NULL)) {
768 return false;
769 }
770 }
771 SkAutoLockPixels alp(decodedBitmap);
772
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000773 /* Turn on interlace handling. REQUIRED if you are not using
774 * png_read_image(). To see how to handle interlacing passes,
775 * see the png_read_row() method below:
776 */
777 const int number_passes = (interlaceType != PNG_INTERLACE_NONE) ?
778 png_set_interlace_handling(png_ptr) : 1;
779
780 /* Optional call to gamma correct and add the background to the palette
781 * and update info structure. REQUIRED if you are expecting libpng to
782 * update the palette for you (ie you selected such a transform above).
783 */
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000784
785 // Direct access to png_ptr fields is deprecated in libpng > 1.2.
786#if defined(PNG_1_0_X) || defined (PNG_1_2_X)
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000787 png_ptr->pass = 0;
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000788#else
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000789 // FIXME: This sets pass as desired, but also sets iwidth. Is that ok?
790 png_set_interlaced_pass(png_ptr, 0);
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000791#endif
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000792 png_read_update_info(png_ptr, info_ptr);
793
794 int actualTop = rect.fTop;
795
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000796 if ((SkBitmap::kA8_Config == config || SkBitmap::kIndex8_Config == config)
797 && 1 == sampleSize) {
798 // A8 is only allowed if the original was GRAY.
799 SkASSERT(config != SkBitmap::kA8_Config
800 || PNG_COLOR_TYPE_GRAY == colorType);
801
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000802 for (int i = 0; i < number_passes; i++) {
803 png_configure_decoder(png_ptr, &actualTop, i);
804 for (int j = 0; j < rect.fTop - actualTop; j++) {
805 uint8_t* bmRow = decodedBitmap.getAddr8(0, 0);
806 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
807 }
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000808 png_uint_32 bitmapHeight = (png_uint_32) decodedBitmap.height();
809 for (png_uint_32 y = 0; y < bitmapHeight; y++) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000810 uint8_t* bmRow = decodedBitmap.getAddr8(0, y);
811 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
812 }
813 }
814 } else {
815 SkScaledBitmapSampler::SrcConfig sc;
816 int srcBytesPerPixel = 4;
817
818 if (colorTable != NULL) {
819 sc = SkScaledBitmapSampler::kIndex;
820 srcBytesPerPixel = 1;
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000821 } else if (SkBitmap::kA8_Config == config) {
822 // A8 is only allowed if the original was GRAY.
823 SkASSERT(PNG_COLOR_TYPE_GRAY == colorType);
824 sc = SkScaledBitmapSampler::kGray;
825 srcBytesPerPixel = 1;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000826 } else if (hasAlpha) {
827 sc = SkScaledBitmapSampler::kRGBA;
828 } else {
829 sc = SkScaledBitmapSampler::kRGBX;
830 }
831
832 /* We have to pass the colortable explicitly, since we may have one
833 even if our decodedBitmap doesn't, due to the request that we
834 upscale png's palette to a direct model
835 */
836 SkAutoLockColors ctLock(colorTable);
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000837 if (!sampler.begin(&decodedBitmap, sc, doDither, ctLock.colors(),
838 this->getRequireUnpremultipliedColors())) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000839 return false;
840 }
841 const int height = decodedBitmap.height();
842
843 if (number_passes > 1) {
844 SkAutoMalloc storage(origWidth * origHeight * srcBytesPerPixel);
845 uint8_t* base = (uint8_t*)storage.get();
846 size_t rb = origWidth * srcBytesPerPixel;
847
848 for (int i = 0; i < number_passes; i++) {
849 png_configure_decoder(png_ptr, &actualTop, i);
850 for (int j = 0; j < rect.fTop - actualTop; j++) {
851 uint8_t* bmRow = (uint8_t*)decodedBitmap.getPixels();
852 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
853 }
854 uint8_t* row = base;
855 for (int32_t y = 0; y < rect.height(); y++) {
856 uint8_t* bmRow = row;
857 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
858 row += rb;
859 }
860 }
861 // now sample it
862 base += sampler.srcY0() * rb;
863 for (int y = 0; y < height; y++) {
864 reallyHasAlpha |= sampler.next(base);
865 base += sampler.srcDY() * rb;
866 }
867 } else {
868 SkAutoMalloc storage(origWidth * srcBytesPerPixel);
869 uint8_t* srcRow = (uint8_t*)storage.get();
870
871 png_configure_decoder(png_ptr, &actualTop, 0);
872 skip_src_rows(png_ptr, srcRow, sampler.srcY0());
873
874 for (int i = 0; i < rect.fTop - actualTop; i++) {
875 uint8_t* bmRow = (uint8_t*)decodedBitmap.getPixels();
876 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
877 }
878 for (int y = 0; y < height; y++) {
879 uint8_t* tmp = srcRow;
880 png_read_rows(png_ptr, &tmp, png_bytepp_NULL, 1);
881 reallyHasAlpha |= sampler.next(srcRow);
882 if (y < height - 1) {
883 skip_src_rows(png_ptr, srcRow, sampler.srcDY() - 1);
884 }
885 }
886 }
887 }
888
889 if (0 != theTranspColor) {
890 reallyHasAlpha |= substituteTranspColor(&decodedBitmap, theTranspColor);
891 }
892 decodedBitmap.setIsOpaque(!reallyHasAlpha);
893
894 if (swapOnly) {
895 bm->swap(decodedBitmap);
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000896 return true;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000897 }
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000898 return this->cropBitmap(bm, &decodedBitmap, sampleSize, region.x(), region.y(),
899 region.width(), region.height(), 0, rect.y());
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000900}
901#endif
902
reed@android.com8a1c16f2008-12-17 15:59:43 +0000903///////////////////////////////////////////////////////////////////////////////
904
reed@android.com8a1c16f2008-12-17 15:59:43 +0000905#include "SkColorPriv.h"
906#include "SkUnPreMultiply.h"
907
908static void sk_write_fn(png_structp png_ptr, png_bytep data, png_size_t len) {
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000909 SkWStream* sk_stream = (SkWStream*)png_get_io_ptr(png_ptr);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000910 if (!sk_stream->write(data, len)) {
911 png_error(png_ptr, "sk_write_fn Error!");
912 }
913}
914
reed@android.com8a1c16f2008-12-17 15:59:43 +0000915static transform_scanline_proc choose_proc(SkBitmap::Config config,
916 bool hasAlpha) {
917 // we don't care about search on alpha if we're kIndex8, since only the
918 // colortable packing cares about that distinction, not the pixels
919 if (SkBitmap::kIndex8_Config == config) {
920 hasAlpha = false; // we store false in the table entries for kIndex8
921 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000922
reed@android.com8a1c16f2008-12-17 15:59:43 +0000923 static const struct {
924 SkBitmap::Config fConfig;
925 bool fHasAlpha;
926 transform_scanline_proc fProc;
927 } gMap[] = {
928 { SkBitmap::kRGB_565_Config, false, transform_scanline_565 },
929 { SkBitmap::kARGB_8888_Config, false, transform_scanline_888 },
930 { SkBitmap::kARGB_8888_Config, true, transform_scanline_8888 },
931 { SkBitmap::kARGB_4444_Config, false, transform_scanline_444 },
932 { SkBitmap::kARGB_4444_Config, true, transform_scanline_4444 },
epoger@google.com4ce738b2012-11-16 18:44:18 +0000933 { SkBitmap::kIndex8_Config, false, transform_scanline_memcpy },
reed@android.com8a1c16f2008-12-17 15:59:43 +0000934 };
935
936 for (int i = SK_ARRAY_COUNT(gMap) - 1; i >= 0; --i) {
937 if (gMap[i].fConfig == config && gMap[i].fHasAlpha == hasAlpha) {
938 return gMap[i].fProc;
939 }
940 }
941 sk_throw();
942 return NULL;
943}
944
945// return the minimum legal bitdepth (by png standards) for this many colortable
946// entries. SkBitmap always stores in 8bits per pixel, but for colorcount <= 16,
947// we can use fewer bits per in png
948static int computeBitDepth(int colorCount) {
949#if 0
950 int bits = SkNextLog2(colorCount);
951 SkASSERT(bits >= 1 && bits <= 8);
952 // now we need bits itself to be a power of 2 (e.g. 1, 2, 4, 8)
953 return SkNextPow2(bits);
954#else
955 // for the moment, we don't know how to pack bitdepth < 8
956 return 8;
957#endif
958}
959
960/* Pack palette[] with the corresponding colors, and if hasAlpha is true, also
961 pack trans[] and return the number of trans[] entries written. If hasAlpha
962 is false, the return value will always be 0.
rmistry@google.comd6176b02012-08-23 18:14:13 +0000963
reed@android.com8a1c16f2008-12-17 15:59:43 +0000964 Note: this routine takes care of unpremultiplying the RGB values when we
965 have alpha in the colortable, since png doesn't support premul colors
966*/
reed@android.com6f252972009-01-14 16:46:16 +0000967static inline int pack_palette(SkColorTable* ctable,
reed@android.comb50a60c2009-01-14 17:51:08 +0000968 png_color* SK_RESTRICT palette,
969 png_byte* SK_RESTRICT trans, bool hasAlpha) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000970 SkAutoLockColors alc(ctable);
971 const SkPMColor* SK_RESTRICT colors = alc.colors();
972 const int ctCount = ctable->count();
973 int i, num_trans = 0;
974
975 if (hasAlpha) {
976 /* first see if we have some number of fully opaque at the end of the
977 ctable. PNG allows num_trans < num_palette, but all of the trans
978 entries must come first in the palette. If I was smarter, I'd
979 reorder the indices and ctable so that all non-opaque colors came
980 first in the palette. But, since that would slow down the encode,
981 I'm leaving the indices and ctable order as is, and just looking
982 at the tail of the ctable for opaqueness.
983 */
984 num_trans = ctCount;
985 for (i = ctCount - 1; i >= 0; --i) {
986 if (SkGetPackedA32(colors[i]) != 0xFF) {
987 break;
988 }
989 num_trans -= 1;
990 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000991
reed@android.com8a1c16f2008-12-17 15:59:43 +0000992 const SkUnPreMultiply::Scale* SK_RESTRICT table =
993 SkUnPreMultiply::GetScaleTable();
994
995 for (i = 0; i < num_trans; i++) {
996 const SkPMColor c = *colors++;
997 const unsigned a = SkGetPackedA32(c);
998 const SkUnPreMultiply::Scale s = table[a];
999 trans[i] = a;
1000 palette[i].red = SkUnPreMultiply::ApplyScale(s, SkGetPackedR32(c));
1001 palette[i].green = SkUnPreMultiply::ApplyScale(s,SkGetPackedG32(c));
1002 palette[i].blue = SkUnPreMultiply::ApplyScale(s, SkGetPackedB32(c));
rmistry@google.comd6176b02012-08-23 18:14:13 +00001003 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001004 // now fall out of this if-block to use common code for the trailing
1005 // opaque entries
1006 }
rmistry@google.comd6176b02012-08-23 18:14:13 +00001007
reed@android.com8a1c16f2008-12-17 15:59:43 +00001008 // these (remaining) entries are opaque
1009 for (i = num_trans; i < ctCount; i++) {
1010 SkPMColor c = *colors++;
1011 palette[i].red = SkGetPackedR32(c);
1012 palette[i].green = SkGetPackedG32(c);
1013 palette[i].blue = SkGetPackedB32(c);
1014 }
1015 return num_trans;
1016}
1017
1018class SkPNGImageEncoder : public SkImageEncoder {
1019protected:
commit-bot@chromium.orga936e372013-03-14 14:42:18 +00001020 virtual bool onEncode(SkWStream* stream, const SkBitmap& bm, int quality) SK_OVERRIDE;
tomhudson@google.com5c210c72011-07-28 21:06:40 +00001021private:
1022 bool doEncode(SkWStream* stream, const SkBitmap& bm,
1023 const bool& hasAlpha, int colorType,
1024 int bitDepth, SkBitmap::Config config,
1025 png_color_8& sig_bit);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +00001026
1027 typedef SkImageEncoder INHERITED;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001028};
1029
1030bool SkPNGImageEncoder::onEncode(SkWStream* stream, const SkBitmap& bitmap,
1031 int /*quality*/) {
1032 SkBitmap::Config config = bitmap.getConfig();
1033
1034 const bool hasAlpha = !bitmap.isOpaque();
1035 int colorType = PNG_COLOR_MASK_COLOR;
1036 int bitDepth = 8; // default for color
1037 png_color_8 sig_bit;
1038
1039 switch (config) {
1040 case SkBitmap::kIndex8_Config:
1041 colorType |= PNG_COLOR_MASK_PALETTE;
1042 // fall through to the ARGB_8888 case
1043 case SkBitmap::kARGB_8888_Config:
1044 sig_bit.red = 8;
1045 sig_bit.green = 8;
1046 sig_bit.blue = 8;
1047 sig_bit.alpha = 8;
1048 break;
1049 case SkBitmap::kARGB_4444_Config:
1050 sig_bit.red = 4;
1051 sig_bit.green = 4;
1052 sig_bit.blue = 4;
1053 sig_bit.alpha = 4;
1054 break;
1055 case SkBitmap::kRGB_565_Config:
1056 sig_bit.red = 5;
1057 sig_bit.green = 6;
1058 sig_bit.blue = 5;
1059 sig_bit.alpha = 0;
1060 break;
1061 default:
1062 return false;
1063 }
rmistry@google.comd6176b02012-08-23 18:14:13 +00001064
reed@android.com8a1c16f2008-12-17 15:59:43 +00001065 if (hasAlpha) {
1066 // don't specify alpha if we're a palette, even if our ctable has alpha
1067 if (!(colorType & PNG_COLOR_MASK_PALETTE)) {
1068 colorType |= PNG_COLOR_MASK_ALPHA;
1069 }
1070 } else {
1071 sig_bit.alpha = 0;
1072 }
rmistry@google.comd6176b02012-08-23 18:14:13 +00001073
reed@android.com8a1c16f2008-12-17 15:59:43 +00001074 SkAutoLockPixels alp(bitmap);
1075 // readyToDraw checks for pixels (and colortable if that is required)
1076 if (!bitmap.readyToDraw()) {
1077 return false;
1078 }
1079
1080 // we must do this after we have locked the pixels
1081 SkColorTable* ctable = bitmap.getColorTable();
1082 if (NULL != ctable) {
1083 if (ctable->count() == 0) {
1084 return false;
1085 }
1086 // check if we can store in fewer than 8 bits
1087 bitDepth = computeBitDepth(ctable->count());
1088 }
1089
tomhudson@google.com5c210c72011-07-28 21:06:40 +00001090 return doEncode(stream, bitmap, hasAlpha, colorType,
1091 bitDepth, config, sig_bit);
1092}
1093
1094bool SkPNGImageEncoder::doEncode(SkWStream* stream, const SkBitmap& bitmap,
1095 const bool& hasAlpha, int colorType,
1096 int bitDepth, SkBitmap::Config config,
1097 png_color_8& sig_bit) {
1098
reed@android.com8a1c16f2008-12-17 15:59:43 +00001099 png_structp png_ptr;
1100 png_infop info_ptr;
1101
1102 png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, sk_error_fn,
1103 NULL);
1104 if (NULL == png_ptr) {
1105 return false;
1106 }
1107
1108 info_ptr = png_create_info_struct(png_ptr);
1109 if (NULL == info_ptr) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +00001110 png_destroy_write_struct(&png_ptr, png_infopp_NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001111 return false;
1112 }
1113
1114 /* Set error handling. REQUIRED if you aren't supplying your own
1115 * error handling functions in the png_create_write_struct() call.
1116 */
1117 if (setjmp(png_jmpbuf(png_ptr))) {
1118 png_destroy_write_struct(&png_ptr, &info_ptr);
1119 return false;
1120 }
1121
commit-bot@chromium.orga936e372013-03-14 14:42:18 +00001122 png_set_write_fn(png_ptr, (void*)stream, sk_write_fn, png_flush_ptr_NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001123
1124 /* Set the image information here. Width and height are up to 2^31,
1125 * bit_depth is one of 1, 2, 4, 8, or 16, but valid values also depend on
1126 * the color_type selected. color_type is one of PNG_COLOR_TYPE_GRAY,
1127 * PNG_COLOR_TYPE_GRAY_ALPHA, PNG_COLOR_TYPE_PALETTE, PNG_COLOR_TYPE_RGB,
1128 * or PNG_COLOR_TYPE_RGB_ALPHA. interlace is either PNG_INTERLACE_NONE or
1129 * PNG_INTERLACE_ADAM7, and the compression_type and filter_type MUST
1130 * currently be PNG_COMPRESSION_TYPE_BASE and PNG_FILTER_TYPE_BASE. REQUIRED
1131 */
1132
1133 png_set_IHDR(png_ptr, info_ptr, bitmap.width(), bitmap.height(),
1134 bitDepth, colorType,
1135 PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE,
1136 PNG_FILTER_TYPE_BASE);
1137
reed@android.com61898772009-07-07 19:38:01 +00001138 // set our colortable/trans arrays if needed
1139 png_color paletteColors[256];
1140 png_byte trans[256];
1141 if (SkBitmap::kIndex8_Config == config) {
1142 SkColorTable* ct = bitmap.getColorTable();
1143 int numTrans = pack_palette(ct, paletteColors, trans, hasAlpha);
1144 png_set_PLTE(png_ptr, info_ptr, paletteColors, ct->count());
1145 if (numTrans > 0) {
1146 png_set_tRNS(png_ptr, info_ptr, trans, numTrans, NULL);
1147 }
1148 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001149
1150 png_set_sBIT(png_ptr, info_ptr, &sig_bit);
1151 png_write_info(png_ptr, info_ptr);
1152
1153 const char* srcImage = (const char*)bitmap.getPixels();
1154 SkAutoSMalloc<1024> rowStorage(bitmap.width() << 2);
1155 char* storage = (char*)rowStorage.get();
1156 transform_scanline_proc proc = choose_proc(config, hasAlpha);
1157
1158 for (int y = 0; y < bitmap.height(); y++) {
1159 png_bytep row_ptr = (png_bytep)storage;
1160 proc(srcImage, bitmap.width(), storage);
1161 png_write_rows(png_ptr, &row_ptr, 1);
1162 srcImage += bitmap.rowBytes();
1163 }
1164
1165 png_write_end(png_ptr, info_ptr);
1166
1167 /* clean up after the write, and free any memory allocated */
1168 png_destroy_write_struct(&png_ptr, &info_ptr);
1169 return true;
1170}
1171
reed@android.com00bf85a2009-01-22 13:04:56 +00001172///////////////////////////////////////////////////////////////////////////////
robertphillips@google.comec51cb82012-03-23 18:13:47 +00001173DEFINE_DECODER_CREATOR(PNGImageDecoder);
1174DEFINE_ENCODER_CREATOR(PNGImageEncoder);
1175///////////////////////////////////////////////////////////////////////////////
reed@android.com00bf85a2009-01-22 13:04:56 +00001176
scroggo@google.com39edf4c2013-04-25 17:33:51 +00001177static bool is_png(SkStream* stream) {
reed@android.com00bf85a2009-01-22 13:04:56 +00001178 char buf[PNG_BYTES_TO_CHECK];
1179 if (stream->read(buf, PNG_BYTES_TO_CHECK) == PNG_BYTES_TO_CHECK &&
1180 !png_sig_cmp((png_bytep) buf, (png_size_t)0, PNG_BYTES_TO_CHECK)) {
scroggo@google.com39edf4c2013-04-25 17:33:51 +00001181 return true;
1182 }
1183 return false;
1184}
1185
1186SkImageDecoder* sk_libpng_dfactory(SkStream* stream) {
1187 if (is_png(stream)) {
reed@android.com00bf85a2009-01-22 13:04:56 +00001188 return SkNEW(SkPNGImageDecoder);
1189 }
1190 return NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001191}
1192
scroggo@google.com39edf4c2013-04-25 17:33:51 +00001193static SkImageDecoder::Format get_format_png(SkStream* stream) {
1194 if (is_png(stream)) {
1195 return SkImageDecoder::kPNG_Format;
1196 }
1197 return SkImageDecoder::kUnknown_Format;
1198}
1199
reed@android.comdfee5792010-04-15 14:24:50 +00001200SkImageEncoder* sk_libpng_efactory(SkImageEncoder::Type t) {
reed@android.com00bf85a2009-01-22 13:04:56 +00001201 return (SkImageEncoder::kPNG_Type == t) ? SkNEW(SkPNGImageEncoder) : NULL;
1202}
1203
mtklein@google.combd6343b2013-09-04 17:20:18 +00001204static SkImageDecoder_DecodeReg gDReg(sk_libpng_dfactory);
1205static SkImageDecoder_FormatReg gFormatReg(get_format_png);
1206static SkImageEncoder_EncodeReg gEReg(sk_libpng_efactory);