blob: f9ef6b7942306016c714668555d96b7c3c31fbb3 [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:
scroggoa1193e42015-01-21 12:09:53 -080056 // Takes ownership of stream.
scroggo@google.comb5571b32013-09-25 21:34:24 +000057 SkPNGImageIndex(SkStreamRewindable* stream, png_structp png_ptr, png_infop info_ptr)
scroggo@google.comc70a3aa2013-07-18 20:03:15 +000058 : fStream(stream)
59 , fPng_ptr(png_ptr)
60 , fInfo_ptr(info_ptr)
reed6c225732014-06-09 19:52:07 -070061 , fColorType(kUnknown_SkColorType) {
scroggo@google.comc70a3aa2013-07-18 20:03:15 +000062 SkASSERT(stream != NULL);
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
scroggoa1193e42015-01-21 12:09:53 -080070 SkAutoTDelete<SkStreamRewindable> fStream;
scroggo@google.comb5571b32013-09-25 21:34:24 +000071 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) {
scroggoa1193e42015-01-21 12:09:53 -0800721 SkAutoTDelete<SkStreamRewindable> streamDeleter(sk_stream);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000722 png_structp png_ptr;
723 png_infop info_ptr;
724
725 if (!onDecodeInit(sk_stream, &png_ptr, &info_ptr)) {
726 return false;
727 }
728
729 if (setjmp(png_jmpbuf(png_ptr)) != 0) {
730 png_destroy_read_struct(&png_ptr, &info_ptr, png_infopp_NULL);
731 return false;
732 }
733
734 png_uint_32 origWidth, origHeight;
735 int bitDepth, colorType;
736 png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
737 &colorType, int_p_NULL, int_p_NULL, int_p_NULL);
738
739 *width = origWidth;
740 *height = origHeight;
741
742 png_build_index(png_ptr);
743
744 if (fImageIndex) {
745 SkDELETE(fImageIndex);
746 }
scroggoa1193e42015-01-21 12:09:53 -0800747 fImageIndex = SkNEW_ARGS(SkPNGImageIndex, (streamDeleter.detach(), png_ptr, info_ptr));
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000748
749 return true;
750}
751
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000752bool SkPNGImageDecoder::onDecodeSubset(SkBitmap* bm, const SkIRect& region) {
753 if (NULL == fImageIndex) {
754 return false;
755 }
756
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000757 png_structp png_ptr = fImageIndex->fPng_ptr;
758 png_infop info_ptr = fImageIndex->fInfo_ptr;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000759 if (setjmp(png_jmpbuf(png_ptr))) {
760 return false;
761 }
762
763 png_uint_32 origWidth, origHeight;
reed6c225732014-06-09 19:52:07 -0700764 int bitDepth, pngColorType, interlaceType;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000765 png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
reed6c225732014-06-09 19:52:07 -0700766 &pngColorType, &interlaceType, int_p_NULL, int_p_NULL);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000767
768 SkIRect rect = SkIRect::MakeWH(origWidth, origHeight);
769
770 if (!rect.intersect(region)) {
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000771 // If the requested region is entirely outside the image, just
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000772 // returns false
773 return false;
774 }
775
reed6c225732014-06-09 19:52:07 -0700776 SkColorType colorType;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000777 bool hasAlpha = false;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000778 SkPMColor theTranspColor = 0; // 0 tells us not to try to match
779
reed0689d7b2014-06-14 05:30:20 -0700780 if (!this->getBitmapColorType(png_ptr, info_ptr, &colorType, &hasAlpha, &theTranspColor)) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000781 return false;
782 }
783
784 const int sampleSize = this->getSampleSize();
785 SkScaledBitmapSampler sampler(origWidth, rect.height(), sampleSize);
786
787 SkBitmap decodedBitmap;
reed6c225732014-06-09 19:52:07 -0700788 decodedBitmap.setInfo(SkImageInfo::Make(sampler.scaledWidth(), sampler.scaledHeight(),
789 colorType, kPremul_SkAlphaType));
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000790
791 // from here down we are concerned with colortables and pixels
792
793 // we track if we actually see a non-opaque pixels, since sometimes a PNG sets its colortype
794 // to |= PNG_COLOR_MASK_ALPHA, but all of its pixels are in fact opaque. We care, since we
795 // draw lots faster if we can flag the bitmap has being opaque
796 bool reallyHasAlpha = false;
797 SkColorTable* colorTable = NULL;
798
reed6c225732014-06-09 19:52:07 -0700799 if (pngColorType == PNG_COLOR_TYPE_PALETTE) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000800 decodePalette(png_ptr, info_ptr, &hasAlpha, &reallyHasAlpha, &colorTable);
801 }
802
803 SkAutoUnref aur(colorTable);
804
805 // Check ahead of time if the swap(dest, src) is possible.
806 // If yes, then we will stick to AllocPixelRef since it's cheaper with the swap happening.
807 // If no, then we will use alloc to allocate pixels to prevent garbage collection.
808 int w = rect.width() / sampleSize;
809 int h = rect.height() / sampleSize;
810 const bool swapOnly = (rect == region) && (w == decodedBitmap.width()) &&
811 (h == decodedBitmap.height()) && bm->isNull();
reed6c225732014-06-09 19:52:07 -0700812 const bool needColorTable = kIndex_8_SkColorType == colorType;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000813 if (swapOnly) {
814 if (!this->allocPixelRef(&decodedBitmap, needColorTable ? colorTable : NULL)) {
815 return false;
816 }
817 } else {
reed84825042014-09-02 12:50:45 -0700818 if (!decodedBitmap.tryAllocPixels(NULL, needColorTable ? colorTable : NULL)) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000819 return false;
820 }
821 }
822 SkAutoLockPixels alp(decodedBitmap);
823
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000824 /* Turn on interlace handling. REQUIRED if you are not using
825 * png_read_image(). To see how to handle interlacing passes,
826 * see the png_read_row() method below:
827 */
828 const int number_passes = (interlaceType != PNG_INTERLACE_NONE) ?
829 png_set_interlace_handling(png_ptr) : 1;
830
831 /* Optional call to gamma correct and add the background to the palette
832 * and update info structure. REQUIRED if you are expecting libpng to
833 * update the palette for you (ie you selected such a transform above).
834 */
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000835
836 // Direct access to png_ptr fields is deprecated in libpng > 1.2.
837#if defined(PNG_1_0_X) || defined (PNG_1_2_X)
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000838 png_ptr->pass = 0;
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000839#else
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000840 // FIXME: This sets pass as desired, but also sets iwidth. Is that ok?
841 png_set_interlaced_pass(png_ptr, 0);
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000842#endif
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000843 png_read_update_info(png_ptr, info_ptr);
844
845 int actualTop = rect.fTop;
846
reed6c225732014-06-09 19:52:07 -0700847 if ((kAlpha_8_SkColorType == colorType || kIndex_8_SkColorType == colorType)
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000848 && 1 == sampleSize) {
reed6c225732014-06-09 19:52:07 -0700849 if (kAlpha_8_SkColorType == colorType) {
scroggo@google.com5ee18dd2013-10-21 20:47:31 +0000850 // For an A8 bitmap, we assume there is an alpha for speed. It is
851 // possible the bitmap is opaque, but that is an unlikely use case
852 // since it would not be very interesting.
853 reallyHasAlpha = true;
854 // A8 is only allowed if the original was GRAY.
reed6c225732014-06-09 19:52:07 -0700855 SkASSERT(PNG_COLOR_TYPE_GRAY == pngColorType);
scroggo@google.com5ee18dd2013-10-21 20:47:31 +0000856 }
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000857
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000858 for (int i = 0; i < number_passes; i++) {
859 png_configure_decoder(png_ptr, &actualTop, i);
860 for (int j = 0; j < rect.fTop - actualTop; j++) {
861 uint8_t* bmRow = decodedBitmap.getAddr8(0, 0);
862 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
863 }
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000864 png_uint_32 bitmapHeight = (png_uint_32) decodedBitmap.height();
865 for (png_uint_32 y = 0; y < bitmapHeight; y++) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000866 uint8_t* bmRow = decodedBitmap.getAddr8(0, y);
867 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
868 }
869 }
870 } else {
871 SkScaledBitmapSampler::SrcConfig sc;
872 int srcBytesPerPixel = 4;
873
874 if (colorTable != NULL) {
875 sc = SkScaledBitmapSampler::kIndex;
876 srcBytesPerPixel = 1;
reed6c225732014-06-09 19:52:07 -0700877 } else if (kAlpha_8_SkColorType == colorType) {
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000878 // A8 is only allowed if the original was GRAY.
reed6c225732014-06-09 19:52:07 -0700879 SkASSERT(PNG_COLOR_TYPE_GRAY == pngColorType);
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000880 sc = SkScaledBitmapSampler::kGray;
881 srcBytesPerPixel = 1;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000882 } else if (hasAlpha) {
883 sc = SkScaledBitmapSampler::kRGBA;
884 } else {
885 sc = SkScaledBitmapSampler::kRGBX;
886 }
887
888 /* We have to pass the colortable explicitly, since we may have one
889 even if our decodedBitmap doesn't, due to the request that we
890 upscale png's palette to a direct model
891 */
mtklein775b8192014-12-02 09:11:25 -0800892 const SkPMColor* colors = colorTable ? colorTable->readColors() : NULL;
893 if (!sampler.begin(&decodedBitmap, sc, *this, colors)) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000894 return false;
895 }
896 const int height = decodedBitmap.height();
897
898 if (number_passes > 1) {
899 SkAutoMalloc storage(origWidth * origHeight * srcBytesPerPixel);
900 uint8_t* base = (uint8_t*)storage.get();
901 size_t rb = origWidth * srcBytesPerPixel;
902
903 for (int i = 0; i < number_passes; i++) {
904 png_configure_decoder(png_ptr, &actualTop, i);
905 for (int j = 0; j < rect.fTop - actualTop; j++) {
scroggofc7063b2014-07-25 13:54:43 -0700906 png_read_rows(png_ptr, &base, png_bytepp_NULL, 1);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000907 }
908 uint8_t* row = base;
909 for (int32_t y = 0; y < rect.height(); y++) {
910 uint8_t* bmRow = row;
911 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
912 row += rb;
913 }
914 }
915 // now sample it
916 base += sampler.srcY0() * rb;
917 for (int y = 0; y < height; y++) {
918 reallyHasAlpha |= sampler.next(base);
919 base += sampler.srcDY() * rb;
920 }
921 } else {
922 SkAutoMalloc storage(origWidth * srcBytesPerPixel);
923 uint8_t* srcRow = (uint8_t*)storage.get();
924
925 png_configure_decoder(png_ptr, &actualTop, 0);
926 skip_src_rows(png_ptr, srcRow, sampler.srcY0());
927
928 for (int i = 0; i < rect.fTop - actualTop; i++) {
scroggofc7063b2014-07-25 13:54:43 -0700929 png_read_rows(png_ptr, &srcRow, png_bytepp_NULL, 1);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000930 }
931 for (int y = 0; y < height; y++) {
932 uint8_t* tmp = srcRow;
933 png_read_rows(png_ptr, &tmp, png_bytepp_NULL, 1);
934 reallyHasAlpha |= sampler.next(srcRow);
935 if (y < height - 1) {
936 skip_src_rows(png_ptr, srcRow, sampler.srcDY() - 1);
937 }
938 }
939 }
940 }
941
942 if (0 != theTranspColor) {
943 reallyHasAlpha |= substituteTranspColor(&decodedBitmap, theTranspColor);
944 }
scroggo@google.com5ee18dd2013-10-21 20:47:31 +0000945 if (reallyHasAlpha && this->getRequireUnpremultipliedColors()) {
reed6c225732014-06-09 19:52:07 -0700946 switch (decodedBitmap.colorType()) {
947 case kIndex_8_SkColorType:
scroggo@google.com5ee18dd2013-10-21 20:47:31 +0000948 // Fall through.
reed6c225732014-06-09 19:52:07 -0700949 case kARGB_4444_SkColorType:
950 // We have chosen not to support unpremul for these colortypess.
scroggo@google.com5ee18dd2013-10-21 20:47:31 +0000951 return false;
952 default: {
953 // Fall through to finish the decode. This config either
954 // supports unpremul or it is irrelevant because it has no
955 // alpha (or only alpha).
956 // These brackets prevent a warning.
957 }
958 }
commit-bot@chromium.org546f70c2013-10-03 17:13:38 +0000959 }
reed@google.com383a6972013-10-21 14:00:07 +0000960 SkAlphaType alphaType = kOpaque_SkAlphaType;
961 if (reallyHasAlpha) {
962 if (this->getRequireUnpremultipliedColors()) {
963 alphaType = kUnpremul_SkAlphaType;
964 } else {
965 alphaType = kPremul_SkAlphaType;
966 }
967 }
968 decodedBitmap.setAlphaType(alphaType);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000969
970 if (swapOnly) {
971 bm->swap(decodedBitmap);
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000972 return true;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000973 }
scroggo@google.com7e6fcee2013-05-03 20:14:28 +0000974 return this->cropBitmap(bm, &decodedBitmap, sampleSize, region.x(), region.y(),
975 region.width(), region.height(), 0, rect.y());
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000976}
977#endif
978
reed@android.com8a1c16f2008-12-17 15:59:43 +0000979///////////////////////////////////////////////////////////////////////////////
980
reed@android.com8a1c16f2008-12-17 15:59:43 +0000981#include "SkColorPriv.h"
982#include "SkUnPreMultiply.h"
983
984static void sk_write_fn(png_structp png_ptr, png_bytep data, png_size_t len) {
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000985 SkWStream* sk_stream = (SkWStream*)png_get_io_ptr(png_ptr);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000986 if (!sk_stream->write(data, len)) {
987 png_error(png_ptr, "sk_write_fn Error!");
988 }
989}
990
reed0689d7b2014-06-14 05:30:20 -0700991static transform_scanline_proc choose_proc(SkColorType ct, bool hasAlpha) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000992 // we don't care about search on alpha if we're kIndex8, since only the
993 // colortable packing cares about that distinction, not the pixels
reed0689d7b2014-06-14 05:30:20 -0700994 if (kIndex_8_SkColorType == ct) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000995 hasAlpha = false; // we store false in the table entries for kIndex8
996 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000997
reed@android.com8a1c16f2008-12-17 15:59:43 +0000998 static const struct {
reed0689d7b2014-06-14 05:30:20 -0700999 SkColorType fColorType;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001000 bool fHasAlpha;
1001 transform_scanline_proc fProc;
1002 } gMap[] = {
reed0689d7b2014-06-14 05:30:20 -07001003 { kRGB_565_SkColorType, false, transform_scanline_565 },
1004 { kN32_SkColorType, false, transform_scanline_888 },
1005 { kN32_SkColorType, true, transform_scanline_8888 },
1006 { kARGB_4444_SkColorType, false, transform_scanline_444 },
1007 { kARGB_4444_SkColorType, true, transform_scanline_4444 },
1008 { kIndex_8_SkColorType, false, transform_scanline_memcpy },
reed@android.com8a1c16f2008-12-17 15:59:43 +00001009 };
1010
1011 for (int i = SK_ARRAY_COUNT(gMap) - 1; i >= 0; --i) {
reed0689d7b2014-06-14 05:30:20 -07001012 if (gMap[i].fColorType == ct && gMap[i].fHasAlpha == hasAlpha) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001013 return gMap[i].fProc;
1014 }
1015 }
1016 sk_throw();
1017 return NULL;
1018}
1019
1020// return the minimum legal bitdepth (by png standards) for this many colortable
1021// entries. SkBitmap always stores in 8bits per pixel, but for colorcount <= 16,
1022// we can use fewer bits per in png
1023static int computeBitDepth(int colorCount) {
1024#if 0
1025 int bits = SkNextLog2(colorCount);
1026 SkASSERT(bits >= 1 && bits <= 8);
1027 // now we need bits itself to be a power of 2 (e.g. 1, 2, 4, 8)
1028 return SkNextPow2(bits);
1029#else
1030 // for the moment, we don't know how to pack bitdepth < 8
1031 return 8;
1032#endif
1033}
1034
1035/* Pack palette[] with the corresponding colors, and if hasAlpha is true, also
1036 pack trans[] and return the number of trans[] entries written. If hasAlpha
1037 is false, the return value will always be 0.
rmistry@google.comd6176b02012-08-23 18:14:13 +00001038
reed@android.com8a1c16f2008-12-17 15:59:43 +00001039 Note: this routine takes care of unpremultiplying the RGB values when we
1040 have alpha in the colortable, since png doesn't support premul colors
1041*/
reed@android.com6f252972009-01-14 16:46:16 +00001042static inline int pack_palette(SkColorTable* ctable,
reed@android.comb50a60c2009-01-14 17:51:08 +00001043 png_color* SK_RESTRICT palette,
1044 png_byte* SK_RESTRICT trans, bool hasAlpha) {
mtklein775b8192014-12-02 09:11:25 -08001045 const SkPMColor* SK_RESTRICT colors = ctable ? ctable->readColors() : NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001046 const int ctCount = ctable->count();
1047 int i, num_trans = 0;
1048
1049 if (hasAlpha) {
1050 /* first see if we have some number of fully opaque at the end of the
1051 ctable. PNG allows num_trans < num_palette, but all of the trans
1052 entries must come first in the palette. If I was smarter, I'd
1053 reorder the indices and ctable so that all non-opaque colors came
1054 first in the palette. But, since that would slow down the encode,
1055 I'm leaving the indices and ctable order as is, and just looking
1056 at the tail of the ctable for opaqueness.
1057 */
1058 num_trans = ctCount;
1059 for (i = ctCount - 1; i >= 0; --i) {
1060 if (SkGetPackedA32(colors[i]) != 0xFF) {
1061 break;
1062 }
1063 num_trans -= 1;
1064 }
rmistry@google.comd6176b02012-08-23 18:14:13 +00001065
reed@android.com8a1c16f2008-12-17 15:59:43 +00001066 const SkUnPreMultiply::Scale* SK_RESTRICT table =
1067 SkUnPreMultiply::GetScaleTable();
1068
1069 for (i = 0; i < num_trans; i++) {
1070 const SkPMColor c = *colors++;
1071 const unsigned a = SkGetPackedA32(c);
1072 const SkUnPreMultiply::Scale s = table[a];
1073 trans[i] = a;
1074 palette[i].red = SkUnPreMultiply::ApplyScale(s, SkGetPackedR32(c));
1075 palette[i].green = SkUnPreMultiply::ApplyScale(s,SkGetPackedG32(c));
1076 palette[i].blue = SkUnPreMultiply::ApplyScale(s, SkGetPackedB32(c));
rmistry@google.comd6176b02012-08-23 18:14:13 +00001077 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001078 // now fall out of this if-block to use common code for the trailing
1079 // opaque entries
1080 }
rmistry@google.comd6176b02012-08-23 18:14:13 +00001081
reed@android.com8a1c16f2008-12-17 15:59:43 +00001082 // these (remaining) entries are opaque
1083 for (i = num_trans; i < ctCount; i++) {
1084 SkPMColor c = *colors++;
1085 palette[i].red = SkGetPackedR32(c);
1086 palette[i].green = SkGetPackedG32(c);
1087 palette[i].blue = SkGetPackedB32(c);
1088 }
1089 return num_trans;
1090}
1091
1092class SkPNGImageEncoder : public SkImageEncoder {
1093protected:
mtklein72c9faa2015-01-09 10:06:39 -08001094 bool onEncode(SkWStream* stream, const SkBitmap& bm, int quality) SK_OVERRIDE;
tomhudson@google.com5c210c72011-07-28 21:06:40 +00001095private:
1096 bool doEncode(SkWStream* stream, const SkBitmap& bm,
1097 const bool& hasAlpha, int colorType,
reed0689d7b2014-06-14 05:30:20 -07001098 int bitDepth, SkColorType ct,
tomhudson@google.com5c210c72011-07-28 21:06:40 +00001099 png_color_8& sig_bit);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +00001100
1101 typedef SkImageEncoder INHERITED;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001102};
1103
reed0689d7b2014-06-14 05:30:20 -07001104bool SkPNGImageEncoder::onEncode(SkWStream* stream, const SkBitmap& bitmap, int /*quality*/) {
1105 SkColorType ct = bitmap.colorType();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001106
1107 const bool hasAlpha = !bitmap.isOpaque();
1108 int colorType = PNG_COLOR_MASK_COLOR;
1109 int bitDepth = 8; // default for color
1110 png_color_8 sig_bit;
1111
reed0689d7b2014-06-14 05:30:20 -07001112 switch (ct) {
1113 case kIndex_8_SkColorType:
reed@android.com8a1c16f2008-12-17 15:59:43 +00001114 colorType |= PNG_COLOR_MASK_PALETTE;
1115 // fall through to the ARGB_8888 case
reed0689d7b2014-06-14 05:30:20 -07001116 case kN32_SkColorType:
reed@android.com8a1c16f2008-12-17 15:59:43 +00001117 sig_bit.red = 8;
1118 sig_bit.green = 8;
1119 sig_bit.blue = 8;
1120 sig_bit.alpha = 8;
1121 break;
reed0689d7b2014-06-14 05:30:20 -07001122 case kARGB_4444_SkColorType:
reed@android.com8a1c16f2008-12-17 15:59:43 +00001123 sig_bit.red = 4;
1124 sig_bit.green = 4;
1125 sig_bit.blue = 4;
1126 sig_bit.alpha = 4;
1127 break;
reed0689d7b2014-06-14 05:30:20 -07001128 case kRGB_565_SkColorType:
reed@android.com8a1c16f2008-12-17 15:59:43 +00001129 sig_bit.red = 5;
1130 sig_bit.green = 6;
1131 sig_bit.blue = 5;
1132 sig_bit.alpha = 0;
1133 break;
1134 default:
1135 return false;
1136 }
rmistry@google.comd6176b02012-08-23 18:14:13 +00001137
reed@android.com8a1c16f2008-12-17 15:59:43 +00001138 if (hasAlpha) {
1139 // don't specify alpha if we're a palette, even if our ctable has alpha
1140 if (!(colorType & PNG_COLOR_MASK_PALETTE)) {
1141 colorType |= PNG_COLOR_MASK_ALPHA;
1142 }
1143 } else {
1144 sig_bit.alpha = 0;
1145 }
rmistry@google.comd6176b02012-08-23 18:14:13 +00001146
reed@android.com8a1c16f2008-12-17 15:59:43 +00001147 SkAutoLockPixels alp(bitmap);
1148 // readyToDraw checks for pixels (and colortable if that is required)
1149 if (!bitmap.readyToDraw()) {
1150 return false;
1151 }
1152
1153 // we must do this after we have locked the pixels
1154 SkColorTable* ctable = bitmap.getColorTable();
bsalomon49f085d2014-09-05 13:34:00 -07001155 if (ctable) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001156 if (ctable->count() == 0) {
1157 return false;
1158 }
1159 // check if we can store in fewer than 8 bits
1160 bitDepth = computeBitDepth(ctable->count());
1161 }
1162
reed0689d7b2014-06-14 05:30:20 -07001163 return doEncode(stream, bitmap, hasAlpha, colorType, bitDepth, ct, sig_bit);
tomhudson@google.com5c210c72011-07-28 21:06:40 +00001164}
1165
1166bool SkPNGImageEncoder::doEncode(SkWStream* stream, const SkBitmap& bitmap,
1167 const bool& hasAlpha, int colorType,
reed0689d7b2014-06-14 05:30:20 -07001168 int bitDepth, SkColorType ct,
tomhudson@google.com5c210c72011-07-28 21:06:40 +00001169 png_color_8& sig_bit) {
1170
reed@android.com8a1c16f2008-12-17 15:59:43 +00001171 png_structp png_ptr;
1172 png_infop info_ptr;
1173
1174 png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, sk_error_fn,
1175 NULL);
1176 if (NULL == png_ptr) {
1177 return false;
1178 }
1179
1180 info_ptr = png_create_info_struct(png_ptr);
1181 if (NULL == info_ptr) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +00001182 png_destroy_write_struct(&png_ptr, png_infopp_NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001183 return false;
1184 }
1185
1186 /* Set error handling. REQUIRED if you aren't supplying your own
1187 * error handling functions in the png_create_write_struct() call.
1188 */
1189 if (setjmp(png_jmpbuf(png_ptr))) {
1190 png_destroy_write_struct(&png_ptr, &info_ptr);
1191 return false;
1192 }
1193
commit-bot@chromium.orga936e372013-03-14 14:42:18 +00001194 png_set_write_fn(png_ptr, (void*)stream, sk_write_fn, png_flush_ptr_NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001195
1196 /* Set the image information here. Width and height are up to 2^31,
1197 * bit_depth is one of 1, 2, 4, 8, or 16, but valid values also depend on
1198 * the color_type selected. color_type is one of PNG_COLOR_TYPE_GRAY,
1199 * PNG_COLOR_TYPE_GRAY_ALPHA, PNG_COLOR_TYPE_PALETTE, PNG_COLOR_TYPE_RGB,
1200 * or PNG_COLOR_TYPE_RGB_ALPHA. interlace is either PNG_INTERLACE_NONE or
1201 * PNG_INTERLACE_ADAM7, and the compression_type and filter_type MUST
1202 * currently be PNG_COMPRESSION_TYPE_BASE and PNG_FILTER_TYPE_BASE. REQUIRED
1203 */
1204
1205 png_set_IHDR(png_ptr, info_ptr, bitmap.width(), bitmap.height(),
1206 bitDepth, colorType,
1207 PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE,
1208 PNG_FILTER_TYPE_BASE);
1209
reed@android.com61898772009-07-07 19:38:01 +00001210 // set our colortable/trans arrays if needed
1211 png_color paletteColors[256];
1212 png_byte trans[256];
reed0689d7b2014-06-14 05:30:20 -07001213 if (kIndex_8_SkColorType == ct) {
reed@android.com61898772009-07-07 19:38:01 +00001214 SkColorTable* ct = bitmap.getColorTable();
1215 int numTrans = pack_palette(ct, paletteColors, trans, hasAlpha);
1216 png_set_PLTE(png_ptr, info_ptr, paletteColors, ct->count());
1217 if (numTrans > 0) {
1218 png_set_tRNS(png_ptr, info_ptr, trans, numTrans, NULL);
1219 }
1220 }
kkinnunen93b255b2014-10-19 22:07:23 -07001221#ifdef PNG_sBIT_SUPPORTED
reed@android.com8a1c16f2008-12-17 15:59:43 +00001222 png_set_sBIT(png_ptr, info_ptr, &sig_bit);
kkinnunen93b255b2014-10-19 22:07:23 -07001223#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +00001224 png_write_info(png_ptr, info_ptr);
1225
1226 const char* srcImage = (const char*)bitmap.getPixels();
1227 SkAutoSMalloc<1024> rowStorage(bitmap.width() << 2);
1228 char* storage = (char*)rowStorage.get();
reed0689d7b2014-06-14 05:30:20 -07001229 transform_scanline_proc proc = choose_proc(ct, hasAlpha);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001230
1231 for (int y = 0; y < bitmap.height(); y++) {
1232 png_bytep row_ptr = (png_bytep)storage;
1233 proc(srcImage, bitmap.width(), storage);
1234 png_write_rows(png_ptr, &row_ptr, 1);
1235 srcImage += bitmap.rowBytes();
1236 }
1237
1238 png_write_end(png_ptr, info_ptr);
1239
1240 /* clean up after the write, and free any memory allocated */
1241 png_destroy_write_struct(&png_ptr, &info_ptr);
1242 return true;
1243}
1244
reed@android.com00bf85a2009-01-22 13:04:56 +00001245///////////////////////////////////////////////////////////////////////////////
robertphillips@google.comec51cb82012-03-23 18:13:47 +00001246DEFINE_DECODER_CREATOR(PNGImageDecoder);
1247DEFINE_ENCODER_CREATOR(PNGImageEncoder);
1248///////////////////////////////////////////////////////////////////////////////
reed@android.com00bf85a2009-01-22 13:04:56 +00001249
scroggo@google.comb5571b32013-09-25 21:34:24 +00001250static bool is_png(SkStreamRewindable* stream) {
reed@android.com00bf85a2009-01-22 13:04:56 +00001251 char buf[PNG_BYTES_TO_CHECK];
1252 if (stream->read(buf, PNG_BYTES_TO_CHECK) == PNG_BYTES_TO_CHECK &&
1253 !png_sig_cmp((png_bytep) buf, (png_size_t)0, PNG_BYTES_TO_CHECK)) {
scroggo@google.com39edf4c2013-04-25 17:33:51 +00001254 return true;
1255 }
1256 return false;
1257}
1258
scroggo@google.comb5571b32013-09-25 21:34:24 +00001259SkImageDecoder* sk_libpng_dfactory(SkStreamRewindable* stream) {
scroggo@google.com39edf4c2013-04-25 17:33:51 +00001260 if (is_png(stream)) {
reed@android.com00bf85a2009-01-22 13:04:56 +00001261 return SkNEW(SkPNGImageDecoder);
1262 }
1263 return NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001264}
1265
scroggo@google.comb5571b32013-09-25 21:34:24 +00001266static SkImageDecoder::Format get_format_png(SkStreamRewindable* stream) {
scroggo@google.com39edf4c2013-04-25 17:33:51 +00001267 if (is_png(stream)) {
1268 return SkImageDecoder::kPNG_Format;
1269 }
1270 return SkImageDecoder::kUnknown_Format;
1271}
1272
reed@android.comdfee5792010-04-15 14:24:50 +00001273SkImageEncoder* sk_libpng_efactory(SkImageEncoder::Type t) {
reed@android.com00bf85a2009-01-22 13:04:56 +00001274 return (SkImageEncoder::kPNG_Type == t) ? SkNEW(SkPNGImageEncoder) : NULL;
1275}
1276
mtklein@google.combd6343b2013-09-04 17:20:18 +00001277static SkImageDecoder_DecodeReg gDReg(sk_libpng_dfactory);
1278static SkImageDecoder_FormatReg gFormatReg(get_format_png);
1279static SkImageEncoder_EncodeReg gEReg(sk_libpng_efactory);