blob: 8a3e40ea19633b639448d1af73efabcdbd69ec54 [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
scroggo2a120802014-10-22 12:07:00 -070094 virtual Result onDecode(SkStream* stream, SkBitmap* bm, Mode) SK_OVERRIDE;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +000095
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
kkinnunen93b255b2014-10-19 22:07:23 -0700144#ifdef PNG_READ_UNKNOWN_CHUNKS_SUPPORTED
reed@android.com8a1c16f2008-12-17 15:59:43 +0000145static int sk_read_user_chunk(png_structp png_ptr, png_unknown_chunkp chunk) {
146 SkImageDecoder::Peeker* peeker =
147 (SkImageDecoder::Peeker*)png_get_user_chunk_ptr(png_ptr);
148 // peek() returning true means continue decoding
149 return peeker->peek((const char*)chunk->name, chunk->data, chunk->size) ?
150 1 : -1;
151}
kkinnunen93b255b2014-10-19 22:07:23 -0700152#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000153
154static void sk_error_fn(png_structp png_ptr, png_const_charp msg) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000155 SkDEBUGF(("------ png error %s\n", msg));
reed@android.com8a1c16f2008-12-17 15:59:43 +0000156 longjmp(png_jmpbuf(png_ptr), 1);
157}
158
159static void skip_src_rows(png_structp png_ptr, uint8_t storage[], int count) {
160 for (int i = 0; i < count; i++) {
161 uint8_t* tmp = storage;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000162 png_read_rows(png_ptr, &tmp, png_bytepp_NULL, 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000163 }
164}
165
166static bool pos_le(int value, int max) {
167 return value > 0 && value <= max;
168}
169
170static bool substituteTranspColor(SkBitmap* bm, SkPMColor match) {
reed0689d7b2014-06-14 05:30:20 -0700171 SkASSERT(bm->colorType() == kN32_SkColorType);
rmistry@google.comd6176b02012-08-23 18:14:13 +0000172
reed@android.com8a1c16f2008-12-17 15:59:43 +0000173 bool reallyHasAlpha = false;
174
175 for (int y = bm->height() - 1; y >= 0; --y) {
176 SkPMColor* p = bm->getAddr32(0, y);
177 for (int x = bm->width() - 1; x >= 0; --x) {
178 if (match == *p) {
179 *p = 0;
180 reallyHasAlpha = true;
181 }
182 p += 1;
183 }
184 }
185 return reallyHasAlpha;
186}
187
reed6c225732014-06-09 19:52:07 -0700188static bool canUpscalePaletteToConfig(SkColorType dstColorType, bool srcHasAlpha) {
189 switch (dstColorType) {
190 case kN32_SkColorType:
191 case kARGB_4444_SkColorType:
reed@android.comb6137c32009-07-29 20:56:52 +0000192 return true;
reed6c225732014-06-09 19:52:07 -0700193 case kRGB_565_SkColorType:
reed@android.comb6137c32009-07-29 20:56:52 +0000194 // only return true if the src is opaque (since 565 is opaque)
195 return !srcHasAlpha;
196 default:
197 return false;
198 }
199}
200
201// call only if color_type is PALETTE. Returns true if the ctable has alpha
202static bool hasTransparencyInPalette(png_structp png_ptr, png_infop info_ptr) {
203 png_bytep trans;
204 int num_trans;
205
206 if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
207 png_get_tRNS(png_ptr, info_ptr, &trans, &num_trans, NULL);
208 return num_trans > 0;
209 }
210 return false;
reed@android.com11344262009-07-08 20:09:23 +0000211}
212
halcanary@google.comfed30372013-10-04 12:46:45 +0000213void do_nothing_warning_fn(png_structp, png_const_charp) {
214 /* do nothing */
215}
216
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000217bool SkPNGImageDecoder::onDecodeInit(SkStream* sk_stream, png_structp *png_ptrp,
218 png_infop *info_ptrp) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000219 /* Create and initialize the png_struct with the desired error handler
220 * functions. If you want to use the default stderr and longjump method,
221 * you can supply NULL for the last three parameters. We also supply the
222 * the compiler header file version, so that we know if the application
223 * was compiled with a compatible version of the library. */
halcanary@google.comfed30372013-10-04 12:46:45 +0000224
halcanary@google.comfed30372013-10-04 12:46:45 +0000225 png_error_ptr user_warning_fn =
226 (c_suppressPNGImageDecoderWarnings) ? (&do_nothing_warning_fn) : NULL;
227 /* NULL means to leave as default library behavior. */
halcanary@google.com2a103182013-10-14 12:49:15 +0000228 /* c_suppressPNGImageDecoderWarnings default depends on SK_DEBUG. */
halcanary@google.comfed30372013-10-04 12:46:45 +0000229 /* To suppress warnings with a SK_DEBUG binary, set the
230 * environment variable "skia_images_png_suppressDecoderWarnings"
231 * to "true". Inside a program that links to skia:
232 * SK_CONF_SET("images.png.suppressDecoderWarnings", true); */
halcanary@google.comfed30372013-10-04 12:46:45 +0000233
reed@android.com8a1c16f2008-12-17 15:59:43 +0000234 png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
halcanary@google.comfed30372013-10-04 12:46:45 +0000235 NULL, sk_error_fn, user_warning_fn);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000236 // png_voidp user_error_ptr, user_error_fn, user_warning_fn);
237 if (png_ptr == NULL) {
238 return false;
239 }
halcanary@google.comfed30372013-10-04 12:46:45 +0000240
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000241 *png_ptrp = png_ptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000242
243 /* Allocate/initialize the memory for image information. */
244 png_infop info_ptr = png_create_info_struct(png_ptr);
245 if (info_ptr == NULL) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000246 png_destroy_read_struct(&png_ptr, png_infopp_NULL, png_infopp_NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000247 return false;
248 }
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000249 *info_ptrp = info_ptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000250
251 /* Set error handling if you are using the setjmp/longjmp method (this is
252 * the normal method of doing things with libpng). REQUIRED unless you
253 * set up your own error handlers in the png_create_read_struct() earlier.
254 */
255 if (setjmp(png_jmpbuf(png_ptr))) {
scroggo@google.com5401cd02013-11-12 15:30:06 +0000256 png_destroy_read_struct(&png_ptr, &info_ptr, png_infopp_NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000257 return false;
258 }
259
260 /* If you are using replacement read functions, instead of calling
261 * png_init_io() here you would call:
262 */
263 png_set_read_fn(png_ptr, (void *)sk_stream, sk_read_fn);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000264#ifdef SK_BUILD_FOR_ANDROID
265 png_set_seek_fn(png_ptr, sk_seek_fn);
266#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000267 /* where user_io_ptr is a structure you want available to the callbacks */
268 /* If we have already read some of the signature */
269// png_set_sig_bytes(png_ptr, 0 /* sig_read */ );
270
kkinnunen93b255b2014-10-19 22:07:23 -0700271#ifdef PNG_READ_UNKNOWN_CHUNKS_SUPPORTED
reed@android.com8a1c16f2008-12-17 15:59:43 +0000272 // hookup our peeker so we can see any user-chunks the caller may be interested in
273 png_set_keep_unknown_chunks(png_ptr, PNG_HANDLE_CHUNK_ALWAYS, (png_byte*)"", 0);
274 if (this->getPeeker()) {
275 png_set_read_user_chunk_fn(png_ptr, (png_voidp)this->getPeeker(), sk_read_user_chunk);
276 }
kkinnunen93b255b2014-10-19 22:07:23 -0700277#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000278 /* The call to png_read_info() gives us all of the information from the
279 * PNG file before the first IDAT (image data chunk). */
280 png_read_info(png_ptr, info_ptr);
281 png_uint_32 origWidth, origHeight;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000282 int bitDepth, colorType;
283 png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
284 &colorType, int_p_NULL, int_p_NULL, int_p_NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000285
286 /* tell libpng to strip 16 bit/color files down to 8 bits/color */
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000287 if (bitDepth == 16) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000288 png_set_strip_16(png_ptr);
289 }
kkinnunen93b255b2014-10-19 22:07:23 -0700290#ifdef PNG_READ_PACK_SUPPORTED
reed@android.com8a1c16f2008-12-17 15:59:43 +0000291 /* Extract multiple pixels with bit depths of 1, 2, and 4 from a single
292 * byte into separate bytes (useful for paletted and grayscale images). */
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000293 if (bitDepth < 8) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000294 png_set_packing(png_ptr);
295 }
kkinnunen93b255b2014-10-19 22:07:23 -0700296#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000297 /* Expand grayscale images to the full 8 bits from 1, 2, or 4 bits/pixel */
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000298 if (colorType == PNG_COLOR_TYPE_GRAY && bitDepth < 8) {
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000299 png_set_expand_gray_1_2_4_to_8(png_ptr);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000300 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000301
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000302 return true;
303}
304
scroggo2a120802014-10-22 12:07:00 -0700305SkImageDecoder::Result SkPNGImageDecoder::onDecode(SkStream* sk_stream, SkBitmap* decodedBitmap,
306 Mode mode) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000307 png_structp png_ptr;
308 png_infop info_ptr;
309
310 if (!onDecodeInit(sk_stream, &png_ptr, &info_ptr)) {
scroggo2a120802014-10-22 12:07:00 -0700311 return kFailure;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000312 }
313
scroggo@google.comfeeca3c2013-11-12 14:38:41 +0000314 PNGAutoClean autoClean(png_ptr, info_ptr);
315
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000316 if (setjmp(png_jmpbuf(png_ptr))) {
scroggo2a120802014-10-22 12:07:00 -0700317 return kFailure;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000318 }
319
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000320 png_uint_32 origWidth, origHeight;
reed6c225732014-06-09 19:52:07 -0700321 int bitDepth, pngColorType, interlaceType;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000322 png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
reed6c225732014-06-09 19:52:07 -0700323 &pngColorType, &interlaceType, int_p_NULL, int_p_NULL);
rmistry@google.comd6176b02012-08-23 18:14:13 +0000324
reed6c225732014-06-09 19:52:07 -0700325 SkColorType colorType;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000326 bool hasAlpha = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000327 SkPMColor theTranspColor = 0; // 0 tells us not to try to match
rmistry@google.comd6176b02012-08-23 18:14:13 +0000328
reed0689d7b2014-06-14 05:30:20 -0700329 if (!this->getBitmapColorType(png_ptr, info_ptr, &colorType, &hasAlpha, &theTranspColor)) {
scroggo2a120802014-10-22 12:07:00 -0700330 return kFailure;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000331 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000332
reed6c225732014-06-09 19:52:07 -0700333 SkAlphaType alphaType = this->getRequireUnpremultipliedColors() ?
334 kUnpremul_SkAlphaType : kPremul_SkAlphaType;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000335 const int sampleSize = this->getSampleSize();
336 SkScaledBitmapSampler sampler(origWidth, origHeight, sampleSize);
reed6c225732014-06-09 19:52:07 -0700337 decodedBitmap->setInfo(SkImageInfo::Make(sampler.scaledWidth(), sampler.scaledHeight(),
338 colorType, alphaType));
djsollen@google.com446cf712014-02-19 21:45:35 +0000339
reed@android.com8a1c16f2008-12-17 15:59:43 +0000340 if (SkImageDecoder::kDecodeBounds_Mode == mode) {
scroggo2a120802014-10-22 12:07:00 -0700341 return kSuccess;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000342 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000343
reed@android.com8a1c16f2008-12-17 15:59:43 +0000344 // from here down we are concerned with colortables and pixels
345
346 // we track if we actually see a non-opaque pixels, since sometimes a PNG sets its colortype
347 // to |= PNG_COLOR_MASK_ALPHA, but all of its pixels are in fact opaque. We care, since we
348 // draw lots faster if we can flag the bitmap has being opaque
349 bool reallyHasAlpha = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000350 SkColorTable* colorTable = NULL;
351
reed6c225732014-06-09 19:52:07 -0700352 if (pngColorType == PNG_COLOR_TYPE_PALETTE) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000353 decodePalette(png_ptr, info_ptr, &hasAlpha, &reallyHasAlpha, &colorTable);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000354 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000355
reed@android.com8a1c16f2008-12-17 15:59:43 +0000356 SkAutoUnref aur(colorTable);
357
scroggo@google.combc69ce92013-07-09 15:45:14 +0000358 if (!this->allocPixelRef(decodedBitmap,
reed6c225732014-06-09 19:52:07 -0700359 kIndex_8_SkColorType == colorType ? colorTable : NULL)) {
scroggo2a120802014-10-22 12:07:00 -0700360 return kFailure;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000361 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000362
reed@android.com8a1c16f2008-12-17 15:59:43 +0000363 SkAutoLockPixels alp(*decodedBitmap);
364
reed@android.com8a1c16f2008-12-17 15:59:43 +0000365 /* Turn on interlace handling. REQUIRED if you are not using
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000366 * png_read_image(). To see how to handle interlacing passes,
367 * see the png_read_row() method below:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000368 */
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000369 const int number_passes = (interlaceType != PNG_INTERLACE_NONE) ?
370 png_set_interlace_handling(png_ptr) : 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000371
372 /* Optional call to gamma correct and add the background to the palette
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000373 * and update info structure. REQUIRED if you are expecting libpng to
374 * update the palette for you (ie you selected such a transform above).
reed@android.com8a1c16f2008-12-17 15:59:43 +0000375 */
376 png_read_update_info(png_ptr, info_ptr);
377
reed6c225732014-06-09 19:52:07 -0700378 if ((kAlpha_8_SkColorType == colorType || kIndex_8_SkColorType == colorType) &&
379 1 == sampleSize) {
380 if (kAlpha_8_SkColorType == colorType) {
scroggo@google.com5ee18dd2013-10-21 20:47:31 +0000381 // For an A8 bitmap, we assume there is an alpha for speed. It is
382 // possible the bitmap is opaque, but that is an unlikely use case
383 // since it would not be very interesting.
384 reallyHasAlpha = true;
385 // A8 is only allowed if the original was GRAY.
reed6c225732014-06-09 19:52:07 -0700386 SkASSERT(PNG_COLOR_TYPE_GRAY == pngColorType);
scroggo@google.com5ee18dd2013-10-21 20:47:31 +0000387 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000388 for (int i = 0; i < number_passes; i++) {
389 for (png_uint_32 y = 0; y < origHeight; y++) {
390 uint8_t* bmRow = decodedBitmap->getAddr8(0, y);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000391 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000392 }
393 }
394 } else {
395 SkScaledBitmapSampler::SrcConfig sc;
396 int srcBytesPerPixel = 4;
rmistry@google.comd6176b02012-08-23 18:14:13 +0000397
reed@android.com11344262009-07-08 20:09:23 +0000398 if (colorTable != NULL) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000399 sc = SkScaledBitmapSampler::kIndex;
400 srcBytesPerPixel = 1;
reed6c225732014-06-09 19:52:07 -0700401 } else if (kAlpha_8_SkColorType == colorType) {
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000402 // A8 is only allowed if the original was GRAY.
reed6c225732014-06-09 19:52:07 -0700403 SkASSERT(PNG_COLOR_TYPE_GRAY == pngColorType);
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000404 sc = SkScaledBitmapSampler::kGray;
405 srcBytesPerPixel = 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000406 } else if (hasAlpha) {
407 sc = SkScaledBitmapSampler::kRGBA;
408 } else {
409 sc = SkScaledBitmapSampler::kRGBX;
410 }
reed@android.com11344262009-07-08 20:09:23 +0000411
412 /* We have to pass the colortable explicitly, since we may have one
413 even if our decodedBitmap doesn't, due to the request that we
414 upscale png's palette to a direct model
415 */
mtklein775b8192014-12-02 09:11:25 -0800416 const SkPMColor* colors = colorTable ? colorTable->readColors() : NULL;
417 if (!sampler.begin(decodedBitmap, sc, *this, colors)) {
scroggo2a120802014-10-22 12:07:00 -0700418 return kFailure;
reed@android.com862e91b2009-04-28 15:27:07 +0000419 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000420 const int height = decodedBitmap->height();
421
reed@android.com862e91b2009-04-28 15:27:07 +0000422 if (number_passes > 1) {
423 SkAutoMalloc storage(origWidth * origHeight * srcBytesPerPixel);
424 uint8_t* base = (uint8_t*)storage.get();
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000425 size_t rowBytes = origWidth * srcBytesPerPixel;
reed@android.coma8a8b8b2009-05-04 15:00:11 +0000426
reed@android.com862e91b2009-04-28 15:27:07 +0000427 for (int i = 0; i < number_passes; i++) {
428 uint8_t* row = base;
429 for (png_uint_32 y = 0; y < origHeight; y++) {
430 uint8_t* bmRow = row;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000431 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
432 row += rowBytes;
reed@android.com862e91b2009-04-28 15:27:07 +0000433 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000434 }
reed@android.com862e91b2009-04-28 15:27:07 +0000435 // now sample it
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000436 base += sampler.srcY0() * rowBytes;
reed@android.com862e91b2009-04-28 15:27:07 +0000437 for (int y = 0; y < height; y++) {
438 reallyHasAlpha |= sampler.next(base);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000439 base += sampler.srcDY() * rowBytes;
reed@android.com862e91b2009-04-28 15:27:07 +0000440 }
441 } else {
442 SkAutoMalloc storage(origWidth * srcBytesPerPixel);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000443 uint8_t* srcRow = (uint8_t*)storage.get();
444 skip_src_rows(png_ptr, srcRow, sampler.srcY0());
445
446 for (int y = 0; y < height; y++) {
447 uint8_t* tmp = srcRow;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000448 png_read_rows(png_ptr, &tmp, png_bytepp_NULL, 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000449 reallyHasAlpha |= sampler.next(srcRow);
450 if (y < height - 1) {
451 skip_src_rows(png_ptr, srcRow, sampler.srcDY() - 1);
452 }
453 }
reed@android.com862e91b2009-04-28 15:27:07 +0000454
reed@android.com8a1c16f2008-12-17 15:59:43 +0000455 // skip the rest of the rows (if any)
456 png_uint_32 read = (height - 1) * sampler.srcDY() +
457 sampler.srcY0() + 1;
458 SkASSERT(read <= origHeight);
459 skip_src_rows(png_ptr, srcRow, origHeight - read);
460 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000461 }
462
463 /* read rest of file, and get additional chunks in info_ptr - REQUIRED */
464 png_read_end(png_ptr, info_ptr);
465
466 if (0 != theTranspColor) {
467 reallyHasAlpha |= substituteTranspColor(decodedBitmap, theTranspColor);
468 }
scroggo@google.com5ee18dd2013-10-21 20:47:31 +0000469 if (reallyHasAlpha && this->getRequireUnpremultipliedColors()) {
reed0689d7b2014-06-14 05:30:20 -0700470 switch (decodedBitmap->colorType()) {
471 case kIndex_8_SkColorType:
scroggo@google.com5ee18dd2013-10-21 20:47:31 +0000472 // Fall through.
reed0689d7b2014-06-14 05:30:20 -0700473 case kARGB_4444_SkColorType:
474 // We have chosen not to support unpremul for these colortypes.
scroggo2a120802014-10-22 12:07:00 -0700475 return kFailure;
scroggo@google.com5ee18dd2013-10-21 20:47:31 +0000476 default: {
reed0689d7b2014-06-14 05:30:20 -0700477 // Fall through to finish the decode. This colortype either
scroggo@google.com5ee18dd2013-10-21 20:47:31 +0000478 // supports unpremul or it is irrelevant because it has no
479 // alpha (or only alpha).
480 // These brackets prevent a warning.
481 }
482 }
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000483 }
scroggo@google.com5ee18dd2013-10-21 20:47:31 +0000484
djsollen@google.com446cf712014-02-19 21:45:35 +0000485 if (!reallyHasAlpha) {
486 decodedBitmap->setAlphaType(kOpaque_SkAlphaType);
reed@google.com383a6972013-10-21 14:00:07 +0000487 }
scroggo2a120802014-10-22 12:07:00 -0700488 return kSuccess;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000489}
490
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000491
492
reed0689d7b2014-06-14 05:30:20 -0700493bool SkPNGImageDecoder::getBitmapColorType(png_structp png_ptr, png_infop info_ptr,
494 SkColorType* colorTypep,
495 bool* hasAlphap,
496 SkPMColor* SK_RESTRICT theTranspColorp) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000497 png_uint_32 origWidth, origHeight;
498 int bitDepth, colorType;
499 png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
500 &colorType, int_p_NULL, int_p_NULL, int_p_NULL);
501
kkinnunen93b255b2014-10-19 22:07:23 -0700502#ifdef PNG_sBIT_SUPPORTED
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000503 // check for sBIT chunk data, in case we should disable dithering because
504 // our data is not truely 8bits per component
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000505 png_color_8p sig_bit;
scroggo@google.com8d239242013-10-01 17:27:15 +0000506 if (this->getDitherImage() && png_get_sBIT(png_ptr, info_ptr, &sig_bit)) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000507#if 0
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000508 SkDebugf("----- sBIT %d %d %d %d\n", sig_bit->red, sig_bit->green,
509 sig_bit->blue, sig_bit->alpha);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000510#endif
511 // 0 seems to indicate no information available
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000512 if (pos_le(sig_bit->red, SK_R16_BITS) &&
513 pos_le(sig_bit->green, SK_G16_BITS) &&
514 pos_le(sig_bit->blue, SK_B16_BITS)) {
scroggo@google.com8d239242013-10-01 17:27:15 +0000515 this->setDitherImage(false);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000516 }
517 }
kkinnunen93b255b2014-10-19 22:07:23 -0700518#endif
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000519
520 if (colorType == PNG_COLOR_TYPE_PALETTE) {
521 bool paletteHasAlpha = hasTransparencyInPalette(png_ptr, info_ptr);
reed6c225732014-06-09 19:52:07 -0700522 *colorTypep = this->getPrefColorType(kIndex_SrcDepth, paletteHasAlpha);
reed0689d7b2014-06-14 05:30:20 -0700523 // now see if we can upscale to their requested colortype
reed6c225732014-06-09 19:52:07 -0700524 if (!canUpscalePaletteToConfig(*colorTypep, paletteHasAlpha)) {
525 *colorTypep = kIndex_8_SkColorType;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000526 }
527 } else {
528 png_color_16p transpColor = NULL;
529 int numTransp = 0;
530
531 png_get_tRNS(png_ptr, info_ptr, NULL, &numTransp, &transpColor);
532
533 bool valid = png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS);
534
535 if (valid && numTransp == 1 && transpColor != NULL) {
536 /* Compute our transparent color, which we'll match against later.
537 We don't really handle 16bit components properly here, since we
538 do our compare *after* the values have been knocked down to 8bit
539 which means we will find more matches than we should. The real
540 fix seems to be to see the actual 16bit components, do the
541 compare, and then knock it down to 8bits ourselves.
542 */
543 if (colorType & PNG_COLOR_MASK_COLOR) {
544 if (16 == bitDepth) {
545 *theTranspColorp = SkPackARGB32(0xFF, transpColor->red >> 8,
546 transpColor->green >> 8,
547 transpColor->blue >> 8);
548 } else {
halcanary@google.comfed30372013-10-04 12:46:45 +0000549 /* We apply the mask because in a very small
550 number of corrupt PNGs, (transpColor->red > 255)
551 and (bitDepth == 8), for certain versions of libpng. */
552 *theTranspColorp = SkPackARGB32(0xFF,
553 0xFF & (transpColor->red),
554 0xFF & (transpColor->green),
555 0xFF & (transpColor->blue));
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000556 }
557 } else { // gray
558 if (16 == bitDepth) {
559 *theTranspColorp = SkPackARGB32(0xFF, transpColor->gray >> 8,
560 transpColor->gray >> 8,
561 transpColor->gray >> 8);
562 } else {
halcanary@google.comfed30372013-10-04 12:46:45 +0000563 /* We apply the mask because in a very small
564 number of corrupt PNGs, (transpColor->red >
565 255) and (bitDepth == 8), for certain versions
566 of libpng. For safety we assume the same could
567 happen with a grayscale PNG. */
568 *theTranspColorp = SkPackARGB32(0xFF,
569 0xFF & (transpColor->gray),
570 0xFF & (transpColor->gray),
571 0xFF & (transpColor->gray));
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000572 }
573 }
574 }
575
576 if (valid ||
577 PNG_COLOR_TYPE_RGB_ALPHA == colorType ||
578 PNG_COLOR_TYPE_GRAY_ALPHA == colorType) {
579 *hasAlphap = true;
580 }
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000581
582 SrcDepth srcDepth = k32Bit_SrcDepth;
583 if (PNG_COLOR_TYPE_GRAY == colorType) {
584 srcDepth = k8BitGray_SrcDepth;
scroggo@google.com8e2ef012013-07-18 20:14:45 +0000585 // Remove this assert, which fails on desk_pokemonwiki.skp
586 //SkASSERT(!*hasAlphap);
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000587 }
588
reed6c225732014-06-09 19:52:07 -0700589 *colorTypep = this->getPrefColorType(srcDepth, *hasAlphap);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000590 // now match the request against our capabilities
591 if (*hasAlphap) {
reed6c225732014-06-09 19:52:07 -0700592 if (*colorTypep != kARGB_4444_SkColorType) {
593 *colorTypep = kN32_SkColorType;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000594 }
595 } else {
reed6c225732014-06-09 19:52:07 -0700596 if (kAlpha_8_SkColorType == *colorTypep) {
scroggo@google.com354fd972013-10-02 15:50:19 +0000597 if (k8BitGray_SrcDepth != srcDepth) {
598 // Converting a non grayscale image to A8 is not currently supported.
reed6c225732014-06-09 19:52:07 -0700599 *colorTypep = kN32_SkColorType;
scroggo@google.com354fd972013-10-02 15:50:19 +0000600 }
reed6c225732014-06-09 19:52:07 -0700601 } else if (*colorTypep != kRGB_565_SkColorType &&
602 *colorTypep != kARGB_4444_SkColorType) {
603 *colorTypep = kN32_SkColorType;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000604 }
605 }
606 }
607
608 // sanity check for size
609 {
reed@google.com57212f92013-12-30 14:40:38 +0000610 int64_t size = sk_64_mul(origWidth, origHeight);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000611 // now check that if we are 4-bytes per pixel, we also don't overflow
reed@google.com57212f92013-12-30 14:40:38 +0000612 if (size < 0 || size > (0x7FFFFFFF >> 2)) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000613 return false;
614 }
615 }
616
reed5926b862014-06-11 10:33:13 -0700617#ifdef SK_SUPPORT_LEGACY_IMAGEDECODER_CHOOSER
reed6c225732014-06-09 19:52:07 -0700618 if (!this->chooseFromOneChoice(*colorTypep, origWidth, origHeight)) {
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000619 return false;
620 }
reed5926b862014-06-11 10:33:13 -0700621#endif
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000622
623 // If the image has alpha and the decoder wants unpremultiplied
reed0689d7b2014-06-14 05:30:20 -0700624 // colors, the only supported colortype is 8888.
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000625 if (this->getRequireUnpremultipliedColors() && *hasAlphap) {
reed6c225732014-06-09 19:52:07 -0700626 *colorTypep = kN32_SkColorType;
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000627 }
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000628
629 if (fImageIndex != NULL) {
reed6c225732014-06-09 19:52:07 -0700630 if (kUnknown_SkColorType == fImageIndex->fColorType) {
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000631 // This is the first time for this subset decode. From now on,
reed0689d7b2014-06-14 05:30:20 -0700632 // all decodes must be in the same colortype.
reed6c225732014-06-09 19:52:07 -0700633 fImageIndex->fColorType = *colorTypep;
634 } else if (fImageIndex->fColorType != *colorTypep) {
635 // Requesting a different colortype for a subsequent decode is not
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000636 // supported. Report failure before we make changes to png_ptr.
637 return false;
638 }
639 }
640
reed6c225732014-06-09 19:52:07 -0700641 bool convertGrayToRGB = PNG_COLOR_TYPE_GRAY == colorType && *colorTypep != kAlpha_8_SkColorType;
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000642
643 // Unless the user is requesting A8, convert a grayscale image into RGB.
644 // GRAY_ALPHA will always be converted to RGB
645 if (convertGrayToRGB || colorType == PNG_COLOR_TYPE_GRAY_ALPHA) {
646 png_set_gray_to_rgb(png_ptr);
647 }
648
649 // Add filler (or alpha) byte (after each RGB triplet) if necessary.
650 if (colorType == PNG_COLOR_TYPE_RGB || convertGrayToRGB) {
651 png_set_filler(png_ptr, 0xff, PNG_FILLER_AFTER);
652 }
653
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000654 return true;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000655}
656
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000657typedef uint32_t (*PackColorProc)(U8CPU a, U8CPU r, U8CPU g, U8CPU b);
658
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000659bool SkPNGImageDecoder::decodePalette(png_structp png_ptr, png_infop info_ptr,
660 bool *hasAlphap, bool *reallyHasAlphap,
661 SkColorTable **colorTablep) {
662 int numPalette;
663 png_colorp palette;
664 png_bytep trans;
665 int numTrans;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000666
667 png_get_PLTE(png_ptr, info_ptr, &palette, &numPalette);
668
669 /* BUGGY IMAGE WORKAROUND
670
671 We hit some images (e.g. fruit_.png) who contain bytes that are == colortable_count
672 which is a problem since we use the byte as an index. To work around this we grow
673 the colortable by 1 (if its < 256) and duplicate the last color into that slot.
674 */
675 int colorCount = numPalette + (numPalette < 256);
reed@google.com0a6151d2013-10-10 14:44:56 +0000676 SkPMColor colorStorage[256]; // worst-case storage
677 SkPMColor* colorPtr = colorStorage;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000678
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000679 if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
680 png_get_tRNS(png_ptr, info_ptr, &trans, &numTrans, NULL);
681 *hasAlphap = (numTrans > 0);
682 } else {
683 numTrans = 0;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000684 }
reed@google.com0a6151d2013-10-10 14:44:56 +0000685
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000686 // check for bad images that might make us crash
687 if (numTrans > numPalette) {
688 numTrans = numPalette;
689 }
690
691 int index = 0;
692 int transLessThanFF = 0;
693
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000694 // Choose which function to use to create the color table. If the final destination's
reed0689d7b2014-06-14 05:30:20 -0700695 // colortype is unpremultiplied, the color table will store unpremultiplied colors.
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000696 PackColorProc proc;
697 if (this->getRequireUnpremultipliedColors()) {
698 proc = &SkPackARGB32NoCheck;
699 } else {
700 proc = &SkPreMultiplyARGB;
701 }
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000702 for (; index < numTrans; index++) {
703 transLessThanFF |= (int)*trans - 0xFF;
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000704 *colorPtr++ = proc(*trans++, palette->red, palette->green, palette->blue);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000705 palette++;
706 }
reed@google.com0a6151d2013-10-10 14:44:56 +0000707 bool reallyHasAlpha = (transLessThanFF < 0);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000708
709 for (; index < numPalette; index++) {
710 *colorPtr++ = SkPackARGB32(0xFF, palette->red, palette->green, palette->blue);
711 palette++;
712 }
713
714 // see BUGGY IMAGE WORKAROUND comment above
715 if (numPalette < 256) {
716 *colorPtr = colorPtr[-1];
717 }
reed@google.com0a6151d2013-10-10 14:44:56 +0000718
reedc5e15a12014-09-29 12:10:27 -0700719 *colorTablep = SkNEW_ARGS(SkColorTable, (colorStorage, colorCount));
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000720 *reallyHasAlphap = reallyHasAlpha;
721 return true;
722}
723
724#ifdef SK_BUILD_FOR_ANDROID
725
scroggo@google.comb5571b32013-09-25 21:34:24 +0000726bool SkPNGImageDecoder::onBuildTileIndex(SkStreamRewindable* sk_stream, int *width, int *height) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000727 png_structp png_ptr;
728 png_infop info_ptr;
729
730 if (!onDecodeInit(sk_stream, &png_ptr, &info_ptr)) {
731 return false;
732 }
733
734 if (setjmp(png_jmpbuf(png_ptr)) != 0) {
735 png_destroy_read_struct(&png_ptr, &info_ptr, png_infopp_NULL);
736 return false;
737 }
738
739 png_uint_32 origWidth, origHeight;
740 int bitDepth, colorType;
741 png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
742 &colorType, int_p_NULL, int_p_NULL, int_p_NULL);
743
744 *width = origWidth;
745 *height = origHeight;
746
747 png_build_index(png_ptr);
748
749 if (fImageIndex) {
750 SkDELETE(fImageIndex);
751 }
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000752 fImageIndex = SkNEW_ARGS(SkPNGImageIndex, (sk_stream, png_ptr, info_ptr));
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000753
754 return true;
755}
756
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000757bool SkPNGImageDecoder::onDecodeSubset(SkBitmap* bm, const SkIRect& region) {
758 if (NULL == fImageIndex) {
759 return false;
760 }
761
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000762 png_structp png_ptr = fImageIndex->fPng_ptr;
763 png_infop info_ptr = fImageIndex->fInfo_ptr;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000764 if (setjmp(png_jmpbuf(png_ptr))) {
765 return false;
766 }
767
768 png_uint_32 origWidth, origHeight;
reed6c225732014-06-09 19:52:07 -0700769 int bitDepth, pngColorType, interlaceType;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000770 png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
reed6c225732014-06-09 19:52:07 -0700771 &pngColorType, &interlaceType, int_p_NULL, int_p_NULL);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000772
773 SkIRect rect = SkIRect::MakeWH(origWidth, origHeight);
774
775 if (!rect.intersect(region)) {
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000776 // If the requested region is entirely outside the image, just
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000777 // returns false
778 return false;
779 }
780
reed6c225732014-06-09 19:52:07 -0700781 SkColorType colorType;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000782 bool hasAlpha = false;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000783 SkPMColor theTranspColor = 0; // 0 tells us not to try to match
784
reed0689d7b2014-06-14 05:30:20 -0700785 if (!this->getBitmapColorType(png_ptr, info_ptr, &colorType, &hasAlpha, &theTranspColor)) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000786 return false;
787 }
788
789 const int sampleSize = this->getSampleSize();
790 SkScaledBitmapSampler sampler(origWidth, rect.height(), sampleSize);
791
792 SkBitmap decodedBitmap;
reed6c225732014-06-09 19:52:07 -0700793 decodedBitmap.setInfo(SkImageInfo::Make(sampler.scaledWidth(), sampler.scaledHeight(),
794 colorType, kPremul_SkAlphaType));
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000795
796 // from here down we are concerned with colortables and pixels
797
798 // we track if we actually see a non-opaque pixels, since sometimes a PNG sets its colortype
799 // to |= PNG_COLOR_MASK_ALPHA, but all of its pixels are in fact opaque. We care, since we
800 // draw lots faster if we can flag the bitmap has being opaque
801 bool reallyHasAlpha = false;
802 SkColorTable* colorTable = NULL;
803
reed6c225732014-06-09 19:52:07 -0700804 if (pngColorType == PNG_COLOR_TYPE_PALETTE) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000805 decodePalette(png_ptr, info_ptr, &hasAlpha, &reallyHasAlpha, &colorTable);
806 }
807
808 SkAutoUnref aur(colorTable);
809
810 // Check ahead of time if the swap(dest, src) is possible.
811 // If yes, then we will stick to AllocPixelRef since it's cheaper with the swap happening.
812 // If no, then we will use alloc to allocate pixels to prevent garbage collection.
813 int w = rect.width() / sampleSize;
814 int h = rect.height() / sampleSize;
815 const bool swapOnly = (rect == region) && (w == decodedBitmap.width()) &&
816 (h == decodedBitmap.height()) && bm->isNull();
reed6c225732014-06-09 19:52:07 -0700817 const bool needColorTable = kIndex_8_SkColorType == colorType;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000818 if (swapOnly) {
819 if (!this->allocPixelRef(&decodedBitmap, needColorTable ? colorTable : NULL)) {
820 return false;
821 }
822 } else {
reed84825042014-09-02 12:50:45 -0700823 if (!decodedBitmap.tryAllocPixels(NULL, needColorTable ? colorTable : NULL)) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000824 return false;
825 }
826 }
827 SkAutoLockPixels alp(decodedBitmap);
828
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000829 /* Turn on interlace handling. REQUIRED if you are not using
830 * png_read_image(). To see how to handle interlacing passes,
831 * see the png_read_row() method below:
832 */
833 const int number_passes = (interlaceType != PNG_INTERLACE_NONE) ?
834 png_set_interlace_handling(png_ptr) : 1;
835
836 /* Optional call to gamma correct and add the background to the palette
837 * and update info structure. REQUIRED if you are expecting libpng to
838 * update the palette for you (ie you selected such a transform above).
839 */
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000840
841 // Direct access to png_ptr fields is deprecated in libpng > 1.2.
842#if defined(PNG_1_0_X) || defined (PNG_1_2_X)
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000843 png_ptr->pass = 0;
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000844#else
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000845 // FIXME: This sets pass as desired, but also sets iwidth. Is that ok?
846 png_set_interlaced_pass(png_ptr, 0);
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000847#endif
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000848 png_read_update_info(png_ptr, info_ptr);
849
850 int actualTop = rect.fTop;
851
reed6c225732014-06-09 19:52:07 -0700852 if ((kAlpha_8_SkColorType == colorType || kIndex_8_SkColorType == colorType)
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000853 && 1 == sampleSize) {
reed6c225732014-06-09 19:52:07 -0700854 if (kAlpha_8_SkColorType == colorType) {
scroggo@google.com5ee18dd2013-10-21 20:47:31 +0000855 // For an A8 bitmap, we assume there is an alpha for speed. It is
856 // possible the bitmap is opaque, but that is an unlikely use case
857 // since it would not be very interesting.
858 reallyHasAlpha = true;
859 // A8 is only allowed if the original was GRAY.
reed6c225732014-06-09 19:52:07 -0700860 SkASSERT(PNG_COLOR_TYPE_GRAY == pngColorType);
scroggo@google.com5ee18dd2013-10-21 20:47:31 +0000861 }
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000862
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000863 for (int i = 0; i < number_passes; i++) {
864 png_configure_decoder(png_ptr, &actualTop, i);
865 for (int j = 0; j < rect.fTop - actualTop; j++) {
866 uint8_t* bmRow = decodedBitmap.getAddr8(0, 0);
867 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
868 }
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000869 png_uint_32 bitmapHeight = (png_uint_32) decodedBitmap.height();
870 for (png_uint_32 y = 0; y < bitmapHeight; y++) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000871 uint8_t* bmRow = decodedBitmap.getAddr8(0, y);
872 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
873 }
874 }
875 } else {
876 SkScaledBitmapSampler::SrcConfig sc;
877 int srcBytesPerPixel = 4;
878
879 if (colorTable != NULL) {
880 sc = SkScaledBitmapSampler::kIndex;
881 srcBytesPerPixel = 1;
reed6c225732014-06-09 19:52:07 -0700882 } else if (kAlpha_8_SkColorType == colorType) {
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000883 // A8 is only allowed if the original was GRAY.
reed6c225732014-06-09 19:52:07 -0700884 SkASSERT(PNG_COLOR_TYPE_GRAY == pngColorType);
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000885 sc = SkScaledBitmapSampler::kGray;
886 srcBytesPerPixel = 1;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000887 } else if (hasAlpha) {
888 sc = SkScaledBitmapSampler::kRGBA;
889 } else {
890 sc = SkScaledBitmapSampler::kRGBX;
891 }
892
893 /* We have to pass the colortable explicitly, since we may have one
894 even if our decodedBitmap doesn't, due to the request that we
895 upscale png's palette to a direct model
896 */
mtklein775b8192014-12-02 09:11:25 -0800897 const SkPMColor* colors = colorTable ? colorTable->readColors() : NULL;
898 if (!sampler.begin(&decodedBitmap, sc, *this, colors)) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000899 return false;
900 }
901 const int height = decodedBitmap.height();
902
903 if (number_passes > 1) {
904 SkAutoMalloc storage(origWidth * origHeight * srcBytesPerPixel);
905 uint8_t* base = (uint8_t*)storage.get();
906 size_t rb = origWidth * srcBytesPerPixel;
907
908 for (int i = 0; i < number_passes; i++) {
909 png_configure_decoder(png_ptr, &actualTop, i);
910 for (int j = 0; j < rect.fTop - actualTop; j++) {
scroggofc7063b2014-07-25 13:54:43 -0700911 png_read_rows(png_ptr, &base, png_bytepp_NULL, 1);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000912 }
913 uint8_t* row = base;
914 for (int32_t y = 0; y < rect.height(); y++) {
915 uint8_t* bmRow = row;
916 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
917 row += rb;
918 }
919 }
920 // now sample it
921 base += sampler.srcY0() * rb;
922 for (int y = 0; y < height; y++) {
923 reallyHasAlpha |= sampler.next(base);
924 base += sampler.srcDY() * rb;
925 }
926 } else {
927 SkAutoMalloc storage(origWidth * srcBytesPerPixel);
928 uint8_t* srcRow = (uint8_t*)storage.get();
929
930 png_configure_decoder(png_ptr, &actualTop, 0);
931 skip_src_rows(png_ptr, srcRow, sampler.srcY0());
932
933 for (int i = 0; i < rect.fTop - actualTop; i++) {
scroggofc7063b2014-07-25 13:54:43 -0700934 png_read_rows(png_ptr, &srcRow, png_bytepp_NULL, 1);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000935 }
936 for (int y = 0; y < height; y++) {
937 uint8_t* tmp = srcRow;
938 png_read_rows(png_ptr, &tmp, png_bytepp_NULL, 1);
939 reallyHasAlpha |= sampler.next(srcRow);
940 if (y < height - 1) {
941 skip_src_rows(png_ptr, srcRow, sampler.srcDY() - 1);
942 }
943 }
944 }
945 }
946
947 if (0 != theTranspColor) {
948 reallyHasAlpha |= substituteTranspColor(&decodedBitmap, theTranspColor);
949 }
scroggo@google.com5ee18dd2013-10-21 20:47:31 +0000950 if (reallyHasAlpha && this->getRequireUnpremultipliedColors()) {
reed6c225732014-06-09 19:52:07 -0700951 switch (decodedBitmap.colorType()) {
952 case kIndex_8_SkColorType:
scroggo@google.com5ee18dd2013-10-21 20:47:31 +0000953 // Fall through.
reed6c225732014-06-09 19:52:07 -0700954 case kARGB_4444_SkColorType:
955 // We have chosen not to support unpremul for these colortypess.
scroggo@google.com5ee18dd2013-10-21 20:47:31 +0000956 return false;
957 default: {
958 // Fall through to finish the decode. This config either
959 // supports unpremul or it is irrelevant because it has no
960 // alpha (or only alpha).
961 // These brackets prevent a warning.
962 }
963 }
commit-bot@chromium.org546f70c2013-10-03 17:13:38 +0000964 }
reed@google.com383a6972013-10-21 14:00:07 +0000965 SkAlphaType alphaType = kOpaque_SkAlphaType;
966 if (reallyHasAlpha) {
967 if (this->getRequireUnpremultipliedColors()) {
968 alphaType = kUnpremul_SkAlphaType;
969 } else {
970 alphaType = kPremul_SkAlphaType;
971 }
972 }
973 decodedBitmap.setAlphaType(alphaType);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000974
975 if (swapOnly) {
976 bm->swap(decodedBitmap);
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000977 return true;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000978 }
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000979 return this->cropBitmap(bm, &decodedBitmap, sampleSize, region.x(), region.y(),
980 region.width(), region.height(), 0, rect.y());
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000981}
982#endif
983
reed@android.com8a1c16f2008-12-17 15:59:43 +0000984///////////////////////////////////////////////////////////////////////////////
985
reed@android.com8a1c16f2008-12-17 15:59:43 +0000986#include "SkColorPriv.h"
987#include "SkUnPreMultiply.h"
988
989static void sk_write_fn(png_structp png_ptr, png_bytep data, png_size_t len) {
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000990 SkWStream* sk_stream = (SkWStream*)png_get_io_ptr(png_ptr);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000991 if (!sk_stream->write(data, len)) {
992 png_error(png_ptr, "sk_write_fn Error!");
993 }
994}
995
reed0689d7b2014-06-14 05:30:20 -0700996static transform_scanline_proc choose_proc(SkColorType ct, bool hasAlpha) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000997 // we don't care about search on alpha if we're kIndex8, since only the
998 // colortable packing cares about that distinction, not the pixels
reed0689d7b2014-06-14 05:30:20 -0700999 if (kIndex_8_SkColorType == ct) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001000 hasAlpha = false; // we store false in the table entries for kIndex8
1001 }
rmistry@google.comd6176b02012-08-23 18:14:13 +00001002
reed@android.com8a1c16f2008-12-17 15:59:43 +00001003 static const struct {
reed0689d7b2014-06-14 05:30:20 -07001004 SkColorType fColorType;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001005 bool fHasAlpha;
1006 transform_scanline_proc fProc;
1007 } gMap[] = {
reed0689d7b2014-06-14 05:30:20 -07001008 { kRGB_565_SkColorType, false, transform_scanline_565 },
1009 { kN32_SkColorType, false, transform_scanline_888 },
1010 { kN32_SkColorType, true, transform_scanline_8888 },
1011 { kARGB_4444_SkColorType, false, transform_scanline_444 },
1012 { kARGB_4444_SkColorType, true, transform_scanline_4444 },
1013 { kIndex_8_SkColorType, false, transform_scanline_memcpy },
reed@android.com8a1c16f2008-12-17 15:59:43 +00001014 };
1015
1016 for (int i = SK_ARRAY_COUNT(gMap) - 1; i >= 0; --i) {
reed0689d7b2014-06-14 05:30:20 -07001017 if (gMap[i].fColorType == ct && gMap[i].fHasAlpha == hasAlpha) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001018 return gMap[i].fProc;
1019 }
1020 }
1021 sk_throw();
1022 return NULL;
1023}
1024
1025// return the minimum legal bitdepth (by png standards) for this many colortable
1026// entries. SkBitmap always stores in 8bits per pixel, but for colorcount <= 16,
1027// we can use fewer bits per in png
1028static int computeBitDepth(int colorCount) {
1029#if 0
1030 int bits = SkNextLog2(colorCount);
1031 SkASSERT(bits >= 1 && bits <= 8);
1032 // now we need bits itself to be a power of 2 (e.g. 1, 2, 4, 8)
1033 return SkNextPow2(bits);
1034#else
1035 // for the moment, we don't know how to pack bitdepth < 8
1036 return 8;
1037#endif
1038}
1039
1040/* Pack palette[] with the corresponding colors, and if hasAlpha is true, also
1041 pack trans[] and return the number of trans[] entries written. If hasAlpha
1042 is false, the return value will always be 0.
rmistry@google.comd6176b02012-08-23 18:14:13 +00001043
reed@android.com8a1c16f2008-12-17 15:59:43 +00001044 Note: this routine takes care of unpremultiplying the RGB values when we
1045 have alpha in the colortable, since png doesn't support premul colors
1046*/
reed@android.com6f252972009-01-14 16:46:16 +00001047static inline int pack_palette(SkColorTable* ctable,
reed@android.comb50a60c2009-01-14 17:51:08 +00001048 png_color* SK_RESTRICT palette,
1049 png_byte* SK_RESTRICT trans, bool hasAlpha) {
mtklein775b8192014-12-02 09:11:25 -08001050 const SkPMColor* SK_RESTRICT colors = ctable ? ctable->readColors() : NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001051 const int ctCount = ctable->count();
1052 int i, num_trans = 0;
1053
1054 if (hasAlpha) {
1055 /* first see if we have some number of fully opaque at the end of the
1056 ctable. PNG allows num_trans < num_palette, but all of the trans
1057 entries must come first in the palette. If I was smarter, I'd
1058 reorder the indices and ctable so that all non-opaque colors came
1059 first in the palette. But, since that would slow down the encode,
1060 I'm leaving the indices and ctable order as is, and just looking
1061 at the tail of the ctable for opaqueness.
1062 */
1063 num_trans = ctCount;
1064 for (i = ctCount - 1; i >= 0; --i) {
1065 if (SkGetPackedA32(colors[i]) != 0xFF) {
1066 break;
1067 }
1068 num_trans -= 1;
1069 }
rmistry@google.comd6176b02012-08-23 18:14:13 +00001070
reed@android.com8a1c16f2008-12-17 15:59:43 +00001071 const SkUnPreMultiply::Scale* SK_RESTRICT table =
1072 SkUnPreMultiply::GetScaleTable();
1073
1074 for (i = 0; i < num_trans; i++) {
1075 const SkPMColor c = *colors++;
1076 const unsigned a = SkGetPackedA32(c);
1077 const SkUnPreMultiply::Scale s = table[a];
1078 trans[i] = a;
1079 palette[i].red = SkUnPreMultiply::ApplyScale(s, SkGetPackedR32(c));
1080 palette[i].green = SkUnPreMultiply::ApplyScale(s,SkGetPackedG32(c));
1081 palette[i].blue = SkUnPreMultiply::ApplyScale(s, SkGetPackedB32(c));
rmistry@google.comd6176b02012-08-23 18:14:13 +00001082 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001083 // now fall out of this if-block to use common code for the trailing
1084 // opaque entries
1085 }
rmistry@google.comd6176b02012-08-23 18:14:13 +00001086
reed@android.com8a1c16f2008-12-17 15:59:43 +00001087 // these (remaining) entries are opaque
1088 for (i = num_trans; i < ctCount; i++) {
1089 SkPMColor c = *colors++;
1090 palette[i].red = SkGetPackedR32(c);
1091 palette[i].green = SkGetPackedG32(c);
1092 palette[i].blue = SkGetPackedB32(c);
1093 }
1094 return num_trans;
1095}
1096
1097class SkPNGImageEncoder : public SkImageEncoder {
1098protected:
commit-bot@chromium.orga936e372013-03-14 14:42:18 +00001099 virtual bool onEncode(SkWStream* stream, const SkBitmap& bm, int quality) SK_OVERRIDE;
tomhudson@google.com5c210c72011-07-28 21:06:40 +00001100private:
1101 bool doEncode(SkWStream* stream, const SkBitmap& bm,
1102 const bool& hasAlpha, int colorType,
reed0689d7b2014-06-14 05:30:20 -07001103 int bitDepth, SkColorType ct,
tomhudson@google.com5c210c72011-07-28 21:06:40 +00001104 png_color_8& sig_bit);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +00001105
1106 typedef SkImageEncoder INHERITED;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001107};
1108
reed0689d7b2014-06-14 05:30:20 -07001109bool SkPNGImageEncoder::onEncode(SkWStream* stream, const SkBitmap& bitmap, int /*quality*/) {
1110 SkColorType ct = bitmap.colorType();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001111
1112 const bool hasAlpha = !bitmap.isOpaque();
1113 int colorType = PNG_COLOR_MASK_COLOR;
1114 int bitDepth = 8; // default for color
1115 png_color_8 sig_bit;
1116
reed0689d7b2014-06-14 05:30:20 -07001117 switch (ct) {
1118 case kIndex_8_SkColorType:
reed@android.com8a1c16f2008-12-17 15:59:43 +00001119 colorType |= PNG_COLOR_MASK_PALETTE;
1120 // fall through to the ARGB_8888 case
reed0689d7b2014-06-14 05:30:20 -07001121 case kN32_SkColorType:
reed@android.com8a1c16f2008-12-17 15:59:43 +00001122 sig_bit.red = 8;
1123 sig_bit.green = 8;
1124 sig_bit.blue = 8;
1125 sig_bit.alpha = 8;
1126 break;
reed0689d7b2014-06-14 05:30:20 -07001127 case kARGB_4444_SkColorType:
reed@android.com8a1c16f2008-12-17 15:59:43 +00001128 sig_bit.red = 4;
1129 sig_bit.green = 4;
1130 sig_bit.blue = 4;
1131 sig_bit.alpha = 4;
1132 break;
reed0689d7b2014-06-14 05:30:20 -07001133 case kRGB_565_SkColorType:
reed@android.com8a1c16f2008-12-17 15:59:43 +00001134 sig_bit.red = 5;
1135 sig_bit.green = 6;
1136 sig_bit.blue = 5;
1137 sig_bit.alpha = 0;
1138 break;
1139 default:
1140 return false;
1141 }
rmistry@google.comd6176b02012-08-23 18:14:13 +00001142
reed@android.com8a1c16f2008-12-17 15:59:43 +00001143 if (hasAlpha) {
1144 // don't specify alpha if we're a palette, even if our ctable has alpha
1145 if (!(colorType & PNG_COLOR_MASK_PALETTE)) {
1146 colorType |= PNG_COLOR_MASK_ALPHA;
1147 }
1148 } else {
1149 sig_bit.alpha = 0;
1150 }
rmistry@google.comd6176b02012-08-23 18:14:13 +00001151
reed@android.com8a1c16f2008-12-17 15:59:43 +00001152 SkAutoLockPixels alp(bitmap);
1153 // readyToDraw checks for pixels (and colortable if that is required)
1154 if (!bitmap.readyToDraw()) {
1155 return false;
1156 }
1157
1158 // we must do this after we have locked the pixels
1159 SkColorTable* ctable = bitmap.getColorTable();
bsalomon49f085d2014-09-05 13:34:00 -07001160 if (ctable) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001161 if (ctable->count() == 0) {
1162 return false;
1163 }
1164 // check if we can store in fewer than 8 bits
1165 bitDepth = computeBitDepth(ctable->count());
1166 }
1167
reed0689d7b2014-06-14 05:30:20 -07001168 return doEncode(stream, bitmap, hasAlpha, colorType, bitDepth, ct, sig_bit);
tomhudson@google.com5c210c72011-07-28 21:06:40 +00001169}
1170
1171bool SkPNGImageEncoder::doEncode(SkWStream* stream, const SkBitmap& bitmap,
1172 const bool& hasAlpha, int colorType,
reed0689d7b2014-06-14 05:30:20 -07001173 int bitDepth, SkColorType ct,
tomhudson@google.com5c210c72011-07-28 21:06:40 +00001174 png_color_8& sig_bit) {
1175
reed@android.com8a1c16f2008-12-17 15:59:43 +00001176 png_structp png_ptr;
1177 png_infop info_ptr;
1178
1179 png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, sk_error_fn,
1180 NULL);
1181 if (NULL == png_ptr) {
1182 return false;
1183 }
1184
1185 info_ptr = png_create_info_struct(png_ptr);
1186 if (NULL == info_ptr) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +00001187 png_destroy_write_struct(&png_ptr, png_infopp_NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001188 return false;
1189 }
1190
1191 /* Set error handling. REQUIRED if you aren't supplying your own
1192 * error handling functions in the png_create_write_struct() call.
1193 */
1194 if (setjmp(png_jmpbuf(png_ptr))) {
1195 png_destroy_write_struct(&png_ptr, &info_ptr);
1196 return false;
1197 }
1198
commit-bot@chromium.orga936e372013-03-14 14:42:18 +00001199 png_set_write_fn(png_ptr, (void*)stream, sk_write_fn, png_flush_ptr_NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001200
1201 /* Set the image information here. Width and height are up to 2^31,
1202 * bit_depth is one of 1, 2, 4, 8, or 16, but valid values also depend on
1203 * the color_type selected. color_type is one of PNG_COLOR_TYPE_GRAY,
1204 * PNG_COLOR_TYPE_GRAY_ALPHA, PNG_COLOR_TYPE_PALETTE, PNG_COLOR_TYPE_RGB,
1205 * or PNG_COLOR_TYPE_RGB_ALPHA. interlace is either PNG_INTERLACE_NONE or
1206 * PNG_INTERLACE_ADAM7, and the compression_type and filter_type MUST
1207 * currently be PNG_COMPRESSION_TYPE_BASE and PNG_FILTER_TYPE_BASE. REQUIRED
1208 */
1209
1210 png_set_IHDR(png_ptr, info_ptr, bitmap.width(), bitmap.height(),
1211 bitDepth, colorType,
1212 PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE,
1213 PNG_FILTER_TYPE_BASE);
1214
reed@android.com61898772009-07-07 19:38:01 +00001215 // set our colortable/trans arrays if needed
1216 png_color paletteColors[256];
1217 png_byte trans[256];
reed0689d7b2014-06-14 05:30:20 -07001218 if (kIndex_8_SkColorType == ct) {
reed@android.com61898772009-07-07 19:38:01 +00001219 SkColorTable* ct = bitmap.getColorTable();
1220 int numTrans = pack_palette(ct, paletteColors, trans, hasAlpha);
1221 png_set_PLTE(png_ptr, info_ptr, paletteColors, ct->count());
1222 if (numTrans > 0) {
1223 png_set_tRNS(png_ptr, info_ptr, trans, numTrans, NULL);
1224 }
1225 }
kkinnunen93b255b2014-10-19 22:07:23 -07001226#ifdef PNG_sBIT_SUPPORTED
reed@android.com8a1c16f2008-12-17 15:59:43 +00001227 png_set_sBIT(png_ptr, info_ptr, &sig_bit);
kkinnunen93b255b2014-10-19 22:07:23 -07001228#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +00001229 png_write_info(png_ptr, info_ptr);
1230
1231 const char* srcImage = (const char*)bitmap.getPixels();
1232 SkAutoSMalloc<1024> rowStorage(bitmap.width() << 2);
1233 char* storage = (char*)rowStorage.get();
reed0689d7b2014-06-14 05:30:20 -07001234 transform_scanline_proc proc = choose_proc(ct, hasAlpha);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001235
1236 for (int y = 0; y < bitmap.height(); y++) {
1237 png_bytep row_ptr = (png_bytep)storage;
1238 proc(srcImage, bitmap.width(), storage);
1239 png_write_rows(png_ptr, &row_ptr, 1);
1240 srcImage += bitmap.rowBytes();
1241 }
1242
1243 png_write_end(png_ptr, info_ptr);
1244
1245 /* clean up after the write, and free any memory allocated */
1246 png_destroy_write_struct(&png_ptr, &info_ptr);
1247 return true;
1248}
1249
reed@android.com00bf85a2009-01-22 13:04:56 +00001250///////////////////////////////////////////////////////////////////////////////
robertphillips@google.comec51cb82012-03-23 18:13:47 +00001251DEFINE_DECODER_CREATOR(PNGImageDecoder);
1252DEFINE_ENCODER_CREATOR(PNGImageEncoder);
1253///////////////////////////////////////////////////////////////////////////////
reed@android.com00bf85a2009-01-22 13:04:56 +00001254
scroggo@google.comb5571b32013-09-25 21:34:24 +00001255static bool is_png(SkStreamRewindable* stream) {
reed@android.com00bf85a2009-01-22 13:04:56 +00001256 char buf[PNG_BYTES_TO_CHECK];
1257 if (stream->read(buf, PNG_BYTES_TO_CHECK) == PNG_BYTES_TO_CHECK &&
1258 !png_sig_cmp((png_bytep) buf, (png_size_t)0, PNG_BYTES_TO_CHECK)) {
scroggo@google.com39edf4c2013-04-25 17:33:51 +00001259 return true;
1260 }
1261 return false;
1262}
1263
scroggo@google.comb5571b32013-09-25 21:34:24 +00001264SkImageDecoder* sk_libpng_dfactory(SkStreamRewindable* stream) {
scroggo@google.com39edf4c2013-04-25 17:33:51 +00001265 if (is_png(stream)) {
reed@android.com00bf85a2009-01-22 13:04:56 +00001266 return SkNEW(SkPNGImageDecoder);
1267 }
1268 return NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001269}
1270
scroggo@google.comb5571b32013-09-25 21:34:24 +00001271static SkImageDecoder::Format get_format_png(SkStreamRewindable* stream) {
scroggo@google.com39edf4c2013-04-25 17:33:51 +00001272 if (is_png(stream)) {
1273 return SkImageDecoder::kPNG_Format;
1274 }
1275 return SkImageDecoder::kUnknown_Format;
1276}
1277
reed@android.comdfee5792010-04-15 14:24:50 +00001278SkImageEncoder* sk_libpng_efactory(SkImageEncoder::Type t) {
reed@android.com00bf85a2009-01-22 13:04:56 +00001279 return (SkImageEncoder::kPNG_Type == t) ? SkNEW(SkPNGImageEncoder) : NULL;
1280}
1281
mtklein@google.combd6343b2013-09-04 17:20:18 +00001282static SkImageDecoder_DecodeReg gDReg(sk_libpng_dfactory);
1283static SkImageDecoder_FormatReg gFormatReg(get_format_png);
1284static SkImageEncoder_EncodeReg gEReg(sk_libpng_efactory);