blob: e942f21c787c5e01f6d11ea392309ca6aad562b7 [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 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000438 decodedBitmap->setIsOpaque(!reallyHasAlpha);
439 return true;
440}
441
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000442
443
444bool SkPNGImageDecoder::getBitmapConfig(png_structp png_ptr, png_infop info_ptr,
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000445 SkBitmap::Config* SK_RESTRICT configp,
446 bool* SK_RESTRICT hasAlphap,
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000447 SkPMColor* SK_RESTRICT theTranspColorp) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000448 png_uint_32 origWidth, origHeight;
449 int bitDepth, colorType;
450 png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
451 &colorType, int_p_NULL, int_p_NULL, int_p_NULL);
452
453 // check for sBIT chunk data, in case we should disable dithering because
454 // our data is not truely 8bits per component
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000455 png_color_8p sig_bit;
scroggo@google.com8d239242013-10-01 17:27:15 +0000456 if (this->getDitherImage() && png_get_sBIT(png_ptr, info_ptr, &sig_bit)) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000457#if 0
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000458 SkDebugf("----- sBIT %d %d %d %d\n", sig_bit->red, sig_bit->green,
459 sig_bit->blue, sig_bit->alpha);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000460#endif
461 // 0 seems to indicate no information available
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000462 if (pos_le(sig_bit->red, SK_R16_BITS) &&
463 pos_le(sig_bit->green, SK_G16_BITS) &&
464 pos_le(sig_bit->blue, SK_B16_BITS)) {
scroggo@google.com8d239242013-10-01 17:27:15 +0000465 this->setDitherImage(false);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000466 }
467 }
468
469 if (colorType == PNG_COLOR_TYPE_PALETTE) {
470 bool paletteHasAlpha = hasTransparencyInPalette(png_ptr, info_ptr);
471 *configp = this->getPrefConfig(kIndex_SrcDepth, paletteHasAlpha);
472 // now see if we can upscale to their requested config
473 if (!canUpscalePaletteToConfig(*configp, paletteHasAlpha)) {
474 *configp = SkBitmap::kIndex8_Config;
475 }
476 } else {
477 png_color_16p transpColor = NULL;
478 int numTransp = 0;
479
480 png_get_tRNS(png_ptr, info_ptr, NULL, &numTransp, &transpColor);
481
482 bool valid = png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS);
483
484 if (valid && numTransp == 1 && transpColor != NULL) {
485 /* Compute our transparent color, which we'll match against later.
486 We don't really handle 16bit components properly here, since we
487 do our compare *after* the values have been knocked down to 8bit
488 which means we will find more matches than we should. The real
489 fix seems to be to see the actual 16bit components, do the
490 compare, and then knock it down to 8bits ourselves.
491 */
492 if (colorType & PNG_COLOR_MASK_COLOR) {
493 if (16 == bitDepth) {
494 *theTranspColorp = SkPackARGB32(0xFF, transpColor->red >> 8,
495 transpColor->green >> 8,
496 transpColor->blue >> 8);
497 } else {
498 *theTranspColorp = SkPackARGB32(0xFF, transpColor->red,
499 transpColor->green,
500 transpColor->blue);
501 }
502 } else { // gray
503 if (16 == bitDepth) {
504 *theTranspColorp = SkPackARGB32(0xFF, transpColor->gray >> 8,
505 transpColor->gray >> 8,
506 transpColor->gray >> 8);
507 } else {
508 *theTranspColorp = SkPackARGB32(0xFF, transpColor->gray,
509 transpColor->gray,
510 transpColor->gray);
511 }
512 }
513 }
514
515 if (valid ||
516 PNG_COLOR_TYPE_RGB_ALPHA == colorType ||
517 PNG_COLOR_TYPE_GRAY_ALPHA == colorType) {
518 *hasAlphap = true;
519 }
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000520
521 SrcDepth srcDepth = k32Bit_SrcDepth;
522 if (PNG_COLOR_TYPE_GRAY == colorType) {
523 srcDepth = k8BitGray_SrcDepth;
scroggo@google.com8e2ef012013-07-18 20:14:45 +0000524 // Remove this assert, which fails on desk_pokemonwiki.skp
525 //SkASSERT(!*hasAlphap);
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000526 }
527
528 *configp = this->getPrefConfig(srcDepth, *hasAlphap);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000529 // now match the request against our capabilities
530 if (*hasAlphap) {
531 if (*configp != SkBitmap::kARGB_4444_Config) {
532 *configp = SkBitmap::kARGB_8888_Config;
533 }
534 } else {
535 if (*configp != SkBitmap::kRGB_565_Config &&
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000536 *configp != SkBitmap::kARGB_4444_Config &&
537 *configp != SkBitmap::kA8_Config) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000538 *configp = SkBitmap::kARGB_8888_Config;
539 }
540 }
541 }
542
543 // sanity check for size
544 {
545 Sk64 size;
546 size.setMul(origWidth, origHeight);
547 if (size.isNeg() || !size.is32()) {
548 return false;
549 }
550 // now check that if we are 4-bytes per pixel, we also don't overflow
551 if (size.get32() > (0x7FFFFFFF >> 2)) {
552 return false;
553 }
554 }
555
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000556 if (!this->chooseFromOneChoice(*configp, origWidth, origHeight)) {
557 return false;
558 }
559
560 // If the image has alpha and the decoder wants unpremultiplied
561 // colors, the only supported config is 8888.
562 if (this->getRequireUnpremultipliedColors() && *hasAlphap) {
563 *configp = SkBitmap::kARGB_8888_Config;
564 }
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000565
566 if (fImageIndex != NULL) {
567 if (SkBitmap::kNo_Config == fImageIndex->fConfig) {
568 // This is the first time for this subset decode. From now on,
569 // all decodes must be in the same config.
570 fImageIndex->fConfig = *configp;
571 } else if (fImageIndex->fConfig != *configp) {
572 // Requesting a different config for a subsequent decode is not
573 // supported. Report failure before we make changes to png_ptr.
574 return false;
575 }
576 }
577
578 bool convertGrayToRGB = PNG_COLOR_TYPE_GRAY == colorType
579 && *configp != SkBitmap::kA8_Config;
580
581 // Unless the user is requesting A8, convert a grayscale image into RGB.
582 // GRAY_ALPHA will always be converted to RGB
583 if (convertGrayToRGB || colorType == PNG_COLOR_TYPE_GRAY_ALPHA) {
584 png_set_gray_to_rgb(png_ptr);
585 }
586
587 // Add filler (or alpha) byte (after each RGB triplet) if necessary.
588 if (colorType == PNG_COLOR_TYPE_RGB || convertGrayToRGB) {
589 png_set_filler(png_ptr, 0xff, PNG_FILLER_AFTER);
590 }
591
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000592 return true;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000593}
594
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000595typedef uint32_t (*PackColorProc)(U8CPU a, U8CPU r, U8CPU g, U8CPU b);
596
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000597bool SkPNGImageDecoder::decodePalette(png_structp png_ptr, png_infop info_ptr,
598 bool *hasAlphap, bool *reallyHasAlphap,
599 SkColorTable **colorTablep) {
600 int numPalette;
601 png_colorp palette;
602 png_bytep trans;
603 int numTrans;
604 bool reallyHasAlpha = false;
605 SkColorTable* colorTable = NULL;
606
607 png_get_PLTE(png_ptr, info_ptr, &palette, &numPalette);
608
609 /* BUGGY IMAGE WORKAROUND
610
611 We hit some images (e.g. fruit_.png) who contain bytes that are == colortable_count
612 which is a problem since we use the byte as an index. To work around this we grow
613 the colortable by 1 (if its < 256) and duplicate the last color into that slot.
614 */
615 int colorCount = numPalette + (numPalette < 256);
616
617 colorTable = SkNEW_ARGS(SkColorTable, (colorCount));
618
619 SkPMColor* colorPtr = colorTable->lockColors();
620 if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
621 png_get_tRNS(png_ptr, info_ptr, &trans, &numTrans, NULL);
622 *hasAlphap = (numTrans > 0);
623 } else {
624 numTrans = 0;
625 colorTable->setFlags(colorTable->getFlags() | SkColorTable::kColorsAreOpaque_Flag);
626 }
627 // check for bad images that might make us crash
628 if (numTrans > numPalette) {
629 numTrans = numPalette;
630 }
631
632 int index = 0;
633 int transLessThanFF = 0;
634
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000635 // Choose which function to use to create the color table. If the final destination's
636 // config is unpremultiplied, the color table will store unpremultiplied colors.
637 PackColorProc proc;
638 if (this->getRequireUnpremultipliedColors()) {
639 proc = &SkPackARGB32NoCheck;
640 } else {
641 proc = &SkPreMultiplyARGB;
642 }
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000643 for (; index < numTrans; index++) {
644 transLessThanFF |= (int)*trans - 0xFF;
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000645 *colorPtr++ = proc(*trans++, palette->red, palette->green, palette->blue);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000646 palette++;
647 }
648 reallyHasAlpha |= (transLessThanFF < 0);
649
650 for (; index < numPalette; index++) {
651 *colorPtr++ = SkPackARGB32(0xFF, palette->red, palette->green, palette->blue);
652 palette++;
653 }
654
655 // see BUGGY IMAGE WORKAROUND comment above
656 if (numPalette < 256) {
657 *colorPtr = colorPtr[-1];
658 }
659 colorTable->unlockColors(true);
660 *colorTablep = colorTable;
661 *reallyHasAlphap = reallyHasAlpha;
662 return true;
663}
664
665#ifdef SK_BUILD_FOR_ANDROID
666
scroggo@google.comb5571b32013-09-25 21:34:24 +0000667bool SkPNGImageDecoder::onBuildTileIndex(SkStreamRewindable* sk_stream, int *width, int *height) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000668 png_structp png_ptr;
669 png_infop info_ptr;
670
671 if (!onDecodeInit(sk_stream, &png_ptr, &info_ptr)) {
672 return false;
673 }
674
675 if (setjmp(png_jmpbuf(png_ptr)) != 0) {
676 png_destroy_read_struct(&png_ptr, &info_ptr, png_infopp_NULL);
677 return false;
678 }
679
680 png_uint_32 origWidth, origHeight;
681 int bitDepth, colorType;
682 png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
683 &colorType, int_p_NULL, int_p_NULL, int_p_NULL);
684
685 *width = origWidth;
686 *height = origHeight;
687
688 png_build_index(png_ptr);
689
690 if (fImageIndex) {
691 SkDELETE(fImageIndex);
692 }
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000693 fImageIndex = SkNEW_ARGS(SkPNGImageIndex, (sk_stream, png_ptr, info_ptr));
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000694
695 return true;
696}
697
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000698bool SkPNGImageDecoder::onDecodeSubset(SkBitmap* bm, const SkIRect& region) {
699 if (NULL == fImageIndex) {
700 return false;
701 }
702
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000703 png_structp png_ptr = fImageIndex->fPng_ptr;
704 png_infop info_ptr = fImageIndex->fInfo_ptr;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000705 if (setjmp(png_jmpbuf(png_ptr))) {
706 return false;
707 }
708
709 png_uint_32 origWidth, origHeight;
710 int bitDepth, colorType, interlaceType;
711 png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
712 &colorType, &interlaceType, int_p_NULL, int_p_NULL);
713
714 SkIRect rect = SkIRect::MakeWH(origWidth, origHeight);
715
716 if (!rect.intersect(region)) {
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000717 // If the requested region is entirely outside the image, just
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000718 // returns false
719 return false;
720 }
721
722 SkBitmap::Config config;
723 bool hasAlpha = false;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000724 SkPMColor theTranspColor = 0; // 0 tells us not to try to match
725
scroggo@google.com8d239242013-10-01 17:27:15 +0000726 if (!this->getBitmapConfig(png_ptr, info_ptr, &config, &hasAlpha, &theTranspColor)) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000727 return false;
728 }
729
730 const int sampleSize = this->getSampleSize();
731 SkScaledBitmapSampler sampler(origWidth, rect.height(), sampleSize);
732
733 SkBitmap decodedBitmap;
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000734 decodedBitmap.setConfig(config, sampler.scaledWidth(), sampler.scaledHeight());
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000735
736 // from here down we are concerned with colortables and pixels
737
738 // we track if we actually see a non-opaque pixels, since sometimes a PNG sets its colortype
739 // to |= PNG_COLOR_MASK_ALPHA, but all of its pixels are in fact opaque. We care, since we
740 // draw lots faster if we can flag the bitmap has being opaque
741 bool reallyHasAlpha = false;
742 SkColorTable* colorTable = NULL;
743
744 if (colorType == PNG_COLOR_TYPE_PALETTE) {
745 decodePalette(png_ptr, info_ptr, &hasAlpha, &reallyHasAlpha, &colorTable);
746 }
747
748 SkAutoUnref aur(colorTable);
749
750 // Check ahead of time if the swap(dest, src) is possible.
751 // If yes, then we will stick to AllocPixelRef since it's cheaper with the swap happening.
752 // If no, then we will use alloc to allocate pixels to prevent garbage collection.
753 int w = rect.width() / sampleSize;
754 int h = rect.height() / sampleSize;
755 const bool swapOnly = (rect == region) && (w == decodedBitmap.width()) &&
756 (h == decodedBitmap.height()) && bm->isNull();
757 const bool needColorTable = SkBitmap::kIndex8_Config == config;
758 if (swapOnly) {
759 if (!this->allocPixelRef(&decodedBitmap, needColorTable ? colorTable : NULL)) {
760 return false;
761 }
762 } else {
763 if (!decodedBitmap.allocPixels(NULL, needColorTable ? colorTable : NULL)) {
764 return false;
765 }
766 }
767 SkAutoLockPixels alp(decodedBitmap);
768
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000769 /* Turn on interlace handling. REQUIRED if you are not using
770 * png_read_image(). To see how to handle interlacing passes,
771 * see the png_read_row() method below:
772 */
773 const int number_passes = (interlaceType != PNG_INTERLACE_NONE) ?
774 png_set_interlace_handling(png_ptr) : 1;
775
776 /* Optional call to gamma correct and add the background to the palette
777 * and update info structure. REQUIRED if you are expecting libpng to
778 * update the palette for you (ie you selected such a transform above).
779 */
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000780
781 // Direct access to png_ptr fields is deprecated in libpng > 1.2.
782#if defined(PNG_1_0_X) || defined (PNG_1_2_X)
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000783 png_ptr->pass = 0;
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000784#else
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000785 // FIXME: This sets pass as desired, but also sets iwidth. Is that ok?
786 png_set_interlaced_pass(png_ptr, 0);
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000787#endif
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000788 png_read_update_info(png_ptr, info_ptr);
789
790 int actualTop = rect.fTop;
791
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000792 if ((SkBitmap::kA8_Config == config || SkBitmap::kIndex8_Config == config)
793 && 1 == sampleSize) {
794 // A8 is only allowed if the original was GRAY.
795 SkASSERT(config != SkBitmap::kA8_Config
796 || PNG_COLOR_TYPE_GRAY == colorType);
797
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000798 for (int i = 0; i < number_passes; i++) {
799 png_configure_decoder(png_ptr, &actualTop, i);
800 for (int j = 0; j < rect.fTop - actualTop; j++) {
801 uint8_t* bmRow = decodedBitmap.getAddr8(0, 0);
802 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
803 }
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000804 png_uint_32 bitmapHeight = (png_uint_32) decodedBitmap.height();
805 for (png_uint_32 y = 0; y < bitmapHeight; y++) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000806 uint8_t* bmRow = decodedBitmap.getAddr8(0, y);
807 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
808 }
809 }
810 } else {
811 SkScaledBitmapSampler::SrcConfig sc;
812 int srcBytesPerPixel = 4;
813
814 if (colorTable != NULL) {
815 sc = SkScaledBitmapSampler::kIndex;
816 srcBytesPerPixel = 1;
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000817 } else if (SkBitmap::kA8_Config == config) {
818 // A8 is only allowed if the original was GRAY.
819 SkASSERT(PNG_COLOR_TYPE_GRAY == colorType);
820 sc = SkScaledBitmapSampler::kGray;
821 srcBytesPerPixel = 1;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000822 } else if (hasAlpha) {
823 sc = SkScaledBitmapSampler::kRGBA;
824 } else {
825 sc = SkScaledBitmapSampler::kRGBX;
826 }
827
828 /* We have to pass the colortable explicitly, since we may have one
829 even if our decodedBitmap doesn't, due to the request that we
830 upscale png's palette to a direct model
831 */
832 SkAutoLockColors ctLock(colorTable);
scroggo@google.com8d239242013-10-01 17:27:15 +0000833 if (!sampler.begin(&decodedBitmap, sc, *this, ctLock.colors())) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000834 return false;
835 }
836 const int height = decodedBitmap.height();
837
838 if (number_passes > 1) {
839 SkAutoMalloc storage(origWidth * origHeight * srcBytesPerPixel);
840 uint8_t* base = (uint8_t*)storage.get();
841 size_t rb = origWidth * srcBytesPerPixel;
842
843 for (int i = 0; i < number_passes; i++) {
844 png_configure_decoder(png_ptr, &actualTop, i);
845 for (int j = 0; j < rect.fTop - actualTop; j++) {
846 uint8_t* bmRow = (uint8_t*)decodedBitmap.getPixels();
847 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
848 }
849 uint8_t* row = base;
850 for (int32_t y = 0; y < rect.height(); y++) {
851 uint8_t* bmRow = row;
852 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
853 row += rb;
854 }
855 }
856 // now sample it
857 base += sampler.srcY0() * rb;
858 for (int y = 0; y < height; y++) {
859 reallyHasAlpha |= sampler.next(base);
860 base += sampler.srcDY() * rb;
861 }
862 } else {
863 SkAutoMalloc storage(origWidth * srcBytesPerPixel);
864 uint8_t* srcRow = (uint8_t*)storage.get();
865
866 png_configure_decoder(png_ptr, &actualTop, 0);
867 skip_src_rows(png_ptr, srcRow, sampler.srcY0());
868
869 for (int i = 0; i < rect.fTop - actualTop; i++) {
870 uint8_t* bmRow = (uint8_t*)decodedBitmap.getPixels();
871 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
872 }
873 for (int y = 0; y < height; y++) {
874 uint8_t* tmp = srcRow;
875 png_read_rows(png_ptr, &tmp, png_bytepp_NULL, 1);
876 reallyHasAlpha |= sampler.next(srcRow);
877 if (y < height - 1) {
878 skip_src_rows(png_ptr, srcRow, sampler.srcDY() - 1);
879 }
880 }
881 }
882 }
883
884 if (0 != theTranspColor) {
885 reallyHasAlpha |= substituteTranspColor(&decodedBitmap, theTranspColor);
886 }
887 decodedBitmap.setIsOpaque(!reallyHasAlpha);
888
889 if (swapOnly) {
890 bm->swap(decodedBitmap);
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000891 return true;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000892 }
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000893 return this->cropBitmap(bm, &decodedBitmap, sampleSize, region.x(), region.y(),
894 region.width(), region.height(), 0, rect.y());
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000895}
896#endif
897
reed@android.com8a1c16f2008-12-17 15:59:43 +0000898///////////////////////////////////////////////////////////////////////////////
899
reed@android.com8a1c16f2008-12-17 15:59:43 +0000900#include "SkColorPriv.h"
901#include "SkUnPreMultiply.h"
902
903static void sk_write_fn(png_structp png_ptr, png_bytep data, png_size_t len) {
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000904 SkWStream* sk_stream = (SkWStream*)png_get_io_ptr(png_ptr);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000905 if (!sk_stream->write(data, len)) {
906 png_error(png_ptr, "sk_write_fn Error!");
907 }
908}
909
reed@android.com8a1c16f2008-12-17 15:59:43 +0000910static transform_scanline_proc choose_proc(SkBitmap::Config config,
911 bool hasAlpha) {
912 // we don't care about search on alpha if we're kIndex8, since only the
913 // colortable packing cares about that distinction, not the pixels
914 if (SkBitmap::kIndex8_Config == config) {
915 hasAlpha = false; // we store false in the table entries for kIndex8
916 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000917
reed@android.com8a1c16f2008-12-17 15:59:43 +0000918 static const struct {
919 SkBitmap::Config fConfig;
920 bool fHasAlpha;
921 transform_scanline_proc fProc;
922 } gMap[] = {
923 { SkBitmap::kRGB_565_Config, false, transform_scanline_565 },
924 { SkBitmap::kARGB_8888_Config, false, transform_scanline_888 },
925 { SkBitmap::kARGB_8888_Config, true, transform_scanline_8888 },
926 { SkBitmap::kARGB_4444_Config, false, transform_scanline_444 },
927 { SkBitmap::kARGB_4444_Config, true, transform_scanline_4444 },
epoger@google.com4ce738b2012-11-16 18:44:18 +0000928 { SkBitmap::kIndex8_Config, false, transform_scanline_memcpy },
reed@android.com8a1c16f2008-12-17 15:59:43 +0000929 };
930
931 for (int i = SK_ARRAY_COUNT(gMap) - 1; i >= 0; --i) {
932 if (gMap[i].fConfig == config && gMap[i].fHasAlpha == hasAlpha) {
933 return gMap[i].fProc;
934 }
935 }
936 sk_throw();
937 return NULL;
938}
939
940// return the minimum legal bitdepth (by png standards) for this many colortable
941// entries. SkBitmap always stores in 8bits per pixel, but for colorcount <= 16,
942// we can use fewer bits per in png
943static int computeBitDepth(int colorCount) {
944#if 0
945 int bits = SkNextLog2(colorCount);
946 SkASSERT(bits >= 1 && bits <= 8);
947 // now we need bits itself to be a power of 2 (e.g. 1, 2, 4, 8)
948 return SkNextPow2(bits);
949#else
950 // for the moment, we don't know how to pack bitdepth < 8
951 return 8;
952#endif
953}
954
955/* Pack palette[] with the corresponding colors, and if hasAlpha is true, also
956 pack trans[] and return the number of trans[] entries written. If hasAlpha
957 is false, the return value will always be 0.
rmistry@google.comd6176b02012-08-23 18:14:13 +0000958
reed@android.com8a1c16f2008-12-17 15:59:43 +0000959 Note: this routine takes care of unpremultiplying the RGB values when we
960 have alpha in the colortable, since png doesn't support premul colors
961*/
reed@android.com6f252972009-01-14 16:46:16 +0000962static inline int pack_palette(SkColorTable* ctable,
reed@android.comb50a60c2009-01-14 17:51:08 +0000963 png_color* SK_RESTRICT palette,
964 png_byte* SK_RESTRICT trans, bool hasAlpha) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000965 SkAutoLockColors alc(ctable);
966 const SkPMColor* SK_RESTRICT colors = alc.colors();
967 const int ctCount = ctable->count();
968 int i, num_trans = 0;
969
970 if (hasAlpha) {
971 /* first see if we have some number of fully opaque at the end of the
972 ctable. PNG allows num_trans < num_palette, but all of the trans
973 entries must come first in the palette. If I was smarter, I'd
974 reorder the indices and ctable so that all non-opaque colors came
975 first in the palette. But, since that would slow down the encode,
976 I'm leaving the indices and ctable order as is, and just looking
977 at the tail of the ctable for opaqueness.
978 */
979 num_trans = ctCount;
980 for (i = ctCount - 1; i >= 0; --i) {
981 if (SkGetPackedA32(colors[i]) != 0xFF) {
982 break;
983 }
984 num_trans -= 1;
985 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000986
reed@android.com8a1c16f2008-12-17 15:59:43 +0000987 const SkUnPreMultiply::Scale* SK_RESTRICT table =
988 SkUnPreMultiply::GetScaleTable();
989
990 for (i = 0; i < num_trans; i++) {
991 const SkPMColor c = *colors++;
992 const unsigned a = SkGetPackedA32(c);
993 const SkUnPreMultiply::Scale s = table[a];
994 trans[i] = a;
995 palette[i].red = SkUnPreMultiply::ApplyScale(s, SkGetPackedR32(c));
996 palette[i].green = SkUnPreMultiply::ApplyScale(s,SkGetPackedG32(c));
997 palette[i].blue = SkUnPreMultiply::ApplyScale(s, SkGetPackedB32(c));
rmistry@google.comd6176b02012-08-23 18:14:13 +0000998 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000999 // now fall out of this if-block to use common code for the trailing
1000 // opaque entries
1001 }
rmistry@google.comd6176b02012-08-23 18:14:13 +00001002
reed@android.com8a1c16f2008-12-17 15:59:43 +00001003 // these (remaining) entries are opaque
1004 for (i = num_trans; i < ctCount; i++) {
1005 SkPMColor c = *colors++;
1006 palette[i].red = SkGetPackedR32(c);
1007 palette[i].green = SkGetPackedG32(c);
1008 palette[i].blue = SkGetPackedB32(c);
1009 }
1010 return num_trans;
1011}
1012
1013class SkPNGImageEncoder : public SkImageEncoder {
1014protected:
commit-bot@chromium.orga936e372013-03-14 14:42:18 +00001015 virtual bool onEncode(SkWStream* stream, const SkBitmap& bm, int quality) SK_OVERRIDE;
tomhudson@google.com5c210c72011-07-28 21:06:40 +00001016private:
1017 bool doEncode(SkWStream* stream, const SkBitmap& bm,
1018 const bool& hasAlpha, int colorType,
1019 int bitDepth, SkBitmap::Config config,
1020 png_color_8& sig_bit);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +00001021
1022 typedef SkImageEncoder INHERITED;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001023};
1024
1025bool SkPNGImageEncoder::onEncode(SkWStream* stream, const SkBitmap& bitmap,
1026 int /*quality*/) {
1027 SkBitmap::Config config = bitmap.getConfig();
1028
1029 const bool hasAlpha = !bitmap.isOpaque();
1030 int colorType = PNG_COLOR_MASK_COLOR;
1031 int bitDepth = 8; // default for color
1032 png_color_8 sig_bit;
1033
1034 switch (config) {
1035 case SkBitmap::kIndex8_Config:
1036 colorType |= PNG_COLOR_MASK_PALETTE;
1037 // fall through to the ARGB_8888 case
1038 case SkBitmap::kARGB_8888_Config:
1039 sig_bit.red = 8;
1040 sig_bit.green = 8;
1041 sig_bit.blue = 8;
1042 sig_bit.alpha = 8;
1043 break;
1044 case SkBitmap::kARGB_4444_Config:
1045 sig_bit.red = 4;
1046 sig_bit.green = 4;
1047 sig_bit.blue = 4;
1048 sig_bit.alpha = 4;
1049 break;
1050 case SkBitmap::kRGB_565_Config:
1051 sig_bit.red = 5;
1052 sig_bit.green = 6;
1053 sig_bit.blue = 5;
1054 sig_bit.alpha = 0;
1055 break;
1056 default:
1057 return false;
1058 }
rmistry@google.comd6176b02012-08-23 18:14:13 +00001059
reed@android.com8a1c16f2008-12-17 15:59:43 +00001060 if (hasAlpha) {
1061 // don't specify alpha if we're a palette, even if our ctable has alpha
1062 if (!(colorType & PNG_COLOR_MASK_PALETTE)) {
1063 colorType |= PNG_COLOR_MASK_ALPHA;
1064 }
1065 } else {
1066 sig_bit.alpha = 0;
1067 }
rmistry@google.comd6176b02012-08-23 18:14:13 +00001068
reed@android.com8a1c16f2008-12-17 15:59:43 +00001069 SkAutoLockPixels alp(bitmap);
1070 // readyToDraw checks for pixels (and colortable if that is required)
1071 if (!bitmap.readyToDraw()) {
1072 return false;
1073 }
1074
1075 // we must do this after we have locked the pixels
1076 SkColorTable* ctable = bitmap.getColorTable();
1077 if (NULL != ctable) {
1078 if (ctable->count() == 0) {
1079 return false;
1080 }
1081 // check if we can store in fewer than 8 bits
1082 bitDepth = computeBitDepth(ctable->count());
1083 }
1084
tomhudson@google.com5c210c72011-07-28 21:06:40 +00001085 return doEncode(stream, bitmap, hasAlpha, colorType,
1086 bitDepth, config, sig_bit);
1087}
1088
1089bool SkPNGImageEncoder::doEncode(SkWStream* stream, const SkBitmap& bitmap,
1090 const bool& hasAlpha, int colorType,
1091 int bitDepth, SkBitmap::Config config,
1092 png_color_8& sig_bit) {
1093
reed@android.com8a1c16f2008-12-17 15:59:43 +00001094 png_structp png_ptr;
1095 png_infop info_ptr;
1096
1097 png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, sk_error_fn,
1098 NULL);
1099 if (NULL == png_ptr) {
1100 return false;
1101 }
1102
1103 info_ptr = png_create_info_struct(png_ptr);
1104 if (NULL == info_ptr) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +00001105 png_destroy_write_struct(&png_ptr, png_infopp_NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001106 return false;
1107 }
1108
1109 /* Set error handling. REQUIRED if you aren't supplying your own
1110 * error handling functions in the png_create_write_struct() call.
1111 */
1112 if (setjmp(png_jmpbuf(png_ptr))) {
1113 png_destroy_write_struct(&png_ptr, &info_ptr);
1114 return false;
1115 }
1116
commit-bot@chromium.orga936e372013-03-14 14:42:18 +00001117 png_set_write_fn(png_ptr, (void*)stream, sk_write_fn, png_flush_ptr_NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001118
1119 /* Set the image information here. Width and height are up to 2^31,
1120 * bit_depth is one of 1, 2, 4, 8, or 16, but valid values also depend on
1121 * the color_type selected. color_type is one of PNG_COLOR_TYPE_GRAY,
1122 * PNG_COLOR_TYPE_GRAY_ALPHA, PNG_COLOR_TYPE_PALETTE, PNG_COLOR_TYPE_RGB,
1123 * or PNG_COLOR_TYPE_RGB_ALPHA. interlace is either PNG_INTERLACE_NONE or
1124 * PNG_INTERLACE_ADAM7, and the compression_type and filter_type MUST
1125 * currently be PNG_COMPRESSION_TYPE_BASE and PNG_FILTER_TYPE_BASE. REQUIRED
1126 */
1127
1128 png_set_IHDR(png_ptr, info_ptr, bitmap.width(), bitmap.height(),
1129 bitDepth, colorType,
1130 PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE,
1131 PNG_FILTER_TYPE_BASE);
1132
reed@android.com61898772009-07-07 19:38:01 +00001133 // set our colortable/trans arrays if needed
1134 png_color paletteColors[256];
1135 png_byte trans[256];
1136 if (SkBitmap::kIndex8_Config == config) {
1137 SkColorTable* ct = bitmap.getColorTable();
1138 int numTrans = pack_palette(ct, paletteColors, trans, hasAlpha);
1139 png_set_PLTE(png_ptr, info_ptr, paletteColors, ct->count());
1140 if (numTrans > 0) {
1141 png_set_tRNS(png_ptr, info_ptr, trans, numTrans, NULL);
1142 }
1143 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001144
1145 png_set_sBIT(png_ptr, info_ptr, &sig_bit);
1146 png_write_info(png_ptr, info_ptr);
1147
1148 const char* srcImage = (const char*)bitmap.getPixels();
1149 SkAutoSMalloc<1024> rowStorage(bitmap.width() << 2);
1150 char* storage = (char*)rowStorage.get();
1151 transform_scanline_proc proc = choose_proc(config, hasAlpha);
1152
1153 for (int y = 0; y < bitmap.height(); y++) {
1154 png_bytep row_ptr = (png_bytep)storage;
1155 proc(srcImage, bitmap.width(), storage);
1156 png_write_rows(png_ptr, &row_ptr, 1);
1157 srcImage += bitmap.rowBytes();
1158 }
1159
1160 png_write_end(png_ptr, info_ptr);
1161
1162 /* clean up after the write, and free any memory allocated */
1163 png_destroy_write_struct(&png_ptr, &info_ptr);
1164 return true;
1165}
1166
reed@android.com00bf85a2009-01-22 13:04:56 +00001167///////////////////////////////////////////////////////////////////////////////
robertphillips@google.comec51cb82012-03-23 18:13:47 +00001168DEFINE_DECODER_CREATOR(PNGImageDecoder);
1169DEFINE_ENCODER_CREATOR(PNGImageEncoder);
1170///////////////////////////////////////////////////////////////////////////////
reed@android.com00bf85a2009-01-22 13:04:56 +00001171
scroggo@google.comb5571b32013-09-25 21:34:24 +00001172static bool is_png(SkStreamRewindable* stream) {
reed@android.com00bf85a2009-01-22 13:04:56 +00001173 char buf[PNG_BYTES_TO_CHECK];
1174 if (stream->read(buf, PNG_BYTES_TO_CHECK) == PNG_BYTES_TO_CHECK &&
1175 !png_sig_cmp((png_bytep) buf, (png_size_t)0, PNG_BYTES_TO_CHECK)) {
scroggo@google.com39edf4c2013-04-25 17:33:51 +00001176 return true;
1177 }
1178 return false;
1179}
1180
scroggo@google.comb5571b32013-09-25 21:34:24 +00001181SkImageDecoder* sk_libpng_dfactory(SkStreamRewindable* stream) {
scroggo@google.com39edf4c2013-04-25 17:33:51 +00001182 if (is_png(stream)) {
reed@android.com00bf85a2009-01-22 13:04:56 +00001183 return SkNEW(SkPNGImageDecoder);
1184 }
1185 return NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001186}
1187
scroggo@google.comb5571b32013-09-25 21:34:24 +00001188static SkImageDecoder::Format get_format_png(SkStreamRewindable* stream) {
scroggo@google.com39edf4c2013-04-25 17:33:51 +00001189 if (is_png(stream)) {
1190 return SkImageDecoder::kPNG_Format;
1191 }
1192 return SkImageDecoder::kUnknown_Format;
1193}
1194
reed@android.comdfee5792010-04-15 14:24:50 +00001195SkImageEncoder* sk_libpng_efactory(SkImageEncoder::Type t) {
reed@android.com00bf85a2009-01-22 13:04:56 +00001196 return (SkImageEncoder::kPNG_Type == t) ? SkNEW(SkPNGImageEncoder) : NULL;
1197}
1198
mtklein@google.combd6343b2013-09-04 17:20:18 +00001199static SkImageDecoder_DecodeReg gDReg(sk_libpng_dfactory);
1200static SkImageDecoder_FormatReg gFormatReg(get_format_png);
1201static SkImageEncoder_EncodeReg gEReg(sk_libpng_efactory);