blob: c3bba6e07af1131ca466901c7a0ac74bab8f1cef [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 }
mtklein72c9faa2015-01-09 10:06:39 -080081 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
mtklein72c9faa2015-01-09 10:06:39 -080091 bool onBuildTileIndex(SkStreamRewindable *stream, int *width, int *height) SK_OVERRIDE;
92 bool onDecodeSubset(SkBitmap* bitmap, const SkIRect& region) SK_OVERRIDE;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +000093#endif
mtklein72c9faa2015-01-09 10:06:39 -080094 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
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000617 // If the image has alpha and the decoder wants unpremultiplied
reed0689d7b2014-06-14 05:30:20 -0700618 // colors, the only supported colortype is 8888.
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000619 if (this->getRequireUnpremultipliedColors() && *hasAlphap) {
reed6c225732014-06-09 19:52:07 -0700620 *colorTypep = kN32_SkColorType;
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000621 }
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000622
623 if (fImageIndex != NULL) {
reed6c225732014-06-09 19:52:07 -0700624 if (kUnknown_SkColorType == fImageIndex->fColorType) {
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000625 // This is the first time for this subset decode. From now on,
reed0689d7b2014-06-14 05:30:20 -0700626 // all decodes must be in the same colortype.
reed6c225732014-06-09 19:52:07 -0700627 fImageIndex->fColorType = *colorTypep;
628 } else if (fImageIndex->fColorType != *colorTypep) {
629 // Requesting a different colortype for a subsequent decode is not
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000630 // supported. Report failure before we make changes to png_ptr.
631 return false;
632 }
633 }
634
reed6c225732014-06-09 19:52:07 -0700635 bool convertGrayToRGB = PNG_COLOR_TYPE_GRAY == colorType && *colorTypep != kAlpha_8_SkColorType;
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000636
637 // Unless the user is requesting A8, convert a grayscale image into RGB.
638 // GRAY_ALPHA will always be converted to RGB
639 if (convertGrayToRGB || colorType == PNG_COLOR_TYPE_GRAY_ALPHA) {
640 png_set_gray_to_rgb(png_ptr);
641 }
642
643 // Add filler (or alpha) byte (after each RGB triplet) if necessary.
644 if (colorType == PNG_COLOR_TYPE_RGB || convertGrayToRGB) {
645 png_set_filler(png_ptr, 0xff, PNG_FILLER_AFTER);
646 }
647
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000648 return true;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000649}
650
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000651typedef uint32_t (*PackColorProc)(U8CPU a, U8CPU r, U8CPU g, U8CPU b);
652
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000653bool SkPNGImageDecoder::decodePalette(png_structp png_ptr, png_infop info_ptr,
654 bool *hasAlphap, bool *reallyHasAlphap,
655 SkColorTable **colorTablep) {
656 int numPalette;
657 png_colorp palette;
658 png_bytep trans;
659 int numTrans;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000660
661 png_get_PLTE(png_ptr, info_ptr, &palette, &numPalette);
662
663 /* BUGGY IMAGE WORKAROUND
664
665 We hit some images (e.g. fruit_.png) who contain bytes that are == colortable_count
666 which is a problem since we use the byte as an index. To work around this we grow
667 the colortable by 1 (if its < 256) and duplicate the last color into that slot.
668 */
669 int colorCount = numPalette + (numPalette < 256);
reed@google.com0a6151d2013-10-10 14:44:56 +0000670 SkPMColor colorStorage[256]; // worst-case storage
671 SkPMColor* colorPtr = colorStorage;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000672
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000673 if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
674 png_get_tRNS(png_ptr, info_ptr, &trans, &numTrans, NULL);
675 *hasAlphap = (numTrans > 0);
676 } else {
677 numTrans = 0;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000678 }
reed@google.com0a6151d2013-10-10 14:44:56 +0000679
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000680 // check for bad images that might make us crash
681 if (numTrans > numPalette) {
682 numTrans = numPalette;
683 }
684
685 int index = 0;
686 int transLessThanFF = 0;
687
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000688 // Choose which function to use to create the color table. If the final destination's
reed0689d7b2014-06-14 05:30:20 -0700689 // colortype is unpremultiplied, the color table will store unpremultiplied colors.
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000690 PackColorProc proc;
691 if (this->getRequireUnpremultipliedColors()) {
692 proc = &SkPackARGB32NoCheck;
693 } else {
694 proc = &SkPreMultiplyARGB;
695 }
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000696 for (; index < numTrans; index++) {
697 transLessThanFF |= (int)*trans - 0xFF;
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000698 *colorPtr++ = proc(*trans++, palette->red, palette->green, palette->blue);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000699 palette++;
700 }
reed@google.com0a6151d2013-10-10 14:44:56 +0000701 bool reallyHasAlpha = (transLessThanFF < 0);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000702
703 for (; index < numPalette; index++) {
704 *colorPtr++ = SkPackARGB32(0xFF, palette->red, palette->green, palette->blue);
705 palette++;
706 }
707
708 // see BUGGY IMAGE WORKAROUND comment above
709 if (numPalette < 256) {
710 *colorPtr = colorPtr[-1];
711 }
reed@google.com0a6151d2013-10-10 14:44:56 +0000712
reedc5e15a12014-09-29 12:10:27 -0700713 *colorTablep = SkNEW_ARGS(SkColorTable, (colorStorage, colorCount));
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000714 *reallyHasAlphap = reallyHasAlpha;
715 return true;
716}
717
718#ifdef SK_BUILD_FOR_ANDROID
719
scroggo@google.comb5571b32013-09-25 21:34:24 +0000720bool SkPNGImageDecoder::onBuildTileIndex(SkStreamRewindable* sk_stream, int *width, int *height) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000721 png_structp png_ptr;
722 png_infop info_ptr;
723
724 if (!onDecodeInit(sk_stream, &png_ptr, &info_ptr)) {
725 return false;
726 }
727
728 if (setjmp(png_jmpbuf(png_ptr)) != 0) {
729 png_destroy_read_struct(&png_ptr, &info_ptr, png_infopp_NULL);
730 return false;
731 }
732
733 png_uint_32 origWidth, origHeight;
734 int bitDepth, colorType;
735 png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
736 &colorType, int_p_NULL, int_p_NULL, int_p_NULL);
737
738 *width = origWidth;
739 *height = origHeight;
740
741 png_build_index(png_ptr);
742
743 if (fImageIndex) {
744 SkDELETE(fImageIndex);
745 }
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000746 fImageIndex = SkNEW_ARGS(SkPNGImageIndex, (sk_stream, png_ptr, info_ptr));
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000747
748 return true;
749}
750
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000751bool SkPNGImageDecoder::onDecodeSubset(SkBitmap* bm, const SkIRect& region) {
752 if (NULL == fImageIndex) {
753 return false;
754 }
755
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000756 png_structp png_ptr = fImageIndex->fPng_ptr;
757 png_infop info_ptr = fImageIndex->fInfo_ptr;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000758 if (setjmp(png_jmpbuf(png_ptr))) {
759 return false;
760 }
761
762 png_uint_32 origWidth, origHeight;
reed6c225732014-06-09 19:52:07 -0700763 int bitDepth, pngColorType, interlaceType;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000764 png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
reed6c225732014-06-09 19:52:07 -0700765 &pngColorType, &interlaceType, int_p_NULL, int_p_NULL);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000766
767 SkIRect rect = SkIRect::MakeWH(origWidth, origHeight);
768
769 if (!rect.intersect(region)) {
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000770 // If the requested region is entirely outside the image, just
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000771 // returns false
772 return false;
773 }
774
reed6c225732014-06-09 19:52:07 -0700775 SkColorType colorType;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000776 bool hasAlpha = false;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000777 SkPMColor theTranspColor = 0; // 0 tells us not to try to match
778
reed0689d7b2014-06-14 05:30:20 -0700779 if (!this->getBitmapColorType(png_ptr, info_ptr, &colorType, &hasAlpha, &theTranspColor)) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000780 return false;
781 }
782
783 const int sampleSize = this->getSampleSize();
784 SkScaledBitmapSampler sampler(origWidth, rect.height(), sampleSize);
785
786 SkBitmap decodedBitmap;
reed6c225732014-06-09 19:52:07 -0700787 decodedBitmap.setInfo(SkImageInfo::Make(sampler.scaledWidth(), sampler.scaledHeight(),
788 colorType, kPremul_SkAlphaType));
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000789
790 // from here down we are concerned with colortables and pixels
791
792 // we track if we actually see a non-opaque pixels, since sometimes a PNG sets its colortype
793 // to |= PNG_COLOR_MASK_ALPHA, but all of its pixels are in fact opaque. We care, since we
794 // draw lots faster if we can flag the bitmap has being opaque
795 bool reallyHasAlpha = false;
796 SkColorTable* colorTable = NULL;
797
reed6c225732014-06-09 19:52:07 -0700798 if (pngColorType == PNG_COLOR_TYPE_PALETTE) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000799 decodePalette(png_ptr, info_ptr, &hasAlpha, &reallyHasAlpha, &colorTable);
800 }
801
802 SkAutoUnref aur(colorTable);
803
804 // Check ahead of time if the swap(dest, src) is possible.
805 // If yes, then we will stick to AllocPixelRef since it's cheaper with the swap happening.
806 // If no, then we will use alloc to allocate pixels to prevent garbage collection.
807 int w = rect.width() / sampleSize;
808 int h = rect.height() / sampleSize;
809 const bool swapOnly = (rect == region) && (w == decodedBitmap.width()) &&
810 (h == decodedBitmap.height()) && bm->isNull();
reed6c225732014-06-09 19:52:07 -0700811 const bool needColorTable = kIndex_8_SkColorType == colorType;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000812 if (swapOnly) {
813 if (!this->allocPixelRef(&decodedBitmap, needColorTable ? colorTable : NULL)) {
814 return false;
815 }
816 } else {
reed84825042014-09-02 12:50:45 -0700817 if (!decodedBitmap.tryAllocPixels(NULL, needColorTable ? colorTable : NULL)) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000818 return false;
819 }
820 }
821 SkAutoLockPixels alp(decodedBitmap);
822
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000823 /* Turn on interlace handling. REQUIRED if you are not using
824 * png_read_image(). To see how to handle interlacing passes,
825 * see the png_read_row() method below:
826 */
827 const int number_passes = (interlaceType != PNG_INTERLACE_NONE) ?
828 png_set_interlace_handling(png_ptr) : 1;
829
830 /* Optional call to gamma correct and add the background to the palette
831 * and update info structure. REQUIRED if you are expecting libpng to
832 * update the palette for you (ie you selected such a transform above).
833 */
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000834
835 // Direct access to png_ptr fields is deprecated in libpng > 1.2.
836#if defined(PNG_1_0_X) || defined (PNG_1_2_X)
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000837 png_ptr->pass = 0;
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000838#else
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000839 // FIXME: This sets pass as desired, but also sets iwidth. Is that ok?
840 png_set_interlaced_pass(png_ptr, 0);
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000841#endif
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000842 png_read_update_info(png_ptr, info_ptr);
843
844 int actualTop = rect.fTop;
845
reed6c225732014-06-09 19:52:07 -0700846 if ((kAlpha_8_SkColorType == colorType || kIndex_8_SkColorType == colorType)
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000847 && 1 == sampleSize) {
reed6c225732014-06-09 19:52:07 -0700848 if (kAlpha_8_SkColorType == colorType) {
scroggo@google.com5ee18dd2013-10-21 20:47:31 +0000849 // For an A8 bitmap, we assume there is an alpha for speed. It is
850 // possible the bitmap is opaque, but that is an unlikely use case
851 // since it would not be very interesting.
852 reallyHasAlpha = true;
853 // A8 is only allowed if the original was GRAY.
reed6c225732014-06-09 19:52:07 -0700854 SkASSERT(PNG_COLOR_TYPE_GRAY == pngColorType);
scroggo@google.com5ee18dd2013-10-21 20:47:31 +0000855 }
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000856
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000857 for (int i = 0; i < number_passes; i++) {
858 png_configure_decoder(png_ptr, &actualTop, i);
859 for (int j = 0; j < rect.fTop - actualTop; j++) {
860 uint8_t* bmRow = decodedBitmap.getAddr8(0, 0);
861 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
862 }
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000863 png_uint_32 bitmapHeight = (png_uint_32) decodedBitmap.height();
864 for (png_uint_32 y = 0; y < bitmapHeight; y++) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000865 uint8_t* bmRow = decodedBitmap.getAddr8(0, y);
866 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
867 }
868 }
869 } else {
870 SkScaledBitmapSampler::SrcConfig sc;
871 int srcBytesPerPixel = 4;
872
873 if (colorTable != NULL) {
874 sc = SkScaledBitmapSampler::kIndex;
875 srcBytesPerPixel = 1;
reed6c225732014-06-09 19:52:07 -0700876 } else if (kAlpha_8_SkColorType == colorType) {
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000877 // A8 is only allowed if the original was GRAY.
reed6c225732014-06-09 19:52:07 -0700878 SkASSERT(PNG_COLOR_TYPE_GRAY == pngColorType);
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000879 sc = SkScaledBitmapSampler::kGray;
880 srcBytesPerPixel = 1;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000881 } else if (hasAlpha) {
882 sc = SkScaledBitmapSampler::kRGBA;
883 } else {
884 sc = SkScaledBitmapSampler::kRGBX;
885 }
886
887 /* We have to pass the colortable explicitly, since we may have one
888 even if our decodedBitmap doesn't, due to the request that we
889 upscale png's palette to a direct model
890 */
mtklein775b8192014-12-02 09:11:25 -0800891 const SkPMColor* colors = colorTable ? colorTable->readColors() : NULL;
892 if (!sampler.begin(&decodedBitmap, sc, *this, colors)) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000893 return false;
894 }
895 const int height = decodedBitmap.height();
896
897 if (number_passes > 1) {
898 SkAutoMalloc storage(origWidth * origHeight * srcBytesPerPixel);
899 uint8_t* base = (uint8_t*)storage.get();
900 size_t rb = origWidth * srcBytesPerPixel;
901
902 for (int i = 0; i < number_passes; i++) {
903 png_configure_decoder(png_ptr, &actualTop, i);
904 for (int j = 0; j < rect.fTop - actualTop; j++) {
scroggofc7063b2014-07-25 13:54:43 -0700905 png_read_rows(png_ptr, &base, png_bytepp_NULL, 1);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000906 }
907 uint8_t* row = base;
908 for (int32_t y = 0; y < rect.height(); y++) {
909 uint8_t* bmRow = row;
910 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
911 row += rb;
912 }
913 }
914 // now sample it
915 base += sampler.srcY0() * rb;
916 for (int y = 0; y < height; y++) {
917 reallyHasAlpha |= sampler.next(base);
918 base += sampler.srcDY() * rb;
919 }
920 } else {
921 SkAutoMalloc storage(origWidth * srcBytesPerPixel);
922 uint8_t* srcRow = (uint8_t*)storage.get();
923
924 png_configure_decoder(png_ptr, &actualTop, 0);
925 skip_src_rows(png_ptr, srcRow, sampler.srcY0());
926
927 for (int i = 0; i < rect.fTop - actualTop; i++) {
scroggofc7063b2014-07-25 13:54:43 -0700928 png_read_rows(png_ptr, &srcRow, png_bytepp_NULL, 1);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000929 }
930 for (int y = 0; y < height; y++) {
931 uint8_t* tmp = srcRow;
932 png_read_rows(png_ptr, &tmp, png_bytepp_NULL, 1);
933 reallyHasAlpha |= sampler.next(srcRow);
934 if (y < height - 1) {
935 skip_src_rows(png_ptr, srcRow, sampler.srcDY() - 1);
936 }
937 }
938 }
939 }
940
941 if (0 != theTranspColor) {
942 reallyHasAlpha |= substituteTranspColor(&decodedBitmap, theTranspColor);
943 }
scroggo@google.com5ee18dd2013-10-21 20:47:31 +0000944 if (reallyHasAlpha && this->getRequireUnpremultipliedColors()) {
reed6c225732014-06-09 19:52:07 -0700945 switch (decodedBitmap.colorType()) {
946 case kIndex_8_SkColorType:
scroggo@google.com5ee18dd2013-10-21 20:47:31 +0000947 // Fall through.
reed6c225732014-06-09 19:52:07 -0700948 case kARGB_4444_SkColorType:
949 // We have chosen not to support unpremul for these colortypess.
scroggo@google.com5ee18dd2013-10-21 20:47:31 +0000950 return false;
951 default: {
952 // Fall through to finish the decode. This config either
953 // supports unpremul or it is irrelevant because it has no
954 // alpha (or only alpha).
955 // These brackets prevent a warning.
956 }
957 }
commit-bot@chromium.org546f70c2013-10-03 17:13:38 +0000958 }
reed@google.com383a6972013-10-21 14:00:07 +0000959 SkAlphaType alphaType = kOpaque_SkAlphaType;
960 if (reallyHasAlpha) {
961 if (this->getRequireUnpremultipliedColors()) {
962 alphaType = kUnpremul_SkAlphaType;
963 } else {
964 alphaType = kPremul_SkAlphaType;
965 }
966 }
967 decodedBitmap.setAlphaType(alphaType);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000968
969 if (swapOnly) {
970 bm->swap(decodedBitmap);
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000971 return true;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000972 }
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000973 return this->cropBitmap(bm, &decodedBitmap, sampleSize, region.x(), region.y(),
974 region.width(), region.height(), 0, rect.y());
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000975}
976#endif
977
reed@android.com8a1c16f2008-12-17 15:59:43 +0000978///////////////////////////////////////////////////////////////////////////////
979
reed@android.com8a1c16f2008-12-17 15:59:43 +0000980#include "SkColorPriv.h"
981#include "SkUnPreMultiply.h"
982
983static void sk_write_fn(png_structp png_ptr, png_bytep data, png_size_t len) {
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000984 SkWStream* sk_stream = (SkWStream*)png_get_io_ptr(png_ptr);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000985 if (!sk_stream->write(data, len)) {
986 png_error(png_ptr, "sk_write_fn Error!");
987 }
988}
989
reed0689d7b2014-06-14 05:30:20 -0700990static transform_scanline_proc choose_proc(SkColorType ct, bool hasAlpha) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000991 // we don't care about search on alpha if we're kIndex8, since only the
992 // colortable packing cares about that distinction, not the pixels
reed0689d7b2014-06-14 05:30:20 -0700993 if (kIndex_8_SkColorType == ct) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000994 hasAlpha = false; // we store false in the table entries for kIndex8
995 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000996
reed@android.com8a1c16f2008-12-17 15:59:43 +0000997 static const struct {
reed0689d7b2014-06-14 05:30:20 -0700998 SkColorType fColorType;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000999 bool fHasAlpha;
1000 transform_scanline_proc fProc;
1001 } gMap[] = {
reed0689d7b2014-06-14 05:30:20 -07001002 { kRGB_565_SkColorType, false, transform_scanline_565 },
1003 { kN32_SkColorType, false, transform_scanline_888 },
1004 { kN32_SkColorType, true, transform_scanline_8888 },
1005 { kARGB_4444_SkColorType, false, transform_scanline_444 },
1006 { kARGB_4444_SkColorType, true, transform_scanline_4444 },
1007 { kIndex_8_SkColorType, false, transform_scanline_memcpy },
reed@android.com8a1c16f2008-12-17 15:59:43 +00001008 };
1009
1010 for (int i = SK_ARRAY_COUNT(gMap) - 1; i >= 0; --i) {
reed0689d7b2014-06-14 05:30:20 -07001011 if (gMap[i].fColorType == ct && gMap[i].fHasAlpha == hasAlpha) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001012 return gMap[i].fProc;
1013 }
1014 }
1015 sk_throw();
1016 return NULL;
1017}
1018
1019// return the minimum legal bitdepth (by png standards) for this many colortable
1020// entries. SkBitmap always stores in 8bits per pixel, but for colorcount <= 16,
1021// we can use fewer bits per in png
1022static int computeBitDepth(int colorCount) {
1023#if 0
1024 int bits = SkNextLog2(colorCount);
1025 SkASSERT(bits >= 1 && bits <= 8);
1026 // now we need bits itself to be a power of 2 (e.g. 1, 2, 4, 8)
1027 return SkNextPow2(bits);
1028#else
1029 // for the moment, we don't know how to pack bitdepth < 8
1030 return 8;
1031#endif
1032}
1033
1034/* Pack palette[] with the corresponding colors, and if hasAlpha is true, also
1035 pack trans[] and return the number of trans[] entries written. If hasAlpha
1036 is false, the return value will always be 0.
rmistry@google.comd6176b02012-08-23 18:14:13 +00001037
reed@android.com8a1c16f2008-12-17 15:59:43 +00001038 Note: this routine takes care of unpremultiplying the RGB values when we
1039 have alpha in the colortable, since png doesn't support premul colors
1040*/
reed@android.com6f252972009-01-14 16:46:16 +00001041static inline int pack_palette(SkColorTable* ctable,
reed@android.comb50a60c2009-01-14 17:51:08 +00001042 png_color* SK_RESTRICT palette,
1043 png_byte* SK_RESTRICT trans, bool hasAlpha) {
mtklein775b8192014-12-02 09:11:25 -08001044 const SkPMColor* SK_RESTRICT colors = ctable ? ctable->readColors() : NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001045 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:
mtklein72c9faa2015-01-09 10:06:39 -08001093 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 }
kkinnunen93b255b2014-10-19 22:07:23 -07001220#ifdef PNG_sBIT_SUPPORTED
reed@android.com8a1c16f2008-12-17 15:59:43 +00001221 png_set_sBIT(png_ptr, info_ptr, &sig_bit);
kkinnunen93b255b2014-10-19 22:07:23 -07001222#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +00001223 png_write_info(png_ptr, info_ptr);
1224
1225 const char* srcImage = (const char*)bitmap.getPixels();
1226 SkAutoSMalloc<1024> rowStorage(bitmap.width() << 2);
1227 char* storage = (char*)rowStorage.get();
reed0689d7b2014-06-14 05:30:20 -07001228 transform_scanline_proc proc = choose_proc(ct, hasAlpha);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001229
1230 for (int y = 0; y < bitmap.height(); y++) {
1231 png_bytep row_ptr = (png_bytep)storage;
1232 proc(srcImage, bitmap.width(), storage);
1233 png_write_rows(png_ptr, &row_ptr, 1);
1234 srcImage += bitmap.rowBytes();
1235 }
1236
1237 png_write_end(png_ptr, info_ptr);
1238
1239 /* clean up after the write, and free any memory allocated */
1240 png_destroy_write_struct(&png_ptr, &info_ptr);
1241 return true;
1242}
1243
reed@android.com00bf85a2009-01-22 13:04:56 +00001244///////////////////////////////////////////////////////////////////////////////
robertphillips@google.comec51cb82012-03-23 18:13:47 +00001245DEFINE_DECODER_CREATOR(PNGImageDecoder);
1246DEFINE_ENCODER_CREATOR(PNGImageEncoder);
1247///////////////////////////////////////////////////////////////////////////////
reed@android.com00bf85a2009-01-22 13:04:56 +00001248
scroggo@google.comb5571b32013-09-25 21:34:24 +00001249static bool is_png(SkStreamRewindable* stream) {
reed@android.com00bf85a2009-01-22 13:04:56 +00001250 char buf[PNG_BYTES_TO_CHECK];
1251 if (stream->read(buf, PNG_BYTES_TO_CHECK) == PNG_BYTES_TO_CHECK &&
1252 !png_sig_cmp((png_bytep) buf, (png_size_t)0, PNG_BYTES_TO_CHECK)) {
scroggo@google.com39edf4c2013-04-25 17:33:51 +00001253 return true;
1254 }
1255 return false;
1256}
1257
scroggo@google.comb5571b32013-09-25 21:34:24 +00001258SkImageDecoder* sk_libpng_dfactory(SkStreamRewindable* stream) {
scroggo@google.com39edf4c2013-04-25 17:33:51 +00001259 if (is_png(stream)) {
reed@android.com00bf85a2009-01-22 13:04:56 +00001260 return SkNEW(SkPNGImageDecoder);
1261 }
1262 return NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001263}
1264
scroggo@google.comb5571b32013-09-25 21:34:24 +00001265static SkImageDecoder::Format get_format_png(SkStreamRewindable* stream) {
scroggo@google.com39edf4c2013-04-25 17:33:51 +00001266 if (is_png(stream)) {
1267 return SkImageDecoder::kPNG_Format;
1268 }
1269 return SkImageDecoder::kUnknown_Format;
1270}
1271
reed@android.comdfee5792010-04-15 14:24:50 +00001272SkImageEncoder* sk_libpng_efactory(SkImageEncoder::Type t) {
reed@android.com00bf85a2009-01-22 13:04:56 +00001273 return (SkImageEncoder::kPNG_Type == t) ? SkNEW(SkPNGImageEncoder) : NULL;
1274}
1275
mtklein@google.combd6343b2013-09-04 17:20:18 +00001276static SkImageDecoder_DecodeReg gDReg(sk_libpng_dfactory);
1277static SkImageDecoder_FormatReg gFormatReg(get_format_png);
1278static SkImageEncoder_EncodeReg gEReg(sk_libpng_efactory);