blob: 7911a293f67efc953182dfcf320463b9da2e9ef7 [file] [log] [blame]
epoger@google.comec3ed6a2011-07-28 14:26:00 +00001/*
2 * Copyright 2006 The Android Open Source Project
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
reed@android.com8a1c16f2008-12-17 15:59:43 +00008#include "SkImageDecoder.h"
reed@android.comb08eb2b2009-01-06 20:16:26 +00009#include "SkImageEncoder.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000010#include "SkColor.h"
11#include "SkColorPriv.h"
12#include "SkDither.h"
13#include "SkMath.h"
halcanary@google.com2a103182013-10-14 12:49:15 +000014#include "SkRTConf.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000015#include "SkScaledBitmapSampler.h"
16#include "SkStream.h"
17#include "SkTemplates.h"
18#include "SkUtils.h"
epoger@google.com4ce738b2012-11-16 18:44:18 +000019#include "transform_scanline.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000020extern "C" {
21#include "png.h"
22}
23
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +000024/* These were dropped in libpng >= 1.4 */
25#ifndef png_infopp_NULL
26#define png_infopp_NULL NULL
27#endif
28
29#ifndef png_bytepp_NULL
30#define png_bytepp_NULL NULL
31#endif
32
33#ifndef int_p_NULL
34#define int_p_NULL NULL
35#endif
36
37#ifndef png_flush_ptr_NULL
38#define png_flush_ptr_NULL NULL
39#endif
40
halcanary@google.comfed30372013-10-04 12:46:45 +000041#if defined(SK_DEBUG)
halcanary@google.com2a103182013-10-14 12:49:15 +000042#define DEFAULT_FOR_SUPPRESS_PNG_IMAGE_DECODER_WARNINGS false
43#else // !defined(SK_DEBUG)
44#define DEFAULT_FOR_SUPPRESS_PNG_IMAGE_DECODER_WARNINGS true
halcanary@google.comfed30372013-10-04 12:46:45 +000045#endif // defined(SK_DEBUG)
halcanary@google.com2a103182013-10-14 12:49:15 +000046SK_CONF_DECLARE(bool, c_suppressPNGImageDecoderWarnings,
47 "images.png.suppressDecoderWarnings",
48 DEFAULT_FOR_SUPPRESS_PNG_IMAGE_DECODER_WARNINGS,
49 "Suppress most PNG warnings when calling image decode "
50 "functions.");
51
halcanary@google.comfed30372013-10-04 12:46:45 +000052
53
commit-bot@chromium.orga936e372013-03-14 14:42:18 +000054class SkPNGImageIndex {
55public:
scroggo@google.comb5571b32013-09-25 21:34:24 +000056 SkPNGImageIndex(SkStreamRewindable* stream, png_structp png_ptr, png_infop info_ptr)
scroggo@google.comc70a3aa2013-07-18 20:03:15 +000057 : fStream(stream)
58 , fPng_ptr(png_ptr)
59 , fInfo_ptr(info_ptr)
reed6c225732014-06-09 19:52:07 -070060 , fColorType(kUnknown_SkColorType) {
scroggo@google.comc70a3aa2013-07-18 20:03:15 +000061 SkASSERT(stream != NULL);
62 stream->ref();
commit-bot@chromium.orga936e372013-03-14 14:42:18 +000063 }
64 ~SkPNGImageIndex() {
bsalomon49f085d2014-09-05 13:34:00 -070065 if (fPng_ptr) {
scroggo@google.comc70a3aa2013-07-18 20:03:15 +000066 png_destroy_read_struct(&fPng_ptr, &fInfo_ptr, png_infopp_NULL);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +000067 }
68 }
69
scroggo@google.comb5571b32013-09-25 21:34:24 +000070 SkAutoTUnref<SkStreamRewindable> fStream;
71 png_structp fPng_ptr;
72 png_infop fInfo_ptr;
reed6c225732014-06-09 19:52:07 -070073 SkColorType fColorType;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +000074};
75
reed@android.com8a1c16f2008-12-17 15:59:43 +000076class SkPNGImageDecoder : public SkImageDecoder {
77public:
commit-bot@chromium.orga936e372013-03-14 14:42:18 +000078 SkPNGImageDecoder() {
79 fImageIndex = NULL;
80 }
81 virtual Format getFormat() const SK_OVERRIDE {
reed@android.com8a1c16f2008-12-17 15:59:43 +000082 return kPNG_Format;
83 }
scroggo@google.com39edf4c2013-04-25 17:33:51 +000084
commit-bot@chromium.orga936e372013-03-14 14:42:18 +000085 virtual ~SkPNGImageDecoder() {
86 SkDELETE(fImageIndex);
87 }
rmistry@google.comd6176b02012-08-23 18:14:13 +000088
reed@android.com8a1c16f2008-12-17 15:59:43 +000089protected:
commit-bot@chromium.orga936e372013-03-14 14:42:18 +000090#ifdef SK_BUILD_FOR_ANDROID
scroggo@google.comb5571b32013-09-25 21:34:24 +000091 virtual bool onBuildTileIndex(SkStreamRewindable *stream, int *width, int *height) SK_OVERRIDE;
scroggo@google.com7e6fcee2013-05-03 20:14:28 +000092 virtual bool onDecodeSubset(SkBitmap* bitmap, const SkIRect& region) SK_OVERRIDE;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +000093#endif
94 virtual bool onDecode(SkStream* stream, SkBitmap* bm, Mode) SK_OVERRIDE;
95
96private:
97 SkPNGImageIndex* fImageIndex;
98
99 bool onDecodeInit(SkStream* stream, png_structp *png_ptrp, png_infop *info_ptrp);
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000100 bool decodePalette(png_structp png_ptr, png_infop info_ptr,
101 bool * SK_RESTRICT hasAlphap, bool *reallyHasAlphap,
102 SkColorTable **colorTablep);
reed0689d7b2014-06-14 05:30:20 -0700103 bool getBitmapColorType(png_structp, png_infop, SkColorType*, bool* hasAlpha,
104 SkPMColor* theTranspColor);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000105
106 typedef SkImageDecoder INHERITED;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000107};
108
109#ifndef png_jmpbuf
110# define png_jmpbuf(png_ptr) ((png_ptr)->jmpbuf)
111#endif
112
113#define PNG_BYTES_TO_CHECK 4
114
115/* Automatically clean up after throwing an exception */
116struct PNGAutoClean {
117 PNGAutoClean(png_structp p, png_infop i): png_ptr(p), info_ptr(i) {}
118 ~PNGAutoClean() {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000119 png_destroy_read_struct(&png_ptr, &info_ptr, png_infopp_NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000120 }
121private:
122 png_structp png_ptr;
123 png_infop info_ptr;
124};
125
reed@android.com8a1c16f2008-12-17 15:59:43 +0000126static void sk_read_fn(png_structp png_ptr, png_bytep data, png_size_t length) {
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000127 SkStream* sk_stream = (SkStream*) png_get_io_ptr(png_ptr);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000128 size_t bytes = sk_stream->read(data, length);
129 if (bytes != length) {
130 png_error(png_ptr, "Read Error!");
131 }
132}
133
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000134#ifdef SK_BUILD_FOR_ANDROID
135static void sk_seek_fn(png_structp png_ptr, png_uint_32 offset) {
scroggo@google.comb5571b32013-09-25 21:34:24 +0000136 SkStreamRewindable* sk_stream = (SkStreamRewindable*) png_get_io_ptr(png_ptr);
scroggo@google.com4d213ab2013-08-28 13:08:54 +0000137 if (!sk_stream->rewind()) {
138 png_error(png_ptr, "Failed to rewind stream!");
139 }
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000140 (void)sk_stream->skip(offset);
141}
142#endif
143
reed@android.com8a1c16f2008-12-17 15:59:43 +0000144static int sk_read_user_chunk(png_structp png_ptr, png_unknown_chunkp chunk) {
145 SkImageDecoder::Peeker* peeker =
146 (SkImageDecoder::Peeker*)png_get_user_chunk_ptr(png_ptr);
147 // peek() returning true means continue decoding
148 return peeker->peek((const char*)chunk->name, chunk->data, chunk->size) ?
149 1 : -1;
150}
151
152static void sk_error_fn(png_structp png_ptr, png_const_charp msg) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000153 SkDEBUGF(("------ png error %s\n", msg));
reed@android.com8a1c16f2008-12-17 15:59:43 +0000154 longjmp(png_jmpbuf(png_ptr), 1);
155}
156
157static void skip_src_rows(png_structp png_ptr, uint8_t storage[], int count) {
158 for (int i = 0; i < count; i++) {
159 uint8_t* tmp = storage;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000160 png_read_rows(png_ptr, &tmp, png_bytepp_NULL, 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000161 }
162}
163
164static bool pos_le(int value, int max) {
165 return value > 0 && value <= max;
166}
167
168static bool substituteTranspColor(SkBitmap* bm, SkPMColor match) {
reed0689d7b2014-06-14 05:30:20 -0700169 SkASSERT(bm->colorType() == kN32_SkColorType);
rmistry@google.comd6176b02012-08-23 18:14:13 +0000170
reed@android.com8a1c16f2008-12-17 15:59:43 +0000171 bool reallyHasAlpha = false;
172
173 for (int y = bm->height() - 1; y >= 0; --y) {
174 SkPMColor* p = bm->getAddr32(0, y);
175 for (int x = bm->width() - 1; x >= 0; --x) {
176 if (match == *p) {
177 *p = 0;
178 reallyHasAlpha = true;
179 }
180 p += 1;
181 }
182 }
183 return reallyHasAlpha;
184}
185
reed6c225732014-06-09 19:52:07 -0700186static bool canUpscalePaletteToConfig(SkColorType dstColorType, bool srcHasAlpha) {
187 switch (dstColorType) {
188 case kN32_SkColorType:
189 case kARGB_4444_SkColorType:
reed@android.comb6137c32009-07-29 20:56:52 +0000190 return true;
reed6c225732014-06-09 19:52:07 -0700191 case kRGB_565_SkColorType:
reed@android.comb6137c32009-07-29 20:56:52 +0000192 // only return true if the src is opaque (since 565 is opaque)
193 return !srcHasAlpha;
194 default:
195 return false;
196 }
197}
198
199// call only if color_type is PALETTE. Returns true if the ctable has alpha
200static bool hasTransparencyInPalette(png_structp png_ptr, png_infop info_ptr) {
201 png_bytep trans;
202 int num_trans;
203
204 if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
205 png_get_tRNS(png_ptr, info_ptr, &trans, &num_trans, NULL);
206 return num_trans > 0;
207 }
208 return false;
reed@android.com11344262009-07-08 20:09:23 +0000209}
210
halcanary@google.comfed30372013-10-04 12:46:45 +0000211void do_nothing_warning_fn(png_structp, png_const_charp) {
212 /* do nothing */
213}
214
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000215bool SkPNGImageDecoder::onDecodeInit(SkStream* sk_stream, png_structp *png_ptrp,
216 png_infop *info_ptrp) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000217 /* Create and initialize the png_struct with the desired error handler
218 * functions. If you want to use the default stderr and longjump method,
219 * you can supply NULL for the last three parameters. We also supply the
220 * the compiler header file version, so that we know if the application
221 * was compiled with a compatible version of the library. */
halcanary@google.comfed30372013-10-04 12:46:45 +0000222
halcanary@google.comfed30372013-10-04 12:46:45 +0000223 png_error_ptr user_warning_fn =
224 (c_suppressPNGImageDecoderWarnings) ? (&do_nothing_warning_fn) : NULL;
225 /* NULL means to leave as default library behavior. */
halcanary@google.com2a103182013-10-14 12:49:15 +0000226 /* c_suppressPNGImageDecoderWarnings default depends on SK_DEBUG. */
halcanary@google.comfed30372013-10-04 12:46:45 +0000227 /* To suppress warnings with a SK_DEBUG binary, set the
228 * environment variable "skia_images_png_suppressDecoderWarnings"
229 * to "true". Inside a program that links to skia:
230 * SK_CONF_SET("images.png.suppressDecoderWarnings", true); */
halcanary@google.comfed30372013-10-04 12:46:45 +0000231
reed@android.com8a1c16f2008-12-17 15:59:43 +0000232 png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
halcanary@google.comfed30372013-10-04 12:46:45 +0000233 NULL, sk_error_fn, user_warning_fn);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000234 // png_voidp user_error_ptr, user_error_fn, user_warning_fn);
235 if (png_ptr == NULL) {
236 return false;
237 }
halcanary@google.comfed30372013-10-04 12:46:45 +0000238
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000239 *png_ptrp = png_ptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000240
241 /* Allocate/initialize the memory for image information. */
242 png_infop info_ptr = png_create_info_struct(png_ptr);
243 if (info_ptr == NULL) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000244 png_destroy_read_struct(&png_ptr, png_infopp_NULL, png_infopp_NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000245 return false;
246 }
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000247 *info_ptrp = info_ptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000248
249 /* Set error handling if you are using the setjmp/longjmp method (this is
250 * the normal method of doing things with libpng). REQUIRED unless you
251 * set up your own error handlers in the png_create_read_struct() earlier.
252 */
253 if (setjmp(png_jmpbuf(png_ptr))) {
scroggo@google.com5401cd02013-11-12 15:30:06 +0000254 png_destroy_read_struct(&png_ptr, &info_ptr, png_infopp_NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000255 return false;
256 }
257
258 /* If you are using replacement read functions, instead of calling
259 * png_init_io() here you would call:
260 */
261 png_set_read_fn(png_ptr, (void *)sk_stream, sk_read_fn);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000262#ifdef SK_BUILD_FOR_ANDROID
263 png_set_seek_fn(png_ptr, sk_seek_fn);
264#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000265 /* where user_io_ptr is a structure you want available to the callbacks */
266 /* If we have already read some of the signature */
267// png_set_sig_bytes(png_ptr, 0 /* sig_read */ );
268
269 // hookup our peeker so we can see any user-chunks the caller may be interested in
270 png_set_keep_unknown_chunks(png_ptr, PNG_HANDLE_CHUNK_ALWAYS, (png_byte*)"", 0);
271 if (this->getPeeker()) {
272 png_set_read_user_chunk_fn(png_ptr, (png_voidp)this->getPeeker(), sk_read_user_chunk);
273 }
274
275 /* The call to png_read_info() gives us all of the information from the
276 * PNG file before the first IDAT (image data chunk). */
277 png_read_info(png_ptr, info_ptr);
278 png_uint_32 origWidth, origHeight;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000279 int bitDepth, colorType;
280 png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
281 &colorType, int_p_NULL, int_p_NULL, int_p_NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000282
283 /* tell libpng to strip 16 bit/color files down to 8 bits/color */
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000284 if (bitDepth == 16) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000285 png_set_strip_16(png_ptr);
286 }
287 /* Extract multiple pixels with bit depths of 1, 2, and 4 from a single
288 * byte into separate bytes (useful for paletted and grayscale images). */
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000289 if (bitDepth < 8) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000290 png_set_packing(png_ptr);
291 }
292 /* Expand grayscale images to the full 8 bits from 1, 2, or 4 bits/pixel */
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000293 if (colorType == PNG_COLOR_TYPE_GRAY && bitDepth < 8) {
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000294 png_set_expand_gray_1_2_4_to_8(png_ptr);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000295 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000296
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000297 return true;
298}
299
300bool SkPNGImageDecoder::onDecode(SkStream* sk_stream, SkBitmap* decodedBitmap,
301 Mode mode) {
302 png_structp png_ptr;
303 png_infop info_ptr;
304
305 if (!onDecodeInit(sk_stream, &png_ptr, &info_ptr)) {
306 return false;
307 }
308
scroggo@google.comfeeca3c2013-11-12 14:38:41 +0000309 PNGAutoClean autoClean(png_ptr, info_ptr);
310
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000311 if (setjmp(png_jmpbuf(png_ptr))) {
312 return false;
313 }
314
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000315 png_uint_32 origWidth, origHeight;
reed6c225732014-06-09 19:52:07 -0700316 int bitDepth, pngColorType, interlaceType;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000317 png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
reed6c225732014-06-09 19:52:07 -0700318 &pngColorType, &interlaceType, int_p_NULL, int_p_NULL);
rmistry@google.comd6176b02012-08-23 18:14:13 +0000319
reed6c225732014-06-09 19:52:07 -0700320 SkColorType colorType;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000321 bool hasAlpha = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000322 SkPMColor theTranspColor = 0; // 0 tells us not to try to match
rmistry@google.comd6176b02012-08-23 18:14:13 +0000323
reed0689d7b2014-06-14 05:30:20 -0700324 if (!this->getBitmapColorType(png_ptr, info_ptr, &colorType, &hasAlpha, &theTranspColor)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000325 return false;
326 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000327
reed6c225732014-06-09 19:52:07 -0700328 SkAlphaType alphaType = this->getRequireUnpremultipliedColors() ?
329 kUnpremul_SkAlphaType : kPremul_SkAlphaType;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000330 const int sampleSize = this->getSampleSize();
331 SkScaledBitmapSampler sampler(origWidth, origHeight, sampleSize);
reed6c225732014-06-09 19:52:07 -0700332 decodedBitmap->setInfo(SkImageInfo::Make(sampler.scaledWidth(), sampler.scaledHeight(),
333 colorType, alphaType));
djsollen@google.com446cf712014-02-19 21:45:35 +0000334
reed@android.com8a1c16f2008-12-17 15:59:43 +0000335 if (SkImageDecoder::kDecodeBounds_Mode == mode) {
336 return true;
337 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000338
reed@android.com8a1c16f2008-12-17 15:59:43 +0000339 // from here down we are concerned with colortables and pixels
340
341 // we track if we actually see a non-opaque pixels, since sometimes a PNG sets its colortype
342 // to |= PNG_COLOR_MASK_ALPHA, but all of its pixels are in fact opaque. We care, since we
343 // draw lots faster if we can flag the bitmap has being opaque
344 bool reallyHasAlpha = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000345 SkColorTable* colorTable = NULL;
346
reed6c225732014-06-09 19:52:07 -0700347 if (pngColorType == PNG_COLOR_TYPE_PALETTE) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000348 decodePalette(png_ptr, info_ptr, &hasAlpha, &reallyHasAlpha, &colorTable);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000349 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000350
reed@android.com8a1c16f2008-12-17 15:59:43 +0000351 SkAutoUnref aur(colorTable);
352
scroggo@google.combc69ce92013-07-09 15:45:14 +0000353 if (!this->allocPixelRef(decodedBitmap,
reed6c225732014-06-09 19:52:07 -0700354 kIndex_8_SkColorType == colorType ? colorTable : NULL)) {
scroggo@google.combc69ce92013-07-09 15:45:14 +0000355 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000356 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000357
reed@android.com8a1c16f2008-12-17 15:59:43 +0000358 SkAutoLockPixels alp(*decodedBitmap);
359
reed@android.com8a1c16f2008-12-17 15:59:43 +0000360 /* Turn on interlace handling. REQUIRED if you are not using
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000361 * png_read_image(). To see how to handle interlacing passes,
362 * see the png_read_row() method below:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000363 */
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000364 const int number_passes = (interlaceType != PNG_INTERLACE_NONE) ?
365 png_set_interlace_handling(png_ptr) : 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000366
367 /* Optional call to gamma correct and add the background to the palette
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000368 * and update info structure. REQUIRED if you are expecting libpng to
369 * update the palette for you (ie you selected such a transform above).
reed@android.com8a1c16f2008-12-17 15:59:43 +0000370 */
371 png_read_update_info(png_ptr, info_ptr);
372
reed6c225732014-06-09 19:52:07 -0700373 if ((kAlpha_8_SkColorType == colorType || kIndex_8_SkColorType == colorType) &&
374 1 == sampleSize) {
375 if (kAlpha_8_SkColorType == colorType) {
scroggo@google.com5ee18dd2013-10-21 20:47:31 +0000376 // For an A8 bitmap, we assume there is an alpha for speed. It is
377 // possible the bitmap is opaque, but that is an unlikely use case
378 // since it would not be very interesting.
379 reallyHasAlpha = true;
380 // A8 is only allowed if the original was GRAY.
reed6c225732014-06-09 19:52:07 -0700381 SkASSERT(PNG_COLOR_TYPE_GRAY == pngColorType);
scroggo@google.com5ee18dd2013-10-21 20:47:31 +0000382 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000383 for (int i = 0; i < number_passes; i++) {
384 for (png_uint_32 y = 0; y < origHeight; y++) {
385 uint8_t* bmRow = decodedBitmap->getAddr8(0, y);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000386 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000387 }
388 }
389 } else {
390 SkScaledBitmapSampler::SrcConfig sc;
391 int srcBytesPerPixel = 4;
rmistry@google.comd6176b02012-08-23 18:14:13 +0000392
reed@android.com11344262009-07-08 20:09:23 +0000393 if (colorTable != NULL) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000394 sc = SkScaledBitmapSampler::kIndex;
395 srcBytesPerPixel = 1;
reed6c225732014-06-09 19:52:07 -0700396 } else if (kAlpha_8_SkColorType == colorType) {
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000397 // A8 is only allowed if the original was GRAY.
reed6c225732014-06-09 19:52:07 -0700398 SkASSERT(PNG_COLOR_TYPE_GRAY == pngColorType);
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000399 sc = SkScaledBitmapSampler::kGray;
400 srcBytesPerPixel = 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000401 } else if (hasAlpha) {
402 sc = SkScaledBitmapSampler::kRGBA;
403 } else {
404 sc = SkScaledBitmapSampler::kRGBX;
405 }
reed@android.com11344262009-07-08 20:09:23 +0000406
407 /* We have to pass the colortable explicitly, since we may have one
408 even if our decodedBitmap doesn't, due to the request that we
409 upscale png's palette to a direct model
410 */
411 SkAutoLockColors ctLock(colorTable);
scroggo@google.com8d239242013-10-01 17:27:15 +0000412 if (!sampler.begin(decodedBitmap, sc, *this, ctLock.colors())) {
reed@android.com862e91b2009-04-28 15:27:07 +0000413 return false;
414 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000415 const int height = decodedBitmap->height();
416
reed@android.com862e91b2009-04-28 15:27:07 +0000417 if (number_passes > 1) {
418 SkAutoMalloc storage(origWidth * origHeight * srcBytesPerPixel);
419 uint8_t* base = (uint8_t*)storage.get();
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000420 size_t rowBytes = origWidth * srcBytesPerPixel;
reed@android.coma8a8b8b2009-05-04 15:00:11 +0000421
reed@android.com862e91b2009-04-28 15:27:07 +0000422 for (int i = 0; i < number_passes; i++) {
423 uint8_t* row = base;
424 for (png_uint_32 y = 0; y < origHeight; y++) {
425 uint8_t* bmRow = row;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000426 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
427 row += rowBytes;
reed@android.com862e91b2009-04-28 15:27:07 +0000428 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000429 }
reed@android.com862e91b2009-04-28 15:27:07 +0000430 // now sample it
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000431 base += sampler.srcY0() * rowBytes;
reed@android.com862e91b2009-04-28 15:27:07 +0000432 for (int y = 0; y < height; y++) {
433 reallyHasAlpha |= sampler.next(base);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000434 base += sampler.srcDY() * rowBytes;
reed@android.com862e91b2009-04-28 15:27:07 +0000435 }
436 } else {
437 SkAutoMalloc storage(origWidth * srcBytesPerPixel);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000438 uint8_t* srcRow = (uint8_t*)storage.get();
439 skip_src_rows(png_ptr, srcRow, sampler.srcY0());
440
441 for (int y = 0; y < height; y++) {
442 uint8_t* tmp = srcRow;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000443 png_read_rows(png_ptr, &tmp, png_bytepp_NULL, 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000444 reallyHasAlpha |= sampler.next(srcRow);
445 if (y < height - 1) {
446 skip_src_rows(png_ptr, srcRow, sampler.srcDY() - 1);
447 }
448 }
reed@android.com862e91b2009-04-28 15:27:07 +0000449
reed@android.com8a1c16f2008-12-17 15:59:43 +0000450 // skip the rest of the rows (if any)
451 png_uint_32 read = (height - 1) * sampler.srcDY() +
452 sampler.srcY0() + 1;
453 SkASSERT(read <= origHeight);
454 skip_src_rows(png_ptr, srcRow, origHeight - read);
455 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000456 }
457
458 /* read rest of file, and get additional chunks in info_ptr - REQUIRED */
459 png_read_end(png_ptr, info_ptr);
460
461 if (0 != theTranspColor) {
462 reallyHasAlpha |= substituteTranspColor(decodedBitmap, theTranspColor);
463 }
scroggo@google.com5ee18dd2013-10-21 20:47:31 +0000464 if (reallyHasAlpha && this->getRequireUnpremultipliedColors()) {
reed0689d7b2014-06-14 05:30:20 -0700465 switch (decodedBitmap->colorType()) {
466 case kIndex_8_SkColorType:
scroggo@google.com5ee18dd2013-10-21 20:47:31 +0000467 // Fall through.
reed0689d7b2014-06-14 05:30:20 -0700468 case kARGB_4444_SkColorType:
469 // We have chosen not to support unpremul for these colortypes.
scroggo@google.com5ee18dd2013-10-21 20:47:31 +0000470 return false;
471 default: {
reed0689d7b2014-06-14 05:30:20 -0700472 // Fall through to finish the decode. This colortype either
scroggo@google.com5ee18dd2013-10-21 20:47:31 +0000473 // supports unpremul or it is irrelevant because it has no
474 // alpha (or only alpha).
475 // These brackets prevent a warning.
476 }
477 }
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000478 }
scroggo@google.com5ee18dd2013-10-21 20:47:31 +0000479
djsollen@google.com446cf712014-02-19 21:45:35 +0000480 if (!reallyHasAlpha) {
481 decodedBitmap->setAlphaType(kOpaque_SkAlphaType);
reed@google.com383a6972013-10-21 14:00:07 +0000482 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000483 return true;
484}
485
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000486
487
reed0689d7b2014-06-14 05:30:20 -0700488bool SkPNGImageDecoder::getBitmapColorType(png_structp png_ptr, png_infop info_ptr,
489 SkColorType* colorTypep,
490 bool* hasAlphap,
491 SkPMColor* SK_RESTRICT theTranspColorp) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000492 png_uint_32 origWidth, origHeight;
493 int bitDepth, colorType;
494 png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
495 &colorType, int_p_NULL, int_p_NULL, int_p_NULL);
496
497 // check for sBIT chunk data, in case we should disable dithering because
498 // our data is not truely 8bits per component
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000499 png_color_8p sig_bit;
scroggo@google.com8d239242013-10-01 17:27:15 +0000500 if (this->getDitherImage() && png_get_sBIT(png_ptr, info_ptr, &sig_bit)) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000501#if 0
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000502 SkDebugf("----- sBIT %d %d %d %d\n", sig_bit->red, sig_bit->green,
503 sig_bit->blue, sig_bit->alpha);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000504#endif
505 // 0 seems to indicate no information available
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000506 if (pos_le(sig_bit->red, SK_R16_BITS) &&
507 pos_le(sig_bit->green, SK_G16_BITS) &&
508 pos_le(sig_bit->blue, SK_B16_BITS)) {
scroggo@google.com8d239242013-10-01 17:27:15 +0000509 this->setDitherImage(false);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000510 }
511 }
512
513 if (colorType == PNG_COLOR_TYPE_PALETTE) {
514 bool paletteHasAlpha = hasTransparencyInPalette(png_ptr, info_ptr);
reed6c225732014-06-09 19:52:07 -0700515 *colorTypep = this->getPrefColorType(kIndex_SrcDepth, paletteHasAlpha);
reed0689d7b2014-06-14 05:30:20 -0700516 // now see if we can upscale to their requested colortype
reed6c225732014-06-09 19:52:07 -0700517 if (!canUpscalePaletteToConfig(*colorTypep, paletteHasAlpha)) {
518 *colorTypep = kIndex_8_SkColorType;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000519 }
520 } else {
521 png_color_16p transpColor = NULL;
522 int numTransp = 0;
523
524 png_get_tRNS(png_ptr, info_ptr, NULL, &numTransp, &transpColor);
525
526 bool valid = png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS);
527
528 if (valid && numTransp == 1 && transpColor != NULL) {
529 /* Compute our transparent color, which we'll match against later.
530 We don't really handle 16bit components properly here, since we
531 do our compare *after* the values have been knocked down to 8bit
532 which means we will find more matches than we should. The real
533 fix seems to be to see the actual 16bit components, do the
534 compare, and then knock it down to 8bits ourselves.
535 */
536 if (colorType & PNG_COLOR_MASK_COLOR) {
537 if (16 == bitDepth) {
538 *theTranspColorp = SkPackARGB32(0xFF, transpColor->red >> 8,
539 transpColor->green >> 8,
540 transpColor->blue >> 8);
541 } else {
halcanary@google.comfed30372013-10-04 12:46:45 +0000542 /* We apply the mask because in a very small
543 number of corrupt PNGs, (transpColor->red > 255)
544 and (bitDepth == 8), for certain versions of libpng. */
545 *theTranspColorp = SkPackARGB32(0xFF,
546 0xFF & (transpColor->red),
547 0xFF & (transpColor->green),
548 0xFF & (transpColor->blue));
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000549 }
550 } else { // gray
551 if (16 == bitDepth) {
552 *theTranspColorp = SkPackARGB32(0xFF, transpColor->gray >> 8,
553 transpColor->gray >> 8,
554 transpColor->gray >> 8);
555 } else {
halcanary@google.comfed30372013-10-04 12:46:45 +0000556 /* We apply the mask because in a very small
557 number of corrupt PNGs, (transpColor->red >
558 255) and (bitDepth == 8), for certain versions
559 of libpng. For safety we assume the same could
560 happen with a grayscale PNG. */
561 *theTranspColorp = SkPackARGB32(0xFF,
562 0xFF & (transpColor->gray),
563 0xFF & (transpColor->gray),
564 0xFF & (transpColor->gray));
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000565 }
566 }
567 }
568
569 if (valid ||
570 PNG_COLOR_TYPE_RGB_ALPHA == colorType ||
571 PNG_COLOR_TYPE_GRAY_ALPHA == colorType) {
572 *hasAlphap = true;
573 }
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000574
575 SrcDepth srcDepth = k32Bit_SrcDepth;
576 if (PNG_COLOR_TYPE_GRAY == colorType) {
577 srcDepth = k8BitGray_SrcDepth;
scroggo@google.com8e2ef012013-07-18 20:14:45 +0000578 // Remove this assert, which fails on desk_pokemonwiki.skp
579 //SkASSERT(!*hasAlphap);
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000580 }
581
reed6c225732014-06-09 19:52:07 -0700582 *colorTypep = this->getPrefColorType(srcDepth, *hasAlphap);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000583 // now match the request against our capabilities
584 if (*hasAlphap) {
reed6c225732014-06-09 19:52:07 -0700585 if (*colorTypep != kARGB_4444_SkColorType) {
586 *colorTypep = kN32_SkColorType;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000587 }
588 } else {
reed6c225732014-06-09 19:52:07 -0700589 if (kAlpha_8_SkColorType == *colorTypep) {
scroggo@google.com354fd972013-10-02 15:50:19 +0000590 if (k8BitGray_SrcDepth != srcDepth) {
591 // Converting a non grayscale image to A8 is not currently supported.
reed6c225732014-06-09 19:52:07 -0700592 *colorTypep = kN32_SkColorType;
scroggo@google.com354fd972013-10-02 15:50:19 +0000593 }
reed6c225732014-06-09 19:52:07 -0700594 } else if (*colorTypep != kRGB_565_SkColorType &&
595 *colorTypep != kARGB_4444_SkColorType) {
596 *colorTypep = kN32_SkColorType;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000597 }
598 }
599 }
600
601 // sanity check for size
602 {
reed@google.com57212f92013-12-30 14:40:38 +0000603 int64_t size = sk_64_mul(origWidth, origHeight);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000604 // now check that if we are 4-bytes per pixel, we also don't overflow
reed@google.com57212f92013-12-30 14:40:38 +0000605 if (size < 0 || size > (0x7FFFFFFF >> 2)) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000606 return false;
607 }
608 }
609
reed5926b862014-06-11 10:33:13 -0700610#ifdef SK_SUPPORT_LEGACY_IMAGEDECODER_CHOOSER
reed6c225732014-06-09 19:52:07 -0700611 if (!this->chooseFromOneChoice(*colorTypep, origWidth, origHeight)) {
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000612 return false;
613 }
reed5926b862014-06-11 10:33:13 -0700614#endif
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000615
616 // If the image has alpha and the decoder wants unpremultiplied
reed0689d7b2014-06-14 05:30:20 -0700617 // colors, the only supported colortype is 8888.
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000618 if (this->getRequireUnpremultipliedColors() && *hasAlphap) {
reed6c225732014-06-09 19:52:07 -0700619 *colorTypep = kN32_SkColorType;
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000620 }
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000621
622 if (fImageIndex != NULL) {
reed6c225732014-06-09 19:52:07 -0700623 if (kUnknown_SkColorType == fImageIndex->fColorType) {
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000624 // This is the first time for this subset decode. From now on,
reed0689d7b2014-06-14 05:30:20 -0700625 // all decodes must be in the same colortype.
reed6c225732014-06-09 19:52:07 -0700626 fImageIndex->fColorType = *colorTypep;
627 } else if (fImageIndex->fColorType != *colorTypep) {
628 // Requesting a different colortype for a subsequent decode is not
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000629 // supported. Report failure before we make changes to png_ptr.
630 return false;
631 }
632 }
633
reed6c225732014-06-09 19:52:07 -0700634 bool convertGrayToRGB = PNG_COLOR_TYPE_GRAY == colorType && *colorTypep != kAlpha_8_SkColorType;
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000635
636 // Unless the user is requesting A8, convert a grayscale image into RGB.
637 // GRAY_ALPHA will always be converted to RGB
638 if (convertGrayToRGB || colorType == PNG_COLOR_TYPE_GRAY_ALPHA) {
639 png_set_gray_to_rgb(png_ptr);
640 }
641
642 // Add filler (or alpha) byte (after each RGB triplet) if necessary.
643 if (colorType == PNG_COLOR_TYPE_RGB || convertGrayToRGB) {
644 png_set_filler(png_ptr, 0xff, PNG_FILLER_AFTER);
645 }
646
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000647 return true;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000648}
649
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000650typedef uint32_t (*PackColorProc)(U8CPU a, U8CPU r, U8CPU g, U8CPU b);
651
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000652bool SkPNGImageDecoder::decodePalette(png_structp png_ptr, png_infop info_ptr,
653 bool *hasAlphap, bool *reallyHasAlphap,
654 SkColorTable **colorTablep) {
655 int numPalette;
656 png_colorp palette;
657 png_bytep trans;
658 int numTrans;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000659
660 png_get_PLTE(png_ptr, info_ptr, &palette, &numPalette);
661
662 /* BUGGY IMAGE WORKAROUND
663
664 We hit some images (e.g. fruit_.png) who contain bytes that are == colortable_count
665 which is a problem since we use the byte as an index. To work around this we grow
666 the colortable by 1 (if its < 256) and duplicate the last color into that slot.
667 */
668 int colorCount = numPalette + (numPalette < 256);
reed@google.com0a6151d2013-10-10 14:44:56 +0000669 SkPMColor colorStorage[256]; // worst-case storage
670 SkPMColor* colorPtr = colorStorage;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000671
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000672 if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
673 png_get_tRNS(png_ptr, info_ptr, &trans, &numTrans, NULL);
674 *hasAlphap = (numTrans > 0);
675 } else {
676 numTrans = 0;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000677 }
reed@google.com0a6151d2013-10-10 14:44:56 +0000678
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000679 // check for bad images that might make us crash
680 if (numTrans > numPalette) {
681 numTrans = numPalette;
682 }
683
684 int index = 0;
685 int transLessThanFF = 0;
686
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000687 // Choose which function to use to create the color table. If the final destination's
reed0689d7b2014-06-14 05:30:20 -0700688 // colortype is unpremultiplied, the color table will store unpremultiplied colors.
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000689 PackColorProc proc;
690 if (this->getRequireUnpremultipliedColors()) {
691 proc = &SkPackARGB32NoCheck;
692 } else {
693 proc = &SkPreMultiplyARGB;
694 }
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000695 for (; index < numTrans; index++) {
696 transLessThanFF |= (int)*trans - 0xFF;
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000697 *colorPtr++ = proc(*trans++, palette->red, palette->green, palette->blue);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000698 palette++;
699 }
reed@google.com0a6151d2013-10-10 14:44:56 +0000700 bool reallyHasAlpha = (transLessThanFF < 0);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000701
702 for (; index < numPalette; index++) {
703 *colorPtr++ = SkPackARGB32(0xFF, palette->red, palette->green, palette->blue);
704 palette++;
705 }
706
707 // see BUGGY IMAGE WORKAROUND comment above
708 if (numPalette < 256) {
709 *colorPtr = colorPtr[-1];
710 }
reed@google.com0a6151d2013-10-10 14:44:56 +0000711
reedc5e15a12014-09-29 12:10:27 -0700712 *colorTablep = SkNEW_ARGS(SkColorTable, (colorStorage, colorCount));
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000713 *reallyHasAlphap = reallyHasAlpha;
714 return true;
715}
716
717#ifdef SK_BUILD_FOR_ANDROID
718
scroggo@google.comb5571b32013-09-25 21:34:24 +0000719bool SkPNGImageDecoder::onBuildTileIndex(SkStreamRewindable* sk_stream, int *width, int *height) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000720 png_structp png_ptr;
721 png_infop info_ptr;
722
723 if (!onDecodeInit(sk_stream, &png_ptr, &info_ptr)) {
724 return false;
725 }
726
727 if (setjmp(png_jmpbuf(png_ptr)) != 0) {
728 png_destroy_read_struct(&png_ptr, &info_ptr, png_infopp_NULL);
729 return false;
730 }
731
732 png_uint_32 origWidth, origHeight;
733 int bitDepth, colorType;
734 png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
735 &colorType, int_p_NULL, int_p_NULL, int_p_NULL);
736
737 *width = origWidth;
738 *height = origHeight;
739
740 png_build_index(png_ptr);
741
742 if (fImageIndex) {
743 SkDELETE(fImageIndex);
744 }
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000745 fImageIndex = SkNEW_ARGS(SkPNGImageIndex, (sk_stream, png_ptr, info_ptr));
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000746
747 return true;
748}
749
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000750bool SkPNGImageDecoder::onDecodeSubset(SkBitmap* bm, const SkIRect& region) {
751 if (NULL == fImageIndex) {
752 return false;
753 }
754
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000755 png_structp png_ptr = fImageIndex->fPng_ptr;
756 png_infop info_ptr = fImageIndex->fInfo_ptr;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000757 if (setjmp(png_jmpbuf(png_ptr))) {
758 return false;
759 }
760
761 png_uint_32 origWidth, origHeight;
reed6c225732014-06-09 19:52:07 -0700762 int bitDepth, pngColorType, interlaceType;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000763 png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
reed6c225732014-06-09 19:52:07 -0700764 &pngColorType, &interlaceType, int_p_NULL, int_p_NULL);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000765
766 SkIRect rect = SkIRect::MakeWH(origWidth, origHeight);
767
768 if (!rect.intersect(region)) {
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000769 // If the requested region is entirely outside the image, just
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000770 // returns false
771 return false;
772 }
773
reed6c225732014-06-09 19:52:07 -0700774 SkColorType colorType;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000775 bool hasAlpha = false;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000776 SkPMColor theTranspColor = 0; // 0 tells us not to try to match
777
reed0689d7b2014-06-14 05:30:20 -0700778 if (!this->getBitmapColorType(png_ptr, info_ptr, &colorType, &hasAlpha, &theTranspColor)) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000779 return false;
780 }
781
782 const int sampleSize = this->getSampleSize();
783 SkScaledBitmapSampler sampler(origWidth, rect.height(), sampleSize);
784
785 SkBitmap decodedBitmap;
reed6c225732014-06-09 19:52:07 -0700786 decodedBitmap.setInfo(SkImageInfo::Make(sampler.scaledWidth(), sampler.scaledHeight(),
787 colorType, kPremul_SkAlphaType));
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000788
789 // from here down we are concerned with colortables and pixels
790
791 // we track if we actually see a non-opaque pixels, since sometimes a PNG sets its colortype
792 // to |= PNG_COLOR_MASK_ALPHA, but all of its pixels are in fact opaque. We care, since we
793 // draw lots faster if we can flag the bitmap has being opaque
794 bool reallyHasAlpha = false;
795 SkColorTable* colorTable = NULL;
796
reed6c225732014-06-09 19:52:07 -0700797 if (pngColorType == PNG_COLOR_TYPE_PALETTE) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000798 decodePalette(png_ptr, info_ptr, &hasAlpha, &reallyHasAlpha, &colorTable);
799 }
800
801 SkAutoUnref aur(colorTable);
802
803 // Check ahead of time if the swap(dest, src) is possible.
804 // If yes, then we will stick to AllocPixelRef since it's cheaper with the swap happening.
805 // If no, then we will use alloc to allocate pixels to prevent garbage collection.
806 int w = rect.width() / sampleSize;
807 int h = rect.height() / sampleSize;
808 const bool swapOnly = (rect == region) && (w == decodedBitmap.width()) &&
809 (h == decodedBitmap.height()) && bm->isNull();
reed6c225732014-06-09 19:52:07 -0700810 const bool needColorTable = kIndex_8_SkColorType == colorType;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000811 if (swapOnly) {
812 if (!this->allocPixelRef(&decodedBitmap, needColorTable ? colorTable : NULL)) {
813 return false;
814 }
815 } else {
reed84825042014-09-02 12:50:45 -0700816 if (!decodedBitmap.tryAllocPixels(NULL, needColorTable ? colorTable : NULL)) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000817 return false;
818 }
819 }
820 SkAutoLockPixels alp(decodedBitmap);
821
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000822 /* Turn on interlace handling. REQUIRED if you are not using
823 * png_read_image(). To see how to handle interlacing passes,
824 * see the png_read_row() method below:
825 */
826 const int number_passes = (interlaceType != PNG_INTERLACE_NONE) ?
827 png_set_interlace_handling(png_ptr) : 1;
828
829 /* Optional call to gamma correct and add the background to the palette
830 * and update info structure. REQUIRED if you are expecting libpng to
831 * update the palette for you (ie you selected such a transform above).
832 */
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000833
834 // Direct access to png_ptr fields is deprecated in libpng > 1.2.
835#if defined(PNG_1_0_X) || defined (PNG_1_2_X)
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000836 png_ptr->pass = 0;
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000837#else
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000838 // FIXME: This sets pass as desired, but also sets iwidth. Is that ok?
839 png_set_interlaced_pass(png_ptr, 0);
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000840#endif
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000841 png_read_update_info(png_ptr, info_ptr);
842
843 int actualTop = rect.fTop;
844
reed6c225732014-06-09 19:52:07 -0700845 if ((kAlpha_8_SkColorType == colorType || kIndex_8_SkColorType == colorType)
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000846 && 1 == sampleSize) {
reed6c225732014-06-09 19:52:07 -0700847 if (kAlpha_8_SkColorType == colorType) {
scroggo@google.com5ee18dd2013-10-21 20:47:31 +0000848 // For an A8 bitmap, we assume there is an alpha for speed. It is
849 // possible the bitmap is opaque, but that is an unlikely use case
850 // since it would not be very interesting.
851 reallyHasAlpha = true;
852 // A8 is only allowed if the original was GRAY.
reed6c225732014-06-09 19:52:07 -0700853 SkASSERT(PNG_COLOR_TYPE_GRAY == pngColorType);
scroggo@google.com5ee18dd2013-10-21 20:47:31 +0000854 }
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000855
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000856 for (int i = 0; i < number_passes; i++) {
857 png_configure_decoder(png_ptr, &actualTop, i);
858 for (int j = 0; j < rect.fTop - actualTop; j++) {
859 uint8_t* bmRow = decodedBitmap.getAddr8(0, 0);
860 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
861 }
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000862 png_uint_32 bitmapHeight = (png_uint_32) decodedBitmap.height();
863 for (png_uint_32 y = 0; y < bitmapHeight; y++) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000864 uint8_t* bmRow = decodedBitmap.getAddr8(0, y);
865 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
866 }
867 }
868 } else {
869 SkScaledBitmapSampler::SrcConfig sc;
870 int srcBytesPerPixel = 4;
871
872 if (colorTable != NULL) {
873 sc = SkScaledBitmapSampler::kIndex;
874 srcBytesPerPixel = 1;
reed6c225732014-06-09 19:52:07 -0700875 } else if (kAlpha_8_SkColorType == colorType) {
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000876 // A8 is only allowed if the original was GRAY.
reed6c225732014-06-09 19:52:07 -0700877 SkASSERT(PNG_COLOR_TYPE_GRAY == pngColorType);
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000878 sc = SkScaledBitmapSampler::kGray;
879 srcBytesPerPixel = 1;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000880 } else if (hasAlpha) {
881 sc = SkScaledBitmapSampler::kRGBA;
882 } else {
883 sc = SkScaledBitmapSampler::kRGBX;
884 }
885
886 /* We have to pass the colortable explicitly, since we may have one
887 even if our decodedBitmap doesn't, due to the request that we
888 upscale png's palette to a direct model
889 */
890 SkAutoLockColors ctLock(colorTable);
scroggo@google.com8d239242013-10-01 17:27:15 +0000891 if (!sampler.begin(&decodedBitmap, sc, *this, ctLock.colors())) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000892 return false;
893 }
894 const int height = decodedBitmap.height();
895
896 if (number_passes > 1) {
897 SkAutoMalloc storage(origWidth * origHeight * srcBytesPerPixel);
898 uint8_t* base = (uint8_t*)storage.get();
899 size_t rb = origWidth * srcBytesPerPixel;
900
901 for (int i = 0; i < number_passes; i++) {
902 png_configure_decoder(png_ptr, &actualTop, i);
903 for (int j = 0; j < rect.fTop - actualTop; j++) {
scroggofc7063b2014-07-25 13:54:43 -0700904 png_read_rows(png_ptr, &base, png_bytepp_NULL, 1);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000905 }
906 uint8_t* row = base;
907 for (int32_t y = 0; y < rect.height(); y++) {
908 uint8_t* bmRow = row;
909 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
910 row += rb;
911 }
912 }
913 // now sample it
914 base += sampler.srcY0() * rb;
915 for (int y = 0; y < height; y++) {
916 reallyHasAlpha |= sampler.next(base);
917 base += sampler.srcDY() * rb;
918 }
919 } else {
920 SkAutoMalloc storage(origWidth * srcBytesPerPixel);
921 uint8_t* srcRow = (uint8_t*)storage.get();
922
923 png_configure_decoder(png_ptr, &actualTop, 0);
924 skip_src_rows(png_ptr, srcRow, sampler.srcY0());
925
926 for (int i = 0; i < rect.fTop - actualTop; i++) {
scroggofc7063b2014-07-25 13:54:43 -0700927 png_read_rows(png_ptr, &srcRow, png_bytepp_NULL, 1);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000928 }
929 for (int y = 0; y < height; y++) {
930 uint8_t* tmp = srcRow;
931 png_read_rows(png_ptr, &tmp, png_bytepp_NULL, 1);
932 reallyHasAlpha |= sampler.next(srcRow);
933 if (y < height - 1) {
934 skip_src_rows(png_ptr, srcRow, sampler.srcDY() - 1);
935 }
936 }
937 }
938 }
939
940 if (0 != theTranspColor) {
941 reallyHasAlpha |= substituteTranspColor(&decodedBitmap, theTranspColor);
942 }
scroggo@google.com5ee18dd2013-10-21 20:47:31 +0000943 if (reallyHasAlpha && this->getRequireUnpremultipliedColors()) {
reed6c225732014-06-09 19:52:07 -0700944 switch (decodedBitmap.colorType()) {
945 case kIndex_8_SkColorType:
scroggo@google.com5ee18dd2013-10-21 20:47:31 +0000946 // Fall through.
reed6c225732014-06-09 19:52:07 -0700947 case kARGB_4444_SkColorType:
948 // We have chosen not to support unpremul for these colortypess.
scroggo@google.com5ee18dd2013-10-21 20:47:31 +0000949 return false;
950 default: {
951 // Fall through to finish the decode. This config either
952 // supports unpremul or it is irrelevant because it has no
953 // alpha (or only alpha).
954 // These brackets prevent a warning.
955 }
956 }
commit-bot@chromium.org546f70c2013-10-03 17:13:38 +0000957 }
reed@google.com383a6972013-10-21 14:00:07 +0000958 SkAlphaType alphaType = kOpaque_SkAlphaType;
959 if (reallyHasAlpha) {
960 if (this->getRequireUnpremultipliedColors()) {
961 alphaType = kUnpremul_SkAlphaType;
962 } else {
963 alphaType = kPremul_SkAlphaType;
964 }
965 }
966 decodedBitmap.setAlphaType(alphaType);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000967
968 if (swapOnly) {
969 bm->swap(decodedBitmap);
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000970 return true;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000971 }
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000972 return this->cropBitmap(bm, &decodedBitmap, sampleSize, region.x(), region.y(),
973 region.width(), region.height(), 0, rect.y());
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000974}
975#endif
976
reed@android.com8a1c16f2008-12-17 15:59:43 +0000977///////////////////////////////////////////////////////////////////////////////
978
reed@android.com8a1c16f2008-12-17 15:59:43 +0000979#include "SkColorPriv.h"
980#include "SkUnPreMultiply.h"
981
982static void sk_write_fn(png_structp png_ptr, png_bytep data, png_size_t len) {
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000983 SkWStream* sk_stream = (SkWStream*)png_get_io_ptr(png_ptr);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000984 if (!sk_stream->write(data, len)) {
985 png_error(png_ptr, "sk_write_fn Error!");
986 }
987}
988
reed0689d7b2014-06-14 05:30:20 -0700989static transform_scanline_proc choose_proc(SkColorType ct, bool hasAlpha) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000990 // we don't care about search on alpha if we're kIndex8, since only the
991 // colortable packing cares about that distinction, not the pixels
reed0689d7b2014-06-14 05:30:20 -0700992 if (kIndex_8_SkColorType == ct) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000993 hasAlpha = false; // we store false in the table entries for kIndex8
994 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000995
reed@android.com8a1c16f2008-12-17 15:59:43 +0000996 static const struct {
reed0689d7b2014-06-14 05:30:20 -0700997 SkColorType fColorType;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000998 bool fHasAlpha;
999 transform_scanline_proc fProc;
1000 } gMap[] = {
reed0689d7b2014-06-14 05:30:20 -07001001 { kRGB_565_SkColorType, false, transform_scanline_565 },
1002 { kN32_SkColorType, false, transform_scanline_888 },
1003 { kN32_SkColorType, true, transform_scanline_8888 },
1004 { kARGB_4444_SkColorType, false, transform_scanline_444 },
1005 { kARGB_4444_SkColorType, true, transform_scanline_4444 },
1006 { kIndex_8_SkColorType, false, transform_scanline_memcpy },
reed@android.com8a1c16f2008-12-17 15:59:43 +00001007 };
1008
1009 for (int i = SK_ARRAY_COUNT(gMap) - 1; i >= 0; --i) {
reed0689d7b2014-06-14 05:30:20 -07001010 if (gMap[i].fColorType == ct && gMap[i].fHasAlpha == hasAlpha) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001011 return gMap[i].fProc;
1012 }
1013 }
1014 sk_throw();
1015 return NULL;
1016}
1017
1018// return the minimum legal bitdepth (by png standards) for this many colortable
1019// entries. SkBitmap always stores in 8bits per pixel, but for colorcount <= 16,
1020// we can use fewer bits per in png
1021static int computeBitDepth(int colorCount) {
1022#if 0
1023 int bits = SkNextLog2(colorCount);
1024 SkASSERT(bits >= 1 && bits <= 8);
1025 // now we need bits itself to be a power of 2 (e.g. 1, 2, 4, 8)
1026 return SkNextPow2(bits);
1027#else
1028 // for the moment, we don't know how to pack bitdepth < 8
1029 return 8;
1030#endif
1031}
1032
1033/* Pack palette[] with the corresponding colors, and if hasAlpha is true, also
1034 pack trans[] and return the number of trans[] entries written. If hasAlpha
1035 is false, the return value will always be 0.
rmistry@google.comd6176b02012-08-23 18:14:13 +00001036
reed@android.com8a1c16f2008-12-17 15:59:43 +00001037 Note: this routine takes care of unpremultiplying the RGB values when we
1038 have alpha in the colortable, since png doesn't support premul colors
1039*/
reed@android.com6f252972009-01-14 16:46:16 +00001040static inline int pack_palette(SkColorTable* ctable,
reed@android.comb50a60c2009-01-14 17:51:08 +00001041 png_color* SK_RESTRICT palette,
1042 png_byte* SK_RESTRICT trans, bool hasAlpha) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001043 SkAutoLockColors alc(ctable);
1044 const SkPMColor* SK_RESTRICT colors = alc.colors();
1045 const int ctCount = ctable->count();
1046 int i, num_trans = 0;
1047
1048 if (hasAlpha) {
1049 /* first see if we have some number of fully opaque at the end of the
1050 ctable. PNG allows num_trans < num_palette, but all of the trans
1051 entries must come first in the palette. If I was smarter, I'd
1052 reorder the indices and ctable so that all non-opaque colors came
1053 first in the palette. But, since that would slow down the encode,
1054 I'm leaving the indices and ctable order as is, and just looking
1055 at the tail of the ctable for opaqueness.
1056 */
1057 num_trans = ctCount;
1058 for (i = ctCount - 1; i >= 0; --i) {
1059 if (SkGetPackedA32(colors[i]) != 0xFF) {
1060 break;
1061 }
1062 num_trans -= 1;
1063 }
rmistry@google.comd6176b02012-08-23 18:14:13 +00001064
reed@android.com8a1c16f2008-12-17 15:59:43 +00001065 const SkUnPreMultiply::Scale* SK_RESTRICT table =
1066 SkUnPreMultiply::GetScaleTable();
1067
1068 for (i = 0; i < num_trans; i++) {
1069 const SkPMColor c = *colors++;
1070 const unsigned a = SkGetPackedA32(c);
1071 const SkUnPreMultiply::Scale s = table[a];
1072 trans[i] = a;
1073 palette[i].red = SkUnPreMultiply::ApplyScale(s, SkGetPackedR32(c));
1074 palette[i].green = SkUnPreMultiply::ApplyScale(s,SkGetPackedG32(c));
1075 palette[i].blue = SkUnPreMultiply::ApplyScale(s, SkGetPackedB32(c));
rmistry@google.comd6176b02012-08-23 18:14:13 +00001076 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001077 // now fall out of this if-block to use common code for the trailing
1078 // opaque entries
1079 }
rmistry@google.comd6176b02012-08-23 18:14:13 +00001080
reed@android.com8a1c16f2008-12-17 15:59:43 +00001081 // these (remaining) entries are opaque
1082 for (i = num_trans; i < ctCount; i++) {
1083 SkPMColor c = *colors++;
1084 palette[i].red = SkGetPackedR32(c);
1085 palette[i].green = SkGetPackedG32(c);
1086 palette[i].blue = SkGetPackedB32(c);
1087 }
1088 return num_trans;
1089}
1090
1091class SkPNGImageEncoder : public SkImageEncoder {
1092protected:
commit-bot@chromium.orga936e372013-03-14 14:42:18 +00001093 virtual bool onEncode(SkWStream* stream, const SkBitmap& bm, int quality) SK_OVERRIDE;
tomhudson@google.com5c210c72011-07-28 21:06:40 +00001094private:
1095 bool doEncode(SkWStream* stream, const SkBitmap& bm,
1096 const bool& hasAlpha, int colorType,
reed0689d7b2014-06-14 05:30:20 -07001097 int bitDepth, SkColorType ct,
tomhudson@google.com5c210c72011-07-28 21:06:40 +00001098 png_color_8& sig_bit);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +00001099
1100 typedef SkImageEncoder INHERITED;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001101};
1102
reed0689d7b2014-06-14 05:30:20 -07001103bool SkPNGImageEncoder::onEncode(SkWStream* stream, const SkBitmap& bitmap, int /*quality*/) {
1104 SkColorType ct = bitmap.colorType();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001105
1106 const bool hasAlpha = !bitmap.isOpaque();
1107 int colorType = PNG_COLOR_MASK_COLOR;
1108 int bitDepth = 8; // default for color
1109 png_color_8 sig_bit;
1110
reed0689d7b2014-06-14 05:30:20 -07001111 switch (ct) {
1112 case kIndex_8_SkColorType:
reed@android.com8a1c16f2008-12-17 15:59:43 +00001113 colorType |= PNG_COLOR_MASK_PALETTE;
1114 // fall through to the ARGB_8888 case
reed0689d7b2014-06-14 05:30:20 -07001115 case kN32_SkColorType:
reed@android.com8a1c16f2008-12-17 15:59:43 +00001116 sig_bit.red = 8;
1117 sig_bit.green = 8;
1118 sig_bit.blue = 8;
1119 sig_bit.alpha = 8;
1120 break;
reed0689d7b2014-06-14 05:30:20 -07001121 case kARGB_4444_SkColorType:
reed@android.com8a1c16f2008-12-17 15:59:43 +00001122 sig_bit.red = 4;
1123 sig_bit.green = 4;
1124 sig_bit.blue = 4;
1125 sig_bit.alpha = 4;
1126 break;
reed0689d7b2014-06-14 05:30:20 -07001127 case kRGB_565_SkColorType:
reed@android.com8a1c16f2008-12-17 15:59:43 +00001128 sig_bit.red = 5;
1129 sig_bit.green = 6;
1130 sig_bit.blue = 5;
1131 sig_bit.alpha = 0;
1132 break;
1133 default:
1134 return false;
1135 }
rmistry@google.comd6176b02012-08-23 18:14:13 +00001136
reed@android.com8a1c16f2008-12-17 15:59:43 +00001137 if (hasAlpha) {
1138 // don't specify alpha if we're a palette, even if our ctable has alpha
1139 if (!(colorType & PNG_COLOR_MASK_PALETTE)) {
1140 colorType |= PNG_COLOR_MASK_ALPHA;
1141 }
1142 } else {
1143 sig_bit.alpha = 0;
1144 }
rmistry@google.comd6176b02012-08-23 18:14:13 +00001145
reed@android.com8a1c16f2008-12-17 15:59:43 +00001146 SkAutoLockPixels alp(bitmap);
1147 // readyToDraw checks for pixels (and colortable if that is required)
1148 if (!bitmap.readyToDraw()) {
1149 return false;
1150 }
1151
1152 // we must do this after we have locked the pixels
1153 SkColorTable* ctable = bitmap.getColorTable();
bsalomon49f085d2014-09-05 13:34:00 -07001154 if (ctable) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001155 if (ctable->count() == 0) {
1156 return false;
1157 }
1158 // check if we can store in fewer than 8 bits
1159 bitDepth = computeBitDepth(ctable->count());
1160 }
1161
reed0689d7b2014-06-14 05:30:20 -07001162 return doEncode(stream, bitmap, hasAlpha, colorType, bitDepth, ct, sig_bit);
tomhudson@google.com5c210c72011-07-28 21:06:40 +00001163}
1164
1165bool SkPNGImageEncoder::doEncode(SkWStream* stream, const SkBitmap& bitmap,
1166 const bool& hasAlpha, int colorType,
reed0689d7b2014-06-14 05:30:20 -07001167 int bitDepth, SkColorType ct,
tomhudson@google.com5c210c72011-07-28 21:06:40 +00001168 png_color_8& sig_bit) {
1169
reed@android.com8a1c16f2008-12-17 15:59:43 +00001170 png_structp png_ptr;
1171 png_infop info_ptr;
1172
1173 png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, sk_error_fn,
1174 NULL);
1175 if (NULL == png_ptr) {
1176 return false;
1177 }
1178
1179 info_ptr = png_create_info_struct(png_ptr);
1180 if (NULL == info_ptr) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +00001181 png_destroy_write_struct(&png_ptr, png_infopp_NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001182 return false;
1183 }
1184
1185 /* Set error handling. REQUIRED if you aren't supplying your own
1186 * error handling functions in the png_create_write_struct() call.
1187 */
1188 if (setjmp(png_jmpbuf(png_ptr))) {
1189 png_destroy_write_struct(&png_ptr, &info_ptr);
1190 return false;
1191 }
1192
commit-bot@chromium.orga936e372013-03-14 14:42:18 +00001193 png_set_write_fn(png_ptr, (void*)stream, sk_write_fn, png_flush_ptr_NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001194
1195 /* Set the image information here. Width and height are up to 2^31,
1196 * bit_depth is one of 1, 2, 4, 8, or 16, but valid values also depend on
1197 * the color_type selected. color_type is one of PNG_COLOR_TYPE_GRAY,
1198 * PNG_COLOR_TYPE_GRAY_ALPHA, PNG_COLOR_TYPE_PALETTE, PNG_COLOR_TYPE_RGB,
1199 * or PNG_COLOR_TYPE_RGB_ALPHA. interlace is either PNG_INTERLACE_NONE or
1200 * PNG_INTERLACE_ADAM7, and the compression_type and filter_type MUST
1201 * currently be PNG_COMPRESSION_TYPE_BASE and PNG_FILTER_TYPE_BASE. REQUIRED
1202 */
1203
1204 png_set_IHDR(png_ptr, info_ptr, bitmap.width(), bitmap.height(),
1205 bitDepth, colorType,
1206 PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE,
1207 PNG_FILTER_TYPE_BASE);
1208
reed@android.com61898772009-07-07 19:38:01 +00001209 // set our colortable/trans arrays if needed
1210 png_color paletteColors[256];
1211 png_byte trans[256];
reed0689d7b2014-06-14 05:30:20 -07001212 if (kIndex_8_SkColorType == ct) {
reed@android.com61898772009-07-07 19:38:01 +00001213 SkColorTable* ct = bitmap.getColorTable();
1214 int numTrans = pack_palette(ct, paletteColors, trans, hasAlpha);
1215 png_set_PLTE(png_ptr, info_ptr, paletteColors, ct->count());
1216 if (numTrans > 0) {
1217 png_set_tRNS(png_ptr, info_ptr, trans, numTrans, NULL);
1218 }
1219 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001220
1221 png_set_sBIT(png_ptr, info_ptr, &sig_bit);
1222 png_write_info(png_ptr, info_ptr);
1223
1224 const char* srcImage = (const char*)bitmap.getPixels();
1225 SkAutoSMalloc<1024> rowStorage(bitmap.width() << 2);
1226 char* storage = (char*)rowStorage.get();
reed0689d7b2014-06-14 05:30:20 -07001227 transform_scanline_proc proc = choose_proc(ct, hasAlpha);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001228
1229 for (int y = 0; y < bitmap.height(); y++) {
1230 png_bytep row_ptr = (png_bytep)storage;
1231 proc(srcImage, bitmap.width(), storage);
1232 png_write_rows(png_ptr, &row_ptr, 1);
1233 srcImage += bitmap.rowBytes();
1234 }
1235
1236 png_write_end(png_ptr, info_ptr);
1237
1238 /* clean up after the write, and free any memory allocated */
1239 png_destroy_write_struct(&png_ptr, &info_ptr);
1240 return true;
1241}
1242
reed@android.com00bf85a2009-01-22 13:04:56 +00001243///////////////////////////////////////////////////////////////////////////////
robertphillips@google.comec51cb82012-03-23 18:13:47 +00001244DEFINE_DECODER_CREATOR(PNGImageDecoder);
1245DEFINE_ENCODER_CREATOR(PNGImageEncoder);
1246///////////////////////////////////////////////////////////////////////////////
reed@android.com00bf85a2009-01-22 13:04:56 +00001247
scroggo@google.comb5571b32013-09-25 21:34:24 +00001248static bool is_png(SkStreamRewindable* stream) {
reed@android.com00bf85a2009-01-22 13:04:56 +00001249 char buf[PNG_BYTES_TO_CHECK];
1250 if (stream->read(buf, PNG_BYTES_TO_CHECK) == PNG_BYTES_TO_CHECK &&
1251 !png_sig_cmp((png_bytep) buf, (png_size_t)0, PNG_BYTES_TO_CHECK)) {
scroggo@google.com39edf4c2013-04-25 17:33:51 +00001252 return true;
1253 }
1254 return false;
1255}
1256
scroggo@google.comb5571b32013-09-25 21:34:24 +00001257SkImageDecoder* sk_libpng_dfactory(SkStreamRewindable* stream) {
scroggo@google.com39edf4c2013-04-25 17:33:51 +00001258 if (is_png(stream)) {
reed@android.com00bf85a2009-01-22 13:04:56 +00001259 return SkNEW(SkPNGImageDecoder);
1260 }
1261 return NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001262}
1263
scroggo@google.comb5571b32013-09-25 21:34:24 +00001264static SkImageDecoder::Format get_format_png(SkStreamRewindable* stream) {
scroggo@google.com39edf4c2013-04-25 17:33:51 +00001265 if (is_png(stream)) {
1266 return SkImageDecoder::kPNG_Format;
1267 }
1268 return SkImageDecoder::kUnknown_Format;
1269}
1270
reed@android.comdfee5792010-04-15 14:24:50 +00001271SkImageEncoder* sk_libpng_efactory(SkImageEncoder::Type t) {
reed@android.com00bf85a2009-01-22 13:04:56 +00001272 return (SkImageEncoder::kPNG_Type == t) ? SkNEW(SkPNGImageEncoder) : NULL;
1273}
1274
mtklein@google.combd6343b2013-09-04 17:20:18 +00001275static SkImageDecoder_DecodeReg gDReg(sk_libpng_dfactory);
1276static SkImageDecoder_FormatReg gFormatReg(get_format_png);
1277static SkImageEncoder_EncodeReg gEReg(sk_libpng_efactory);