blob: d3b45c3847488f013d3746fdd12ecac6e9500b25 [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.comb5571b32013-09-25 21:34:24 +000045 SkPNGImageIndex(SkStreamRewindable* stream, png_structp png_ptr, png_infop info_ptr)
scroggo@google.comc70a3aa2013-07-18 20:03:15 +000046 : 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.comb5571b32013-09-25 21:34:24 +000059 SkAutoTUnref<SkStreamRewindable> 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
scroggo@google.comb5571b32013-09-25 21:34:24 +000080 virtual bool onBuildTileIndex(SkStreamRewindable *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,
scroggo@google.com8d239242013-10-01 17:27:15 +000094 SkPMColor *theTranspColor);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +000095
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) {
scroggo@google.comb5571b32013-09-25 21:34:24 +0000126 SkStreamRewindable* sk_stream = (SkStreamRewindable*) 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;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000297 SkPMColor theTranspColor = 0; // 0 tells us not to try to match
rmistry@google.comd6176b02012-08-23 18:14:13 +0000298
scroggo@google.com8d239242013-10-01 17:27:15 +0000299 if (!this->getBitmapConfig(png_ptr, info_ptr, &config, &hasAlpha, &theTranspColor)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000300 return false;
301 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000302
reed@android.com8a1c16f2008-12-17 15:59:43 +0000303 const int sampleSize = this->getSampleSize();
304 SkScaledBitmapSampler sampler(origWidth, origHeight, sampleSize);
scroggo@google.combc69ce92013-07-09 15:45:14 +0000305 decodedBitmap->setConfig(config, sampler.scaledWidth(), sampler.scaledHeight());
reed@android.com8a1c16f2008-12-17 15:59:43 +0000306
reed@android.com8a1c16f2008-12-17 15:59:43 +0000307 if (SkImageDecoder::kDecodeBounds_Mode == mode) {
308 return true;
309 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000310
reed@android.com8a1c16f2008-12-17 15:59:43 +0000311 // from here down we are concerned with colortables and pixels
312
313 // we track if we actually see a non-opaque pixels, since sometimes a PNG sets its colortype
314 // to |= PNG_COLOR_MASK_ALPHA, but all of its pixels are in fact opaque. We care, since we
315 // draw lots faster if we can flag the bitmap has being opaque
316 bool reallyHasAlpha = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000317 SkColorTable* colorTable = NULL;
318
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000319 if (colorType == PNG_COLOR_TYPE_PALETTE) {
320 decodePalette(png_ptr, info_ptr, &hasAlpha, &reallyHasAlpha, &colorTable);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000321 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000322
reed@android.com8a1c16f2008-12-17 15:59:43 +0000323 SkAutoUnref aur(colorTable);
324
scroggo@google.combc69ce92013-07-09 15:45:14 +0000325 if (!this->allocPixelRef(decodedBitmap,
326 SkBitmap::kIndex8_Config == config ? colorTable : NULL)) {
327 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000328 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000329
reed@android.com8a1c16f2008-12-17 15:59:43 +0000330 SkAutoLockPixels alp(*decodedBitmap);
331
reed@android.com8a1c16f2008-12-17 15:59:43 +0000332 /* Turn on interlace handling. REQUIRED if you are not using
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000333 * png_read_image(). To see how to handle interlacing passes,
334 * see the png_read_row() method below:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000335 */
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000336 const int number_passes = (interlaceType != PNG_INTERLACE_NONE) ?
337 png_set_interlace_handling(png_ptr) : 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000338
339 /* Optional call to gamma correct and add the background to the palette
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000340 * and update info structure. REQUIRED if you are expecting libpng to
341 * update the palette for you (ie you selected such a transform above).
reed@android.com8a1c16f2008-12-17 15:59:43 +0000342 */
343 png_read_update_info(png_ptr, info_ptr);
344
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000345 if ((SkBitmap::kA8_Config == config || SkBitmap::kIndex8_Config == config)
346 && 1 == sampleSize) {
347 // A8 is only allowed if the original was GRAY.
348 SkASSERT(config != SkBitmap::kA8_Config
349 || PNG_COLOR_TYPE_GRAY == colorType);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000350 for (int i = 0; i < number_passes; i++) {
351 for (png_uint_32 y = 0; y < origHeight; y++) {
352 uint8_t* bmRow = decodedBitmap->getAddr8(0, y);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000353 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000354 }
355 }
356 } else {
357 SkScaledBitmapSampler::SrcConfig sc;
358 int srcBytesPerPixel = 4;
rmistry@google.comd6176b02012-08-23 18:14:13 +0000359
reed@android.com11344262009-07-08 20:09:23 +0000360 if (colorTable != NULL) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000361 sc = SkScaledBitmapSampler::kIndex;
362 srcBytesPerPixel = 1;
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000363 } else if (SkBitmap::kA8_Config == config) {
364 // A8 is only allowed if the original was GRAY.
365 SkASSERT(PNG_COLOR_TYPE_GRAY == colorType);
366 sc = SkScaledBitmapSampler::kGray;
367 srcBytesPerPixel = 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000368 } else if (hasAlpha) {
369 sc = SkScaledBitmapSampler::kRGBA;
370 } else {
371 sc = SkScaledBitmapSampler::kRGBX;
372 }
reed@android.com11344262009-07-08 20:09:23 +0000373
374 /* We have to pass the colortable explicitly, since we may have one
375 even if our decodedBitmap doesn't, due to the request that we
376 upscale png's palette to a direct model
377 */
378 SkAutoLockColors ctLock(colorTable);
scroggo@google.com8d239242013-10-01 17:27:15 +0000379 if (!sampler.begin(decodedBitmap, sc, *this, ctLock.colors())) {
reed@android.com862e91b2009-04-28 15:27:07 +0000380 return false;
381 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000382 const int height = decodedBitmap->height();
383
reed@android.com862e91b2009-04-28 15:27:07 +0000384 if (number_passes > 1) {
385 SkAutoMalloc storage(origWidth * origHeight * srcBytesPerPixel);
386 uint8_t* base = (uint8_t*)storage.get();
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000387 size_t rowBytes = origWidth * srcBytesPerPixel;
reed@android.coma8a8b8b2009-05-04 15:00:11 +0000388
reed@android.com862e91b2009-04-28 15:27:07 +0000389 for (int i = 0; i < number_passes; i++) {
390 uint8_t* row = base;
391 for (png_uint_32 y = 0; y < origHeight; y++) {
392 uint8_t* bmRow = row;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000393 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
394 row += rowBytes;
reed@android.com862e91b2009-04-28 15:27:07 +0000395 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000396 }
reed@android.com862e91b2009-04-28 15:27:07 +0000397 // now sample it
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000398 base += sampler.srcY0() * rowBytes;
reed@android.com862e91b2009-04-28 15:27:07 +0000399 for (int y = 0; y < height; y++) {
400 reallyHasAlpha |= sampler.next(base);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000401 base += sampler.srcDY() * rowBytes;
reed@android.com862e91b2009-04-28 15:27:07 +0000402 }
403 } else {
404 SkAutoMalloc storage(origWidth * srcBytesPerPixel);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000405 uint8_t* srcRow = (uint8_t*)storage.get();
406 skip_src_rows(png_ptr, srcRow, sampler.srcY0());
407
408 for (int y = 0; y < height; y++) {
409 uint8_t* tmp = srcRow;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000410 png_read_rows(png_ptr, &tmp, png_bytepp_NULL, 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000411 reallyHasAlpha |= sampler.next(srcRow);
412 if (y < height - 1) {
413 skip_src_rows(png_ptr, srcRow, sampler.srcDY() - 1);
414 }
415 }
reed@android.com862e91b2009-04-28 15:27:07 +0000416
reed@android.com8a1c16f2008-12-17 15:59:43 +0000417 // skip the rest of the rows (if any)
418 png_uint_32 read = (height - 1) * sampler.srcDY() +
419 sampler.srcY0() + 1;
420 SkASSERT(read <= origHeight);
421 skip_src_rows(png_ptr, srcRow, origHeight - read);
422 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000423 }
424
425 /* read rest of file, and get additional chunks in info_ptr - REQUIRED */
426 png_read_end(png_ptr, info_ptr);
427
428 if (0 != theTranspColor) {
429 reallyHasAlpha |= substituteTranspColor(decodedBitmap, theTranspColor);
430 }
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000431 if (reallyHasAlpha && this->getRequireUnpremultipliedColors() &&
432 SkBitmap::kARGB_8888_Config != decodedBitmap->config()) {
433 // If the caller wants an unpremultiplied bitmap, and we let them get
434 // away with a config other than 8888, and it has alpha after all,
435 // return false, since the result will have premultiplied colors.
436 return false;
437 }
commit-bot@chromium.org546f70c2013-10-03 17:13:38 +0000438 if (SkBitmap::kA8_Config == decodedBitmap->config()) {
439 reallyHasAlpha = true;
440 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000441 decodedBitmap->setIsOpaque(!reallyHasAlpha);
442 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,
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000448 SkBitmap::Config* SK_RESTRICT configp,
449 bool* SK_RESTRICT hasAlphap,
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000450 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;
scroggo@google.com8d239242013-10-01 17:27:15 +0000459 if (this->getDitherImage() && 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)) {
scroggo@google.com8d239242013-10-01 17:27:15 +0000468 this->setDitherImage(false);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000469 }
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 {
scroggo@google.com354fd972013-10-02 15:50:19 +0000538 if (SkBitmap::kA8_Config == *configp) {
539 if (k8BitGray_SrcDepth != srcDepth) {
540 // Converting a non grayscale image to A8 is not currently supported.
541 *configp = SkBitmap::kARGB_8888_Config;
542 }
543 } else if (*configp != SkBitmap::kRGB_565_Config &&
544 *configp != SkBitmap::kARGB_4444_Config) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000545 *configp = SkBitmap::kARGB_8888_Config;
546 }
547 }
548 }
549
550 // sanity check for size
551 {
552 Sk64 size;
553 size.setMul(origWidth, origHeight);
554 if (size.isNeg() || !size.is32()) {
555 return false;
556 }
557 // now check that if we are 4-bytes per pixel, we also don't overflow
558 if (size.get32() > (0x7FFFFFFF >> 2)) {
559 return false;
560 }
561 }
562
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000563 if (!this->chooseFromOneChoice(*configp, origWidth, origHeight)) {
564 return false;
565 }
566
567 // If the image has alpha and the decoder wants unpremultiplied
568 // colors, the only supported config is 8888.
569 if (this->getRequireUnpremultipliedColors() && *hasAlphap) {
570 *configp = SkBitmap::kARGB_8888_Config;
571 }
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000572
573 if (fImageIndex != NULL) {
574 if (SkBitmap::kNo_Config == fImageIndex->fConfig) {
575 // This is the first time for this subset decode. From now on,
576 // all decodes must be in the same config.
577 fImageIndex->fConfig = *configp;
578 } else if (fImageIndex->fConfig != *configp) {
579 // Requesting a different config for a subsequent decode is not
580 // supported. Report failure before we make changes to png_ptr.
581 return false;
582 }
583 }
584
585 bool convertGrayToRGB = PNG_COLOR_TYPE_GRAY == colorType
586 && *configp != SkBitmap::kA8_Config;
587
588 // Unless the user is requesting A8, convert a grayscale image into RGB.
589 // GRAY_ALPHA will always be converted to RGB
590 if (convertGrayToRGB || colorType == PNG_COLOR_TYPE_GRAY_ALPHA) {
591 png_set_gray_to_rgb(png_ptr);
592 }
593
594 // Add filler (or alpha) byte (after each RGB triplet) if necessary.
595 if (colorType == PNG_COLOR_TYPE_RGB || convertGrayToRGB) {
596 png_set_filler(png_ptr, 0xff, PNG_FILLER_AFTER);
597 }
598
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000599 return true;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000600}
601
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000602typedef uint32_t (*PackColorProc)(U8CPU a, U8CPU r, U8CPU g, U8CPU b);
603
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000604bool SkPNGImageDecoder::decodePalette(png_structp png_ptr, png_infop info_ptr,
605 bool *hasAlphap, bool *reallyHasAlphap,
606 SkColorTable **colorTablep) {
607 int numPalette;
608 png_colorp palette;
609 png_bytep trans;
610 int numTrans;
611 bool reallyHasAlpha = false;
612 SkColorTable* colorTable = NULL;
613
614 png_get_PLTE(png_ptr, info_ptr, &palette, &numPalette);
615
616 /* BUGGY IMAGE WORKAROUND
617
618 We hit some images (e.g. fruit_.png) who contain bytes that are == colortable_count
619 which is a problem since we use the byte as an index. To work around this we grow
620 the colortable by 1 (if its < 256) and duplicate the last color into that slot.
621 */
622 int colorCount = numPalette + (numPalette < 256);
623
624 colorTable = SkNEW_ARGS(SkColorTable, (colorCount));
625
626 SkPMColor* colorPtr = colorTable->lockColors();
627 if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
628 png_get_tRNS(png_ptr, info_ptr, &trans, &numTrans, NULL);
629 *hasAlphap = (numTrans > 0);
630 } else {
631 numTrans = 0;
632 colorTable->setFlags(colorTable->getFlags() | SkColorTable::kColorsAreOpaque_Flag);
633 }
634 // check for bad images that might make us crash
635 if (numTrans > numPalette) {
636 numTrans = numPalette;
637 }
638
639 int index = 0;
640 int transLessThanFF = 0;
641
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000642 // Choose which function to use to create the color table. If the final destination's
643 // config is unpremultiplied, the color table will store unpremultiplied colors.
644 PackColorProc proc;
645 if (this->getRequireUnpremultipliedColors()) {
646 proc = &SkPackARGB32NoCheck;
647 } else {
648 proc = &SkPreMultiplyARGB;
649 }
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000650 for (; index < numTrans; index++) {
651 transLessThanFF |= (int)*trans - 0xFF;
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000652 *colorPtr++ = proc(*trans++, palette->red, palette->green, palette->blue);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000653 palette++;
654 }
655 reallyHasAlpha |= (transLessThanFF < 0);
656
657 for (; index < numPalette; index++) {
658 *colorPtr++ = SkPackARGB32(0xFF, palette->red, palette->green, palette->blue);
659 palette++;
660 }
661
662 // see BUGGY IMAGE WORKAROUND comment above
663 if (numPalette < 256) {
664 *colorPtr = colorPtr[-1];
665 }
666 colorTable->unlockColors(true);
667 *colorTablep = colorTable;
668 *reallyHasAlphap = reallyHasAlpha;
669 return true;
670}
671
672#ifdef SK_BUILD_FOR_ANDROID
673
scroggo@google.comb5571b32013-09-25 21:34:24 +0000674bool SkPNGImageDecoder::onBuildTileIndex(SkStreamRewindable* sk_stream, int *width, int *height) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000675 png_structp png_ptr;
676 png_infop info_ptr;
677
678 if (!onDecodeInit(sk_stream, &png_ptr, &info_ptr)) {
679 return false;
680 }
681
682 if (setjmp(png_jmpbuf(png_ptr)) != 0) {
683 png_destroy_read_struct(&png_ptr, &info_ptr, png_infopp_NULL);
684 return false;
685 }
686
687 png_uint_32 origWidth, origHeight;
688 int bitDepth, colorType;
689 png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
690 &colorType, int_p_NULL, int_p_NULL, int_p_NULL);
691
692 *width = origWidth;
693 *height = origHeight;
694
695 png_build_index(png_ptr);
696
697 if (fImageIndex) {
698 SkDELETE(fImageIndex);
699 }
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000700 fImageIndex = SkNEW_ARGS(SkPNGImageIndex, (sk_stream, png_ptr, info_ptr));
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000701
702 return true;
703}
704
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000705bool SkPNGImageDecoder::onDecodeSubset(SkBitmap* bm, const SkIRect& region) {
706 if (NULL == fImageIndex) {
707 return false;
708 }
709
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000710 png_structp png_ptr = fImageIndex->fPng_ptr;
711 png_infop info_ptr = fImageIndex->fInfo_ptr;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000712 if (setjmp(png_jmpbuf(png_ptr))) {
713 return false;
714 }
715
716 png_uint_32 origWidth, origHeight;
717 int bitDepth, colorType, interlaceType;
718 png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
719 &colorType, &interlaceType, int_p_NULL, int_p_NULL);
720
721 SkIRect rect = SkIRect::MakeWH(origWidth, origHeight);
722
723 if (!rect.intersect(region)) {
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000724 // If the requested region is entirely outside the image, just
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000725 // returns false
726 return false;
727 }
728
729 SkBitmap::Config config;
730 bool hasAlpha = false;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000731 SkPMColor theTranspColor = 0; // 0 tells us not to try to match
732
scroggo@google.com8d239242013-10-01 17:27:15 +0000733 if (!this->getBitmapConfig(png_ptr, info_ptr, &config, &hasAlpha, &theTranspColor)) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000734 return false;
735 }
736
737 const int sampleSize = this->getSampleSize();
738 SkScaledBitmapSampler sampler(origWidth, rect.height(), sampleSize);
739
740 SkBitmap decodedBitmap;
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000741 decodedBitmap.setConfig(config, sampler.scaledWidth(), sampler.scaledHeight());
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000742
743 // from here down we are concerned with colortables and pixels
744
745 // we track if we actually see a non-opaque pixels, since sometimes a PNG sets its colortype
746 // to |= PNG_COLOR_MASK_ALPHA, but all of its pixels are in fact opaque. We care, since we
747 // draw lots faster if we can flag the bitmap has being opaque
748 bool reallyHasAlpha = false;
749 SkColorTable* colorTable = NULL;
750
751 if (colorType == PNG_COLOR_TYPE_PALETTE) {
752 decodePalette(png_ptr, info_ptr, &hasAlpha, &reallyHasAlpha, &colorTable);
753 }
754
755 SkAutoUnref aur(colorTable);
756
757 // Check ahead of time if the swap(dest, src) is possible.
758 // If yes, then we will stick to AllocPixelRef since it's cheaper with the swap happening.
759 // If no, then we will use alloc to allocate pixels to prevent garbage collection.
760 int w = rect.width() / sampleSize;
761 int h = rect.height() / sampleSize;
762 const bool swapOnly = (rect == region) && (w == decodedBitmap.width()) &&
763 (h == decodedBitmap.height()) && bm->isNull();
764 const bool needColorTable = SkBitmap::kIndex8_Config == config;
765 if (swapOnly) {
766 if (!this->allocPixelRef(&decodedBitmap, needColorTable ? colorTable : NULL)) {
767 return false;
768 }
769 } else {
770 if (!decodedBitmap.allocPixels(NULL, needColorTable ? colorTable : NULL)) {
771 return false;
772 }
773 }
774 SkAutoLockPixels alp(decodedBitmap);
775
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000776 /* Turn on interlace handling. REQUIRED if you are not using
777 * png_read_image(). To see how to handle interlacing passes,
778 * see the png_read_row() method below:
779 */
780 const int number_passes = (interlaceType != PNG_INTERLACE_NONE) ?
781 png_set_interlace_handling(png_ptr) : 1;
782
783 /* Optional call to gamma correct and add the background to the palette
784 * and update info structure. REQUIRED if you are expecting libpng to
785 * update the palette for you (ie you selected such a transform above).
786 */
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000787
788 // Direct access to png_ptr fields is deprecated in libpng > 1.2.
789#if defined(PNG_1_0_X) || defined (PNG_1_2_X)
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000790 png_ptr->pass = 0;
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000791#else
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000792 // FIXME: This sets pass as desired, but also sets iwidth. Is that ok?
793 png_set_interlaced_pass(png_ptr, 0);
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000794#endif
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000795 png_read_update_info(png_ptr, info_ptr);
796
797 int actualTop = rect.fTop;
798
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000799 if ((SkBitmap::kA8_Config == config || SkBitmap::kIndex8_Config == config)
800 && 1 == sampleSize) {
801 // A8 is only allowed if the original was GRAY.
802 SkASSERT(config != SkBitmap::kA8_Config
803 || PNG_COLOR_TYPE_GRAY == colorType);
804
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000805 for (int i = 0; i < number_passes; i++) {
806 png_configure_decoder(png_ptr, &actualTop, i);
807 for (int j = 0; j < rect.fTop - actualTop; j++) {
808 uint8_t* bmRow = decodedBitmap.getAddr8(0, 0);
809 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
810 }
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000811 png_uint_32 bitmapHeight = (png_uint_32) decodedBitmap.height();
812 for (png_uint_32 y = 0; y < bitmapHeight; y++) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000813 uint8_t* bmRow = decodedBitmap.getAddr8(0, y);
814 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
815 }
816 }
817 } else {
818 SkScaledBitmapSampler::SrcConfig sc;
819 int srcBytesPerPixel = 4;
820
821 if (colorTable != NULL) {
822 sc = SkScaledBitmapSampler::kIndex;
823 srcBytesPerPixel = 1;
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000824 } else if (SkBitmap::kA8_Config == config) {
825 // A8 is only allowed if the original was GRAY.
826 SkASSERT(PNG_COLOR_TYPE_GRAY == colorType);
827 sc = SkScaledBitmapSampler::kGray;
828 srcBytesPerPixel = 1;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000829 } else if (hasAlpha) {
830 sc = SkScaledBitmapSampler::kRGBA;
831 } else {
832 sc = SkScaledBitmapSampler::kRGBX;
833 }
834
835 /* We have to pass the colortable explicitly, since we may have one
836 even if our decodedBitmap doesn't, due to the request that we
837 upscale png's palette to a direct model
838 */
839 SkAutoLockColors ctLock(colorTable);
scroggo@google.com8d239242013-10-01 17:27:15 +0000840 if (!sampler.begin(&decodedBitmap, sc, *this, ctLock.colors())) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000841 return false;
842 }
843 const int height = decodedBitmap.height();
844
845 if (number_passes > 1) {
846 SkAutoMalloc storage(origWidth * origHeight * srcBytesPerPixel);
847 uint8_t* base = (uint8_t*)storage.get();
848 size_t rb = origWidth * srcBytesPerPixel;
849
850 for (int i = 0; i < number_passes; i++) {
851 png_configure_decoder(png_ptr, &actualTop, i);
852 for (int j = 0; j < rect.fTop - actualTop; j++) {
853 uint8_t* bmRow = (uint8_t*)decodedBitmap.getPixels();
854 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
855 }
856 uint8_t* row = base;
857 for (int32_t y = 0; y < rect.height(); y++) {
858 uint8_t* bmRow = row;
859 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
860 row += rb;
861 }
862 }
863 // now sample it
864 base += sampler.srcY0() * rb;
865 for (int y = 0; y < height; y++) {
866 reallyHasAlpha |= sampler.next(base);
867 base += sampler.srcDY() * rb;
868 }
869 } else {
870 SkAutoMalloc storage(origWidth * srcBytesPerPixel);
871 uint8_t* srcRow = (uint8_t*)storage.get();
872
873 png_configure_decoder(png_ptr, &actualTop, 0);
874 skip_src_rows(png_ptr, srcRow, sampler.srcY0());
875
876 for (int i = 0; i < rect.fTop - actualTop; i++) {
877 uint8_t* bmRow = (uint8_t*)decodedBitmap.getPixels();
878 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
879 }
880 for (int y = 0; y < height; y++) {
881 uint8_t* tmp = srcRow;
882 png_read_rows(png_ptr, &tmp, png_bytepp_NULL, 1);
883 reallyHasAlpha |= sampler.next(srcRow);
884 if (y < height - 1) {
885 skip_src_rows(png_ptr, srcRow, sampler.srcDY() - 1);
886 }
887 }
888 }
889 }
890
891 if (0 != theTranspColor) {
892 reallyHasAlpha |= substituteTranspColor(&decodedBitmap, theTranspColor);
893 }
commit-bot@chromium.org546f70c2013-10-03 17:13:38 +0000894 if (SkBitmap::kA8_Config == decodedBitmap.config()) {
895 reallyHasAlpha = true;
896 }
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000897 decodedBitmap.setIsOpaque(!reallyHasAlpha);
898
899 if (swapOnly) {
900 bm->swap(decodedBitmap);
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000901 return true;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000902 }
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000903 return this->cropBitmap(bm, &decodedBitmap, sampleSize, region.x(), region.y(),
904 region.width(), region.height(), 0, rect.y());
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000905}
906#endif
907
reed@android.com8a1c16f2008-12-17 15:59:43 +0000908///////////////////////////////////////////////////////////////////////////////
909
reed@android.com8a1c16f2008-12-17 15:59:43 +0000910#include "SkColorPriv.h"
911#include "SkUnPreMultiply.h"
912
913static void sk_write_fn(png_structp png_ptr, png_bytep data, png_size_t len) {
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000914 SkWStream* sk_stream = (SkWStream*)png_get_io_ptr(png_ptr);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000915 if (!sk_stream->write(data, len)) {
916 png_error(png_ptr, "sk_write_fn Error!");
917 }
918}
919
reed@android.com8a1c16f2008-12-17 15:59:43 +0000920static transform_scanline_proc choose_proc(SkBitmap::Config config,
921 bool hasAlpha) {
922 // we don't care about search on alpha if we're kIndex8, since only the
923 // colortable packing cares about that distinction, not the pixels
924 if (SkBitmap::kIndex8_Config == config) {
925 hasAlpha = false; // we store false in the table entries for kIndex8
926 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000927
reed@android.com8a1c16f2008-12-17 15:59:43 +0000928 static const struct {
929 SkBitmap::Config fConfig;
930 bool fHasAlpha;
931 transform_scanline_proc fProc;
932 } gMap[] = {
933 { SkBitmap::kRGB_565_Config, false, transform_scanline_565 },
934 { SkBitmap::kARGB_8888_Config, false, transform_scanline_888 },
935 { SkBitmap::kARGB_8888_Config, true, transform_scanline_8888 },
936 { SkBitmap::kARGB_4444_Config, false, transform_scanline_444 },
937 { SkBitmap::kARGB_4444_Config, true, transform_scanline_4444 },
epoger@google.com4ce738b2012-11-16 18:44:18 +0000938 { SkBitmap::kIndex8_Config, false, transform_scanline_memcpy },
reed@android.com8a1c16f2008-12-17 15:59:43 +0000939 };
940
941 for (int i = SK_ARRAY_COUNT(gMap) - 1; i >= 0; --i) {
942 if (gMap[i].fConfig == config && gMap[i].fHasAlpha == hasAlpha) {
943 return gMap[i].fProc;
944 }
945 }
946 sk_throw();
947 return NULL;
948}
949
950// return the minimum legal bitdepth (by png standards) for this many colortable
951// entries. SkBitmap always stores in 8bits per pixel, but for colorcount <= 16,
952// we can use fewer bits per in png
953static int computeBitDepth(int colorCount) {
954#if 0
955 int bits = SkNextLog2(colorCount);
956 SkASSERT(bits >= 1 && bits <= 8);
957 // now we need bits itself to be a power of 2 (e.g. 1, 2, 4, 8)
958 return SkNextPow2(bits);
959#else
960 // for the moment, we don't know how to pack bitdepth < 8
961 return 8;
962#endif
963}
964
965/* Pack palette[] with the corresponding colors, and if hasAlpha is true, also
966 pack trans[] and return the number of trans[] entries written. If hasAlpha
967 is false, the return value will always be 0.
rmistry@google.comd6176b02012-08-23 18:14:13 +0000968
reed@android.com8a1c16f2008-12-17 15:59:43 +0000969 Note: this routine takes care of unpremultiplying the RGB values when we
970 have alpha in the colortable, since png doesn't support premul colors
971*/
reed@android.com6f252972009-01-14 16:46:16 +0000972static inline int pack_palette(SkColorTable* ctable,
reed@android.comb50a60c2009-01-14 17:51:08 +0000973 png_color* SK_RESTRICT palette,
974 png_byte* SK_RESTRICT trans, bool hasAlpha) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000975 SkAutoLockColors alc(ctable);
976 const SkPMColor* SK_RESTRICT colors = alc.colors();
977 const int ctCount = ctable->count();
978 int i, num_trans = 0;
979
980 if (hasAlpha) {
981 /* first see if we have some number of fully opaque at the end of the
982 ctable. PNG allows num_trans < num_palette, but all of the trans
983 entries must come first in the palette. If I was smarter, I'd
984 reorder the indices and ctable so that all non-opaque colors came
985 first in the palette. But, since that would slow down the encode,
986 I'm leaving the indices and ctable order as is, and just looking
987 at the tail of the ctable for opaqueness.
988 */
989 num_trans = ctCount;
990 for (i = ctCount - 1; i >= 0; --i) {
991 if (SkGetPackedA32(colors[i]) != 0xFF) {
992 break;
993 }
994 num_trans -= 1;
995 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000996
reed@android.com8a1c16f2008-12-17 15:59:43 +0000997 const SkUnPreMultiply::Scale* SK_RESTRICT table =
998 SkUnPreMultiply::GetScaleTable();
999
1000 for (i = 0; i < num_trans; i++) {
1001 const SkPMColor c = *colors++;
1002 const unsigned a = SkGetPackedA32(c);
1003 const SkUnPreMultiply::Scale s = table[a];
1004 trans[i] = a;
1005 palette[i].red = SkUnPreMultiply::ApplyScale(s, SkGetPackedR32(c));
1006 palette[i].green = SkUnPreMultiply::ApplyScale(s,SkGetPackedG32(c));
1007 palette[i].blue = SkUnPreMultiply::ApplyScale(s, SkGetPackedB32(c));
rmistry@google.comd6176b02012-08-23 18:14:13 +00001008 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001009 // now fall out of this if-block to use common code for the trailing
1010 // opaque entries
1011 }
rmistry@google.comd6176b02012-08-23 18:14:13 +00001012
reed@android.com8a1c16f2008-12-17 15:59:43 +00001013 // these (remaining) entries are opaque
1014 for (i = num_trans; i < ctCount; i++) {
1015 SkPMColor c = *colors++;
1016 palette[i].red = SkGetPackedR32(c);
1017 palette[i].green = SkGetPackedG32(c);
1018 palette[i].blue = SkGetPackedB32(c);
1019 }
1020 return num_trans;
1021}
1022
1023class SkPNGImageEncoder : public SkImageEncoder {
1024protected:
commit-bot@chromium.orga936e372013-03-14 14:42:18 +00001025 virtual bool onEncode(SkWStream* stream, const SkBitmap& bm, int quality) SK_OVERRIDE;
tomhudson@google.com5c210c72011-07-28 21:06:40 +00001026private:
1027 bool doEncode(SkWStream* stream, const SkBitmap& bm,
1028 const bool& hasAlpha, int colorType,
1029 int bitDepth, SkBitmap::Config config,
1030 png_color_8& sig_bit);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +00001031
1032 typedef SkImageEncoder INHERITED;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001033};
1034
1035bool SkPNGImageEncoder::onEncode(SkWStream* stream, const SkBitmap& bitmap,
1036 int /*quality*/) {
1037 SkBitmap::Config config = bitmap.getConfig();
1038
1039 const bool hasAlpha = !bitmap.isOpaque();
1040 int colorType = PNG_COLOR_MASK_COLOR;
1041 int bitDepth = 8; // default for color
1042 png_color_8 sig_bit;
1043
1044 switch (config) {
1045 case SkBitmap::kIndex8_Config:
1046 colorType |= PNG_COLOR_MASK_PALETTE;
1047 // fall through to the ARGB_8888 case
1048 case SkBitmap::kARGB_8888_Config:
1049 sig_bit.red = 8;
1050 sig_bit.green = 8;
1051 sig_bit.blue = 8;
1052 sig_bit.alpha = 8;
1053 break;
1054 case SkBitmap::kARGB_4444_Config:
1055 sig_bit.red = 4;
1056 sig_bit.green = 4;
1057 sig_bit.blue = 4;
1058 sig_bit.alpha = 4;
1059 break;
1060 case SkBitmap::kRGB_565_Config:
1061 sig_bit.red = 5;
1062 sig_bit.green = 6;
1063 sig_bit.blue = 5;
1064 sig_bit.alpha = 0;
1065 break;
1066 default:
1067 return false;
1068 }
rmistry@google.comd6176b02012-08-23 18:14:13 +00001069
reed@android.com8a1c16f2008-12-17 15:59:43 +00001070 if (hasAlpha) {
1071 // don't specify alpha if we're a palette, even if our ctable has alpha
1072 if (!(colorType & PNG_COLOR_MASK_PALETTE)) {
1073 colorType |= PNG_COLOR_MASK_ALPHA;
1074 }
1075 } else {
1076 sig_bit.alpha = 0;
1077 }
rmistry@google.comd6176b02012-08-23 18:14:13 +00001078
reed@android.com8a1c16f2008-12-17 15:59:43 +00001079 SkAutoLockPixels alp(bitmap);
1080 // readyToDraw checks for pixels (and colortable if that is required)
1081 if (!bitmap.readyToDraw()) {
1082 return false;
1083 }
1084
1085 // we must do this after we have locked the pixels
1086 SkColorTable* ctable = bitmap.getColorTable();
1087 if (NULL != ctable) {
1088 if (ctable->count() == 0) {
1089 return false;
1090 }
1091 // check if we can store in fewer than 8 bits
1092 bitDepth = computeBitDepth(ctable->count());
1093 }
1094
tomhudson@google.com5c210c72011-07-28 21:06:40 +00001095 return doEncode(stream, bitmap, hasAlpha, colorType,
1096 bitDepth, config, sig_bit);
1097}
1098
1099bool SkPNGImageEncoder::doEncode(SkWStream* stream, const SkBitmap& bitmap,
1100 const bool& hasAlpha, int colorType,
1101 int bitDepth, SkBitmap::Config config,
1102 png_color_8& sig_bit) {
1103
reed@android.com8a1c16f2008-12-17 15:59:43 +00001104 png_structp png_ptr;
1105 png_infop info_ptr;
1106
1107 png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, sk_error_fn,
1108 NULL);
1109 if (NULL == png_ptr) {
1110 return false;
1111 }
1112
1113 info_ptr = png_create_info_struct(png_ptr);
1114 if (NULL == info_ptr) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +00001115 png_destroy_write_struct(&png_ptr, png_infopp_NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001116 return false;
1117 }
1118
1119 /* Set error handling. REQUIRED if you aren't supplying your own
1120 * error handling functions in the png_create_write_struct() call.
1121 */
1122 if (setjmp(png_jmpbuf(png_ptr))) {
1123 png_destroy_write_struct(&png_ptr, &info_ptr);
1124 return false;
1125 }
1126
commit-bot@chromium.orga936e372013-03-14 14:42:18 +00001127 png_set_write_fn(png_ptr, (void*)stream, sk_write_fn, png_flush_ptr_NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001128
1129 /* Set the image information here. Width and height are up to 2^31,
1130 * bit_depth is one of 1, 2, 4, 8, or 16, but valid values also depend on
1131 * the color_type selected. color_type is one of PNG_COLOR_TYPE_GRAY,
1132 * PNG_COLOR_TYPE_GRAY_ALPHA, PNG_COLOR_TYPE_PALETTE, PNG_COLOR_TYPE_RGB,
1133 * or PNG_COLOR_TYPE_RGB_ALPHA. interlace is either PNG_INTERLACE_NONE or
1134 * PNG_INTERLACE_ADAM7, and the compression_type and filter_type MUST
1135 * currently be PNG_COMPRESSION_TYPE_BASE and PNG_FILTER_TYPE_BASE. REQUIRED
1136 */
1137
1138 png_set_IHDR(png_ptr, info_ptr, bitmap.width(), bitmap.height(),
1139 bitDepth, colorType,
1140 PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE,
1141 PNG_FILTER_TYPE_BASE);
1142
reed@android.com61898772009-07-07 19:38:01 +00001143 // set our colortable/trans arrays if needed
1144 png_color paletteColors[256];
1145 png_byte trans[256];
1146 if (SkBitmap::kIndex8_Config == config) {
1147 SkColorTable* ct = bitmap.getColorTable();
1148 int numTrans = pack_palette(ct, paletteColors, trans, hasAlpha);
1149 png_set_PLTE(png_ptr, info_ptr, paletteColors, ct->count());
1150 if (numTrans > 0) {
1151 png_set_tRNS(png_ptr, info_ptr, trans, numTrans, NULL);
1152 }
1153 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001154
1155 png_set_sBIT(png_ptr, info_ptr, &sig_bit);
1156 png_write_info(png_ptr, info_ptr);
1157
1158 const char* srcImage = (const char*)bitmap.getPixels();
1159 SkAutoSMalloc<1024> rowStorage(bitmap.width() << 2);
1160 char* storage = (char*)rowStorage.get();
1161 transform_scanline_proc proc = choose_proc(config, hasAlpha);
1162
1163 for (int y = 0; y < bitmap.height(); y++) {
1164 png_bytep row_ptr = (png_bytep)storage;
1165 proc(srcImage, bitmap.width(), storage);
1166 png_write_rows(png_ptr, &row_ptr, 1);
1167 srcImage += bitmap.rowBytes();
1168 }
1169
1170 png_write_end(png_ptr, info_ptr);
1171
1172 /* clean up after the write, and free any memory allocated */
1173 png_destroy_write_struct(&png_ptr, &info_ptr);
1174 return true;
1175}
1176
reed@android.com00bf85a2009-01-22 13:04:56 +00001177///////////////////////////////////////////////////////////////////////////////
robertphillips@google.comec51cb82012-03-23 18:13:47 +00001178DEFINE_DECODER_CREATOR(PNGImageDecoder);
1179DEFINE_ENCODER_CREATOR(PNGImageEncoder);
1180///////////////////////////////////////////////////////////////////////////////
reed@android.com00bf85a2009-01-22 13:04:56 +00001181
scroggo@google.comb5571b32013-09-25 21:34:24 +00001182static bool is_png(SkStreamRewindable* stream) {
reed@android.com00bf85a2009-01-22 13:04:56 +00001183 char buf[PNG_BYTES_TO_CHECK];
1184 if (stream->read(buf, PNG_BYTES_TO_CHECK) == PNG_BYTES_TO_CHECK &&
1185 !png_sig_cmp((png_bytep) buf, (png_size_t)0, PNG_BYTES_TO_CHECK)) {
scroggo@google.com39edf4c2013-04-25 17:33:51 +00001186 return true;
1187 }
1188 return false;
1189}
1190
scroggo@google.comb5571b32013-09-25 21:34:24 +00001191SkImageDecoder* sk_libpng_dfactory(SkStreamRewindable* stream) {
scroggo@google.com39edf4c2013-04-25 17:33:51 +00001192 if (is_png(stream)) {
reed@android.com00bf85a2009-01-22 13:04:56 +00001193 return SkNEW(SkPNGImageDecoder);
1194 }
1195 return NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001196}
1197
scroggo@google.comb5571b32013-09-25 21:34:24 +00001198static SkImageDecoder::Format get_format_png(SkStreamRewindable* stream) {
scroggo@google.com39edf4c2013-04-25 17:33:51 +00001199 if (is_png(stream)) {
1200 return SkImageDecoder::kPNG_Format;
1201 }
1202 return SkImageDecoder::kUnknown_Format;
1203}
1204
reed@android.comdfee5792010-04-15 14:24:50 +00001205SkImageEncoder* sk_libpng_efactory(SkImageEncoder::Type t) {
reed@android.com00bf85a2009-01-22 13:04:56 +00001206 return (SkImageEncoder::kPNG_Type == t) ? SkNEW(SkPNGImageEncoder) : NULL;
1207}
1208
mtklein@google.combd6343b2013-09-04 17:20:18 +00001209static SkImageDecoder_DecodeReg gDReg(sk_libpng_dfactory);
1210static SkImageDecoder_FormatReg gFormatReg(get_format_png);
1211static SkImageEncoder_EncodeReg gEReg(sk_libpng_efactory);