blob: cd8152a36bbf808387bca3c7399f29424de2dad4 [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"
djsollenb2a6fe72015-04-03 12:35:27 -070020
reed@android.com8a1c16f2008-12-17 15:59:43 +000021#include "png.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000022
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +000023/* These were dropped in libpng >= 1.4 */
24#ifndef png_infopp_NULL
halcanary96fcdcc2015-08-27 07:41:13 -070025#define png_infopp_NULL nullptr
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +000026#endif
27
28#ifndef png_bytepp_NULL
halcanary96fcdcc2015-08-27 07:41:13 -070029#define png_bytepp_NULL nullptr
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +000030#endif
31
32#ifndef int_p_NULL
halcanary96fcdcc2015-08-27 07:41:13 -070033#define int_p_NULL nullptr
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +000034#endif
35
36#ifndef png_flush_ptr_NULL
halcanary96fcdcc2015-08-27 07:41:13 -070037#define png_flush_ptr_NULL nullptr
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +000038#endif
39
halcanary@google.com2a103182013-10-14 12:49:15 +000040#define DEFAULT_FOR_SUPPRESS_PNG_IMAGE_DECODER_WARNINGS true
halcanary@google.com2a103182013-10-14 12:49:15 +000041SK_CONF_DECLARE(bool, c_suppressPNGImageDecoderWarnings,
42 "images.png.suppressDecoderWarnings",
43 DEFAULT_FOR_SUPPRESS_PNG_IMAGE_DECODER_WARNINGS,
44 "Suppress most PNG warnings when calling image decode "
45 "functions.");
46
commit-bot@chromium.orga936e372013-03-14 14:42:18 +000047class SkPNGImageIndex {
48public:
scroggoa1193e42015-01-21 12:09:53 -080049 // Takes ownership of stream.
scroggo@google.comb5571b32013-09-25 21:34:24 +000050 SkPNGImageIndex(SkStreamRewindable* stream, png_structp png_ptr, png_infop info_ptr)
scroggo@google.comc70a3aa2013-07-18 20:03:15 +000051 : fStream(stream)
52 , fPng_ptr(png_ptr)
53 , fInfo_ptr(info_ptr)
reed6c225732014-06-09 19:52:07 -070054 , fColorType(kUnknown_SkColorType) {
halcanary96fcdcc2015-08-27 07:41:13 -070055 SkASSERT(stream != nullptr);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +000056 }
57 ~SkPNGImageIndex() {
bsalomon49f085d2014-09-05 13:34:00 -070058 if (fPng_ptr) {
scroggo@google.comc70a3aa2013-07-18 20:03:15 +000059 png_destroy_read_struct(&fPng_ptr, &fInfo_ptr, png_infopp_NULL);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +000060 }
61 }
62
scroggoa1193e42015-01-21 12:09:53 -080063 SkAutoTDelete<SkStreamRewindable> fStream;
scroggo@google.comb5571b32013-09-25 21:34:24 +000064 png_structp fPng_ptr;
65 png_infop fInfo_ptr;
reed6c225732014-06-09 19:52:07 -070066 SkColorType fColorType;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +000067};
68
reed@android.com8a1c16f2008-12-17 15:59:43 +000069class SkPNGImageDecoder : public SkImageDecoder {
70public:
commit-bot@chromium.orga936e372013-03-14 14:42:18 +000071 SkPNGImageDecoder() {
halcanary96fcdcc2015-08-27 07:41:13 -070072 fImageIndex = nullptr;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +000073 }
mtklein36352bf2015-03-25 18:17:31 -070074 Format getFormat() const override {
reed@android.com8a1c16f2008-12-17 15:59:43 +000075 return kPNG_Format;
76 }
scroggo@google.com39edf4c2013-04-25 17:33:51 +000077
halcanary385fe4d2015-08-26 13:07:48 -070078 virtual ~SkPNGImageDecoder() { delete fImageIndex; }
rmistry@google.comd6176b02012-08-23 18:14:13 +000079
reed@android.com8a1c16f2008-12-17 15:59:43 +000080protected:
mtklein36352bf2015-03-25 18:17:31 -070081 Result onDecode(SkStream* stream, SkBitmap* bm, Mode) override;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +000082
83private:
84 SkPNGImageIndex* fImageIndex;
85
86 bool onDecodeInit(SkStream* stream, png_structp *png_ptrp, png_infop *info_ptrp);
dml78acf962015-03-18 06:03:29 -070087 bool decodePalette(png_structp png_ptr, png_infop info_ptr, int bitDepth,
scroggo@google.com2bbc2c92013-06-14 15:33:20 +000088 bool * SK_RESTRICT hasAlphap, bool *reallyHasAlphap,
89 SkColorTable **colorTablep);
reed0689d7b2014-06-14 05:30:20 -070090 bool getBitmapColorType(png_structp, png_infop, SkColorType*, bool* hasAlpha,
91 SkPMColor* theTranspColor);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +000092
93 typedef SkImageDecoder INHERITED;
reed@android.com8a1c16f2008-12-17 15:59:43 +000094};
95
96#ifndef png_jmpbuf
97# define png_jmpbuf(png_ptr) ((png_ptr)->jmpbuf)
98#endif
99
100#define PNG_BYTES_TO_CHECK 4
101
102/* Automatically clean up after throwing an exception */
103struct PNGAutoClean {
104 PNGAutoClean(png_structp p, png_infop i): png_ptr(p), info_ptr(i) {}
105 ~PNGAutoClean() {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000106 png_destroy_read_struct(&png_ptr, &info_ptr, png_infopp_NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000107 }
108private:
109 png_structp png_ptr;
110 png_infop info_ptr;
111};
112
reed@android.com8a1c16f2008-12-17 15:59:43 +0000113static void sk_read_fn(png_structp png_ptr, png_bytep data, png_size_t length) {
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000114 SkStream* sk_stream = (SkStream*) png_get_io_ptr(png_ptr);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000115 size_t bytes = sk_stream->read(data, length);
116 if (bytes != length) {
117 png_error(png_ptr, "Read Error!");
118 }
119}
120
kkinnunen93b255b2014-10-19 22:07:23 -0700121#ifdef PNG_READ_UNKNOWN_CHUNKS_SUPPORTED
reed@android.com8a1c16f2008-12-17 15:59:43 +0000122static int sk_read_user_chunk(png_structp png_ptr, png_unknown_chunkp chunk) {
scroggocf98fa92015-11-23 08:14:40 -0800123 SkPngChunkReader* peeker = (SkPngChunkReader*)png_get_user_chunk_ptr(png_ptr);
124 // readChunk() returning true means continue decoding
125 return peeker->readChunk((const char*)chunk->name, chunk->data, chunk->size) ?
reed@android.com8a1c16f2008-12-17 15:59:43 +0000126 1 : -1;
127}
kkinnunen93b255b2014-10-19 22:07:23 -0700128#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000129
130static void sk_error_fn(png_structp png_ptr, png_const_charp msg) {
scroggo04a0d3f2015-12-10 08:54:36 -0800131 if (!c_suppressPNGImageDecoderWarnings) {
132 SkDEBUGF(("------ png error %s\n", msg));
133 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000134 longjmp(png_jmpbuf(png_ptr), 1);
135}
136
137static void skip_src_rows(png_structp png_ptr, uint8_t storage[], int count) {
138 for (int i = 0; i < count; i++) {
139 uint8_t* tmp = storage;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000140 png_read_rows(png_ptr, &tmp, png_bytepp_NULL, 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000141 }
142}
143
144static bool pos_le(int value, int max) {
145 return value > 0 && value <= max;
146}
147
148static bool substituteTranspColor(SkBitmap* bm, SkPMColor match) {
reed0689d7b2014-06-14 05:30:20 -0700149 SkASSERT(bm->colorType() == kN32_SkColorType);
rmistry@google.comd6176b02012-08-23 18:14:13 +0000150
reed@android.com8a1c16f2008-12-17 15:59:43 +0000151 bool reallyHasAlpha = false;
152
153 for (int y = bm->height() - 1; y >= 0; --y) {
154 SkPMColor* p = bm->getAddr32(0, y);
155 for (int x = bm->width() - 1; x >= 0; --x) {
156 if (match == *p) {
157 *p = 0;
158 reallyHasAlpha = true;
159 }
160 p += 1;
161 }
162 }
163 return reallyHasAlpha;
164}
165
reed6c225732014-06-09 19:52:07 -0700166static bool canUpscalePaletteToConfig(SkColorType dstColorType, bool srcHasAlpha) {
167 switch (dstColorType) {
168 case kN32_SkColorType:
169 case kARGB_4444_SkColorType:
reed@android.comb6137c32009-07-29 20:56:52 +0000170 return true;
reed6c225732014-06-09 19:52:07 -0700171 case kRGB_565_SkColorType:
reed@android.comb6137c32009-07-29 20:56:52 +0000172 // only return true if the src is opaque (since 565 is opaque)
173 return !srcHasAlpha;
174 default:
175 return false;
176 }
177}
178
179// call only if color_type is PALETTE. Returns true if the ctable has alpha
180static bool hasTransparencyInPalette(png_structp png_ptr, png_infop info_ptr) {
181 png_bytep trans;
182 int num_trans;
183
184 if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
halcanary96fcdcc2015-08-27 07:41:13 -0700185 png_get_tRNS(png_ptr, info_ptr, &trans, &num_trans, nullptr);
reed@android.comb6137c32009-07-29 20:56:52 +0000186 return num_trans > 0;
187 }
188 return false;
reed@android.com11344262009-07-08 20:09:23 +0000189}
190
halcanary@google.comfed30372013-10-04 12:46:45 +0000191void do_nothing_warning_fn(png_structp, png_const_charp) {
192 /* do nothing */
193}
194
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000195bool SkPNGImageDecoder::onDecodeInit(SkStream* sk_stream, png_structp *png_ptrp,
196 png_infop *info_ptrp) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000197 /* Create and initialize the png_struct with the desired error handler
198 * functions. If you want to use the default stderr and longjump method,
halcanary96fcdcc2015-08-27 07:41:13 -0700199 * you can supply nullptr for the last three parameters. We also supply the
reed@android.com8a1c16f2008-12-17 15:59:43 +0000200 * the compiler header file version, so that we know if the application
201 * was compiled with a compatible version of the library. */
halcanary@google.comfed30372013-10-04 12:46:45 +0000202
halcanary@google.comfed30372013-10-04 12:46:45 +0000203 png_error_ptr user_warning_fn =
halcanary96fcdcc2015-08-27 07:41:13 -0700204 (c_suppressPNGImageDecoderWarnings) ? (&do_nothing_warning_fn) : nullptr;
205 /* nullptr means to leave as default library behavior. */
halcanary@google.com2a103182013-10-14 12:49:15 +0000206 /* c_suppressPNGImageDecoderWarnings default depends on SK_DEBUG. */
halcanary@google.comfed30372013-10-04 12:46:45 +0000207 /* To suppress warnings with a SK_DEBUG binary, set the
208 * environment variable "skia_images_png_suppressDecoderWarnings"
209 * to "true". Inside a program that links to skia:
210 * SK_CONF_SET("images.png.suppressDecoderWarnings", true); */
halcanary@google.comfed30372013-10-04 12:46:45 +0000211
reed@android.com8a1c16f2008-12-17 15:59:43 +0000212 png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
halcanary96fcdcc2015-08-27 07:41:13 -0700213 nullptr, sk_error_fn, user_warning_fn);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000214 // png_voidp user_error_ptr, user_error_fn, user_warning_fn);
halcanary96fcdcc2015-08-27 07:41:13 -0700215 if (png_ptr == nullptr) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000216 return false;
217 }
halcanary@google.comfed30372013-10-04 12:46:45 +0000218
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000219 *png_ptrp = png_ptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000220
221 /* Allocate/initialize the memory for image information. */
222 png_infop info_ptr = png_create_info_struct(png_ptr);
halcanary96fcdcc2015-08-27 07:41:13 -0700223 if (info_ptr == nullptr) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000224 png_destroy_read_struct(&png_ptr, png_infopp_NULL, png_infopp_NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000225 return false;
226 }
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000227 *info_ptrp = info_ptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000228
229 /* Set error handling if you are using the setjmp/longjmp method (this is
230 * the normal method of doing things with libpng). REQUIRED unless you
231 * set up your own error handlers in the png_create_read_struct() earlier.
232 */
233 if (setjmp(png_jmpbuf(png_ptr))) {
scroggo@google.com5401cd02013-11-12 15:30:06 +0000234 png_destroy_read_struct(&png_ptr, &info_ptr, png_infopp_NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000235 return false;
236 }
237
238 /* If you are using replacement read functions, instead of calling
239 * png_init_io() here you would call:
240 */
241 png_set_read_fn(png_ptr, (void *)sk_stream, sk_read_fn);
242 /* where user_io_ptr is a structure you want available to the callbacks */
243 /* If we have already read some of the signature */
244// png_set_sig_bytes(png_ptr, 0 /* sig_read */ );
245
kkinnunen93b255b2014-10-19 22:07:23 -0700246#ifdef PNG_READ_UNKNOWN_CHUNKS_SUPPORTED
reed@android.com8a1c16f2008-12-17 15:59:43 +0000247 // hookup our peeker so we can see any user-chunks the caller may be interested in
248 png_set_keep_unknown_chunks(png_ptr, PNG_HANDLE_CHUNK_ALWAYS, (png_byte*)"", 0);
249 if (this->getPeeker()) {
250 png_set_read_user_chunk_fn(png_ptr, (png_voidp)this->getPeeker(), sk_read_user_chunk);
251 }
kkinnunen93b255b2014-10-19 22:07:23 -0700252#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000253 /* The call to png_read_info() gives us all of the information from the
254 * PNG file before the first IDAT (image data chunk). */
255 png_read_info(png_ptr, info_ptr);
256 png_uint_32 origWidth, origHeight;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000257 int bitDepth, colorType;
258 png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
259 &colorType, int_p_NULL, int_p_NULL, int_p_NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000260
261 /* tell libpng to strip 16 bit/color files down to 8 bits/color */
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000262 if (bitDepth == 16) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000263 png_set_strip_16(png_ptr);
264 }
kkinnunen93b255b2014-10-19 22:07:23 -0700265#ifdef PNG_READ_PACK_SUPPORTED
reed@android.com8a1c16f2008-12-17 15:59:43 +0000266 /* Extract multiple pixels with bit depths of 1, 2, and 4 from a single
267 * byte into separate bytes (useful for paletted and grayscale images). */
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000268 if (bitDepth < 8) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000269 png_set_packing(png_ptr);
270 }
kkinnunen93b255b2014-10-19 22:07:23 -0700271#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000272 /* Expand grayscale images to the full 8 bits from 1, 2, or 4 bits/pixel */
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000273 if (colorType == PNG_COLOR_TYPE_GRAY && bitDepth < 8) {
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000274 png_set_expand_gray_1_2_4_to_8(png_ptr);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000275 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000276
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000277 return true;
278}
279
scroggo2a120802014-10-22 12:07:00 -0700280SkImageDecoder::Result SkPNGImageDecoder::onDecode(SkStream* sk_stream, SkBitmap* decodedBitmap,
281 Mode mode) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000282 png_structp png_ptr;
283 png_infop info_ptr;
284
285 if (!onDecodeInit(sk_stream, &png_ptr, &info_ptr)) {
scroggo2a120802014-10-22 12:07:00 -0700286 return kFailure;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000287 }
288
scroggo@google.comfeeca3c2013-11-12 14:38:41 +0000289 PNGAutoClean autoClean(png_ptr, info_ptr);
290
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000291 if (setjmp(png_jmpbuf(png_ptr))) {
scroggo2a120802014-10-22 12:07:00 -0700292 return kFailure;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000293 }
294
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000295 png_uint_32 origWidth, origHeight;
reed6c225732014-06-09 19:52:07 -0700296 int bitDepth, pngColorType, interlaceType;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000297 png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
reed6c225732014-06-09 19:52:07 -0700298 &pngColorType, &interlaceType, int_p_NULL, int_p_NULL);
rmistry@google.comd6176b02012-08-23 18:14:13 +0000299
reed6c225732014-06-09 19:52:07 -0700300 SkColorType colorType;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000301 bool hasAlpha = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000302 SkPMColor theTranspColor = 0; // 0 tells us not to try to match
rmistry@google.comd6176b02012-08-23 18:14:13 +0000303
reed0689d7b2014-06-14 05:30:20 -0700304 if (!this->getBitmapColorType(png_ptr, info_ptr, &colorType, &hasAlpha, &theTranspColor)) {
scroggo2a120802014-10-22 12:07:00 -0700305 return kFailure;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000306 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000307
reed6c225732014-06-09 19:52:07 -0700308 SkAlphaType alphaType = this->getRequireUnpremultipliedColors() ?
309 kUnpremul_SkAlphaType : kPremul_SkAlphaType;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000310 const int sampleSize = this->getSampleSize();
311 SkScaledBitmapSampler sampler(origWidth, origHeight, sampleSize);
reed6c225732014-06-09 19:52:07 -0700312 decodedBitmap->setInfo(SkImageInfo::Make(sampler.scaledWidth(), sampler.scaledHeight(),
313 colorType, alphaType));
djsollen@google.com446cf712014-02-19 21:45:35 +0000314
reed@android.com8a1c16f2008-12-17 15:59:43 +0000315 if (SkImageDecoder::kDecodeBounds_Mode == mode) {
scroggo2a120802014-10-22 12:07:00 -0700316 return kSuccess;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000317 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000318
reed@android.com8a1c16f2008-12-17 15:59:43 +0000319 // from here down we are concerned with colortables and pixels
320
321 // we track if we actually see a non-opaque pixels, since sometimes a PNG sets its colortype
322 // to |= PNG_COLOR_MASK_ALPHA, but all of its pixels are in fact opaque. We care, since we
323 // draw lots faster if we can flag the bitmap has being opaque
324 bool reallyHasAlpha = false;
halcanary96fcdcc2015-08-27 07:41:13 -0700325 SkColorTable* colorTable = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000326
reed6c225732014-06-09 19:52:07 -0700327 if (pngColorType == PNG_COLOR_TYPE_PALETTE) {
dml78acf962015-03-18 06:03:29 -0700328 decodePalette(png_ptr, info_ptr, bitDepth, &hasAlpha, &reallyHasAlpha, &colorTable);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000329 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000330
reed@android.com8a1c16f2008-12-17 15:59:43 +0000331 SkAutoUnref aur(colorTable);
332
scroggo@google.combc69ce92013-07-09 15:45:14 +0000333 if (!this->allocPixelRef(decodedBitmap,
halcanary96fcdcc2015-08-27 07:41:13 -0700334 kIndex_8_SkColorType == colorType ? colorTable : nullptr)) {
scroggo2a120802014-10-22 12:07:00 -0700335 return kFailure;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000336 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000337
reed@android.com8a1c16f2008-12-17 15:59:43 +0000338 SkAutoLockPixels alp(*decodedBitmap);
339
dmlf45796e2015-03-11 11:12:54 -0700340 // Repeat setjmp, otherwise variables declared since the last call (e.g. alp
341 // and aur) won't get their destructors called in case of a failure.
342 if (setjmp(png_jmpbuf(png_ptr))) {
343 return kFailure;
344 }
345
reed@android.com8a1c16f2008-12-17 15:59:43 +0000346 /* Turn on interlace handling. REQUIRED if you are not using
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000347 * png_read_image(). To see how to handle interlacing passes,
348 * see the png_read_row() method below:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000349 */
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000350 const int number_passes = (interlaceType != PNG_INTERLACE_NONE) ?
351 png_set_interlace_handling(png_ptr) : 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000352
353 /* Optional call to gamma correct and add the background to the palette
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000354 * and update info structure. REQUIRED if you are expecting libpng to
355 * update the palette for you (ie you selected such a transform above).
reed@android.com8a1c16f2008-12-17 15:59:43 +0000356 */
357 png_read_update_info(png_ptr, info_ptr);
358
reed6c225732014-06-09 19:52:07 -0700359 if ((kAlpha_8_SkColorType == colorType || kIndex_8_SkColorType == colorType) &&
360 1 == sampleSize) {
361 if (kAlpha_8_SkColorType == colorType) {
scroggo@google.com5ee18dd2013-10-21 20:47:31 +0000362 // For an A8 bitmap, we assume there is an alpha for speed. It is
363 // possible the bitmap is opaque, but that is an unlikely use case
364 // since it would not be very interesting.
365 reallyHasAlpha = true;
366 // A8 is only allowed if the original was GRAY.
reed6c225732014-06-09 19:52:07 -0700367 SkASSERT(PNG_COLOR_TYPE_GRAY == pngColorType);
scroggo@google.com5ee18dd2013-10-21 20:47:31 +0000368 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000369 for (int i = 0; i < number_passes; i++) {
370 for (png_uint_32 y = 0; y < origHeight; y++) {
371 uint8_t* bmRow = decodedBitmap->getAddr8(0, y);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000372 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000373 }
374 }
375 } else {
376 SkScaledBitmapSampler::SrcConfig sc;
377 int srcBytesPerPixel = 4;
rmistry@google.comd6176b02012-08-23 18:14:13 +0000378
halcanary96fcdcc2015-08-27 07:41:13 -0700379 if (colorTable != nullptr) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000380 sc = SkScaledBitmapSampler::kIndex;
381 srcBytesPerPixel = 1;
reed6c225732014-06-09 19:52:07 -0700382 } else if (kAlpha_8_SkColorType == colorType) {
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000383 // A8 is only allowed if the original was GRAY.
reed6c225732014-06-09 19:52:07 -0700384 SkASSERT(PNG_COLOR_TYPE_GRAY == pngColorType);
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000385 sc = SkScaledBitmapSampler::kGray;
386 srcBytesPerPixel = 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000387 } else if (hasAlpha) {
388 sc = SkScaledBitmapSampler::kRGBA;
389 } else {
390 sc = SkScaledBitmapSampler::kRGBX;
391 }
reed@android.com11344262009-07-08 20:09:23 +0000392
393 /* We have to pass the colortable explicitly, since we may have one
394 even if our decodedBitmap doesn't, due to the request that we
395 upscale png's palette to a direct model
396 */
halcanary96fcdcc2015-08-27 07:41:13 -0700397 const SkPMColor* colors = colorTable ? colorTable->readColors() : nullptr;
mtklein775b8192014-12-02 09:11:25 -0800398 if (!sampler.begin(decodedBitmap, sc, *this, colors)) {
scroggo2a120802014-10-22 12:07:00 -0700399 return kFailure;
reed@android.com862e91b2009-04-28 15:27:07 +0000400 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000401 const int height = decodedBitmap->height();
402
reed@android.com862e91b2009-04-28 15:27:07 +0000403 if (number_passes > 1) {
scroggo565901d2015-12-10 10:44:13 -0800404 SkAutoTMalloc<uint8_t> storage(origWidth * origHeight * srcBytesPerPixel);
405 uint8_t* base = storage.get();
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000406 size_t rowBytes = origWidth * srcBytesPerPixel;
reed@android.coma8a8b8b2009-05-04 15:00:11 +0000407
reed@android.com862e91b2009-04-28 15:27:07 +0000408 for (int i = 0; i < number_passes; i++) {
409 uint8_t* row = base;
410 for (png_uint_32 y = 0; y < origHeight; y++) {
411 uint8_t* bmRow = row;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000412 png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
413 row += rowBytes;
reed@android.com862e91b2009-04-28 15:27:07 +0000414 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000415 }
reed@android.com862e91b2009-04-28 15:27:07 +0000416 // now sample it
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000417 base += sampler.srcY0() * rowBytes;
reed@android.com862e91b2009-04-28 15:27:07 +0000418 for (int y = 0; y < height; y++) {
419 reallyHasAlpha |= sampler.next(base);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000420 base += sampler.srcDY() * rowBytes;
reed@android.com862e91b2009-04-28 15:27:07 +0000421 }
422 } else {
scroggo565901d2015-12-10 10:44:13 -0800423 SkAutoTMalloc<uint8_t> storage(origWidth * srcBytesPerPixel);
424 uint8_t* srcRow = storage.get();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000425 skip_src_rows(png_ptr, srcRow, sampler.srcY0());
426
427 for (int y = 0; y < height; y++) {
428 uint8_t* tmp = srcRow;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000429 png_read_rows(png_ptr, &tmp, png_bytepp_NULL, 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000430 reallyHasAlpha |= sampler.next(srcRow);
431 if (y < height - 1) {
432 skip_src_rows(png_ptr, srcRow, sampler.srcDY() - 1);
433 }
434 }
reed@android.com862e91b2009-04-28 15:27:07 +0000435
reed@android.com8a1c16f2008-12-17 15:59:43 +0000436 // skip the rest of the rows (if any)
437 png_uint_32 read = (height - 1) * sampler.srcDY() +
438 sampler.srcY0() + 1;
439 SkASSERT(read <= origHeight);
440 skip_src_rows(png_ptr, srcRow, origHeight - read);
441 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000442 }
443
444 /* read rest of file, and get additional chunks in info_ptr - REQUIRED */
445 png_read_end(png_ptr, info_ptr);
446
447 if (0 != theTranspColor) {
448 reallyHasAlpha |= substituteTranspColor(decodedBitmap, theTranspColor);
449 }
scroggo@google.com5ee18dd2013-10-21 20:47:31 +0000450 if (reallyHasAlpha && this->getRequireUnpremultipliedColors()) {
reed0689d7b2014-06-14 05:30:20 -0700451 switch (decodedBitmap->colorType()) {
452 case kIndex_8_SkColorType:
scroggo@google.com5ee18dd2013-10-21 20:47:31 +0000453 // Fall through.
reed0689d7b2014-06-14 05:30:20 -0700454 case kARGB_4444_SkColorType:
455 // We have chosen not to support unpremul for these colortypes.
scroggo2a120802014-10-22 12:07:00 -0700456 return kFailure;
scroggo@google.com5ee18dd2013-10-21 20:47:31 +0000457 default: {
reed0689d7b2014-06-14 05:30:20 -0700458 // Fall through to finish the decode. This colortype either
scroggo@google.com5ee18dd2013-10-21 20:47:31 +0000459 // supports unpremul or it is irrelevant because it has no
460 // alpha (or only alpha).
461 // These brackets prevent a warning.
462 }
463 }
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000464 }
scroggo@google.com5ee18dd2013-10-21 20:47:31 +0000465
djsollen@google.com446cf712014-02-19 21:45:35 +0000466 if (!reallyHasAlpha) {
467 decodedBitmap->setAlphaType(kOpaque_SkAlphaType);
reed@google.com383a6972013-10-21 14:00:07 +0000468 }
scroggo2a120802014-10-22 12:07:00 -0700469 return kSuccess;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000470}
471
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000472
473
reed0689d7b2014-06-14 05:30:20 -0700474bool SkPNGImageDecoder::getBitmapColorType(png_structp png_ptr, png_infop info_ptr,
475 SkColorType* colorTypep,
476 bool* hasAlphap,
477 SkPMColor* SK_RESTRICT theTranspColorp) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000478 png_uint_32 origWidth, origHeight;
479 int bitDepth, colorType;
480 png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
481 &colorType, int_p_NULL, int_p_NULL, int_p_NULL);
482
kkinnunen93b255b2014-10-19 22:07:23 -0700483#ifdef PNG_sBIT_SUPPORTED
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000484 // check for sBIT chunk data, in case we should disable dithering because
485 // our data is not truely 8bits per component
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000486 png_color_8p sig_bit;
scroggo@google.com8d239242013-10-01 17:27:15 +0000487 if (this->getDitherImage() && png_get_sBIT(png_ptr, info_ptr, &sig_bit)) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000488#if 0
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000489 SkDebugf("----- sBIT %d %d %d %d\n", sig_bit->red, sig_bit->green,
490 sig_bit->blue, sig_bit->alpha);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000491#endif
492 // 0 seems to indicate no information available
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000493 if (pos_le(sig_bit->red, SK_R16_BITS) &&
494 pos_le(sig_bit->green, SK_G16_BITS) &&
495 pos_le(sig_bit->blue, SK_B16_BITS)) {
scroggo@google.com8d239242013-10-01 17:27:15 +0000496 this->setDitherImage(false);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000497 }
498 }
kkinnunen93b255b2014-10-19 22:07:23 -0700499#endif
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000500
501 if (colorType == PNG_COLOR_TYPE_PALETTE) {
502 bool paletteHasAlpha = hasTransparencyInPalette(png_ptr, info_ptr);
reed6c225732014-06-09 19:52:07 -0700503 *colorTypep = this->getPrefColorType(kIndex_SrcDepth, paletteHasAlpha);
reed0689d7b2014-06-14 05:30:20 -0700504 // now see if we can upscale to their requested colortype
reed6c225732014-06-09 19:52:07 -0700505 if (!canUpscalePaletteToConfig(*colorTypep, paletteHasAlpha)) {
506 *colorTypep = kIndex_8_SkColorType;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000507 }
508 } else {
halcanary96fcdcc2015-08-27 07:41:13 -0700509 png_color_16p transpColor = nullptr;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000510 int numTransp = 0;
511
halcanary96fcdcc2015-08-27 07:41:13 -0700512 png_get_tRNS(png_ptr, info_ptr, nullptr, &numTransp, &transpColor);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000513
514 bool valid = png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS);
515
halcanary96fcdcc2015-08-27 07:41:13 -0700516 if (valid && numTransp == 1 && transpColor != nullptr) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000517 /* Compute our transparent color, which we'll match against later.
518 We don't really handle 16bit components properly here, since we
519 do our compare *after* the values have been knocked down to 8bit
520 which means we will find more matches than we should. The real
521 fix seems to be to see the actual 16bit components, do the
522 compare, and then knock it down to 8bits ourselves.
523 */
524 if (colorType & PNG_COLOR_MASK_COLOR) {
525 if (16 == bitDepth) {
526 *theTranspColorp = SkPackARGB32(0xFF, transpColor->red >> 8,
527 transpColor->green >> 8,
528 transpColor->blue >> 8);
529 } else {
halcanary@google.comfed30372013-10-04 12:46:45 +0000530 /* We apply the mask because in a very small
531 number of corrupt PNGs, (transpColor->red > 255)
532 and (bitDepth == 8), for certain versions of libpng. */
533 *theTranspColorp = SkPackARGB32(0xFF,
534 0xFF & (transpColor->red),
535 0xFF & (transpColor->green),
536 0xFF & (transpColor->blue));
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000537 }
538 } else { // gray
539 if (16 == bitDepth) {
540 *theTranspColorp = SkPackARGB32(0xFF, transpColor->gray >> 8,
541 transpColor->gray >> 8,
542 transpColor->gray >> 8);
543 } else {
halcanary@google.comfed30372013-10-04 12:46:45 +0000544 /* We apply the mask because in a very small
545 number of corrupt PNGs, (transpColor->red >
546 255) and (bitDepth == 8), for certain versions
547 of libpng. For safety we assume the same could
548 happen with a grayscale PNG. */
549 *theTranspColorp = SkPackARGB32(0xFF,
550 0xFF & (transpColor->gray),
551 0xFF & (transpColor->gray),
552 0xFF & (transpColor->gray));
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000553 }
554 }
555 }
556
557 if (valid ||
558 PNG_COLOR_TYPE_RGB_ALPHA == colorType ||
559 PNG_COLOR_TYPE_GRAY_ALPHA == colorType) {
560 *hasAlphap = true;
561 }
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000562
563 SrcDepth srcDepth = k32Bit_SrcDepth;
564 if (PNG_COLOR_TYPE_GRAY == colorType) {
565 srcDepth = k8BitGray_SrcDepth;
scroggo@google.com8e2ef012013-07-18 20:14:45 +0000566 // Remove this assert, which fails on desk_pokemonwiki.skp
567 //SkASSERT(!*hasAlphap);
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000568 }
569
reed6c225732014-06-09 19:52:07 -0700570 *colorTypep = this->getPrefColorType(srcDepth, *hasAlphap);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000571 // now match the request against our capabilities
572 if (*hasAlphap) {
reed6c225732014-06-09 19:52:07 -0700573 if (*colorTypep != kARGB_4444_SkColorType) {
574 *colorTypep = kN32_SkColorType;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000575 }
576 } else {
reed6c225732014-06-09 19:52:07 -0700577 if (kAlpha_8_SkColorType == *colorTypep) {
scroggo@google.com354fd972013-10-02 15:50:19 +0000578 if (k8BitGray_SrcDepth != srcDepth) {
579 // Converting a non grayscale image to A8 is not currently supported.
reed6c225732014-06-09 19:52:07 -0700580 *colorTypep = kN32_SkColorType;
scroggo@google.com354fd972013-10-02 15:50:19 +0000581 }
reed6c225732014-06-09 19:52:07 -0700582 } else if (*colorTypep != kRGB_565_SkColorType &&
583 *colorTypep != kARGB_4444_SkColorType) {
584 *colorTypep = kN32_SkColorType;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000585 }
586 }
587 }
588
589 // sanity check for size
590 {
reed@google.com57212f92013-12-30 14:40:38 +0000591 int64_t size = sk_64_mul(origWidth, origHeight);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000592 // now check that if we are 4-bytes per pixel, we also don't overflow
reed@google.com57212f92013-12-30 14:40:38 +0000593 if (size < 0 || size > (0x7FFFFFFF >> 2)) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000594 return false;
595 }
596 }
597
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000598 // If the image has alpha and the decoder wants unpremultiplied
reed0689d7b2014-06-14 05:30:20 -0700599 // colors, the only supported colortype is 8888.
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000600 if (this->getRequireUnpremultipliedColors() && *hasAlphap) {
reed6c225732014-06-09 19:52:07 -0700601 *colorTypep = kN32_SkColorType;
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000602 }
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000603
halcanary96fcdcc2015-08-27 07:41:13 -0700604 if (fImageIndex != nullptr) {
reed6c225732014-06-09 19:52:07 -0700605 if (kUnknown_SkColorType == fImageIndex->fColorType) {
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000606 // This is the first time for this subset decode. From now on,
reed0689d7b2014-06-14 05:30:20 -0700607 // all decodes must be in the same colortype.
reed6c225732014-06-09 19:52:07 -0700608 fImageIndex->fColorType = *colorTypep;
609 } else if (fImageIndex->fColorType != *colorTypep) {
610 // Requesting a different colortype for a subsequent decode is not
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000611 // supported. Report failure before we make changes to png_ptr.
612 return false;
613 }
614 }
615
reed6c225732014-06-09 19:52:07 -0700616 bool convertGrayToRGB = PNG_COLOR_TYPE_GRAY == colorType && *colorTypep != kAlpha_8_SkColorType;
scroggo@google.comc70a3aa2013-07-18 20:03:15 +0000617
618 // Unless the user is requesting A8, convert a grayscale image into RGB.
619 // GRAY_ALPHA will always be converted to RGB
620 if (convertGrayToRGB || colorType == PNG_COLOR_TYPE_GRAY_ALPHA) {
621 png_set_gray_to_rgb(png_ptr);
622 }
623
624 // Add filler (or alpha) byte (after each RGB triplet) if necessary.
625 if (colorType == PNG_COLOR_TYPE_RGB || convertGrayToRGB) {
626 png_set_filler(png_ptr, 0xff, PNG_FILLER_AFTER);
627 }
628
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000629 return true;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000630}
631
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000632typedef uint32_t (*PackColorProc)(U8CPU a, U8CPU r, U8CPU g, U8CPU b);
633
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000634bool SkPNGImageDecoder::decodePalette(png_structp png_ptr, png_infop info_ptr,
dml78acf962015-03-18 06:03:29 -0700635 int bitDepth, bool *hasAlphap,
636 bool *reallyHasAlphap,
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000637 SkColorTable **colorTablep) {
638 int numPalette;
639 png_colorp palette;
640 png_bytep trans;
641 int numTrans;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000642
643 png_get_PLTE(png_ptr, info_ptr, &palette, &numPalette);
644
reed@google.com0a6151d2013-10-10 14:44:56 +0000645 SkPMColor colorStorage[256]; // worst-case storage
646 SkPMColor* colorPtr = colorStorage;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000647
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000648 if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
halcanary96fcdcc2015-08-27 07:41:13 -0700649 png_get_tRNS(png_ptr, info_ptr, &trans, &numTrans, nullptr);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000650 *hasAlphap = (numTrans > 0);
651 } else {
652 numTrans = 0;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000653 }
reed@google.com0a6151d2013-10-10 14:44:56 +0000654
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000655 // check for bad images that might make us crash
656 if (numTrans > numPalette) {
657 numTrans = numPalette;
658 }
659
660 int index = 0;
661 int transLessThanFF = 0;
662
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000663 // Choose which function to use to create the color table. If the final destination's
reed0689d7b2014-06-14 05:30:20 -0700664 // colortype is unpremultiplied, the color table will store unpremultiplied colors.
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000665 PackColorProc proc;
666 if (this->getRequireUnpremultipliedColors()) {
667 proc = &SkPackARGB32NoCheck;
668 } else {
669 proc = &SkPreMultiplyARGB;
670 }
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000671 for (; index < numTrans; index++) {
672 transLessThanFF |= (int)*trans - 0xFF;
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000673 *colorPtr++ = proc(*trans++, palette->red, palette->green, palette->blue);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000674 palette++;
675 }
reed@google.com0a6151d2013-10-10 14:44:56 +0000676 bool reallyHasAlpha = (transLessThanFF < 0);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000677
678 for (; index < numPalette; index++) {
679 *colorPtr++ = SkPackARGB32(0xFF, palette->red, palette->green, palette->blue);
680 palette++;
681 }
682
dml78acf962015-03-18 06:03:29 -0700683 /* BUGGY IMAGE WORKAROUND
684
685 Invalid images could contain pixel values that are greater than the number of palette
686 entries. Since we use pixel values as indices into the palette this could result in reading
687 beyond the end of the palette which could leak the contents of uninitialized memory. To
688 ensure this doesn't happen, we grow the colortable to the maximum size that can be
689 addressed by the bitdepth of the image and fill it with the last palette color or black if
690 the palette is empty (really broken image).
691 */
692 int colorCount = SkTMax(numPalette, 1 << SkTMin(bitDepth, 8));
693 SkPMColor lastColor = index > 0 ? colorPtr[-1] : SkPackARGB32(0xFF, 0, 0, 0);
694 for (; index < colorCount; index++) {
695 *colorPtr++ = lastColor;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000696 }
reed@google.com0a6151d2013-10-10 14:44:56 +0000697
halcanary385fe4d2015-08-26 13:07:48 -0700698 *colorTablep = new SkColorTable(colorStorage, colorCount);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000699 *reallyHasAlphap = reallyHasAlpha;
700 return true;
701}
702
reed@android.com8a1c16f2008-12-17 15:59:43 +0000703///////////////////////////////////////////////////////////////////////////////
704
reed@android.com8a1c16f2008-12-17 15:59:43 +0000705#include "SkColorPriv.h"
706#include "SkUnPreMultiply.h"
707
708static void sk_write_fn(png_structp png_ptr, png_bytep data, png_size_t len) {
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +0000709 SkWStream* sk_stream = (SkWStream*)png_get_io_ptr(png_ptr);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000710 if (!sk_stream->write(data, len)) {
711 png_error(png_ptr, "sk_write_fn Error!");
712 }
713}
714
reed0689d7b2014-06-14 05:30:20 -0700715static transform_scanline_proc choose_proc(SkColorType ct, bool hasAlpha) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000716 // we don't care about search on alpha if we're kIndex8, since only the
717 // colortable packing cares about that distinction, not the pixels
reed0689d7b2014-06-14 05:30:20 -0700718 if (kIndex_8_SkColorType == ct) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000719 hasAlpha = false; // we store false in the table entries for kIndex8
720 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000721
reed@android.com8a1c16f2008-12-17 15:59:43 +0000722 static const struct {
reed0689d7b2014-06-14 05:30:20 -0700723 SkColorType fColorType;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000724 bool fHasAlpha;
725 transform_scanline_proc fProc;
726 } gMap[] = {
reed0689d7b2014-06-14 05:30:20 -0700727 { kRGB_565_SkColorType, false, transform_scanline_565 },
728 { kN32_SkColorType, false, transform_scanline_888 },
729 { kN32_SkColorType, true, transform_scanline_8888 },
730 { kARGB_4444_SkColorType, false, transform_scanline_444 },
731 { kARGB_4444_SkColorType, true, transform_scanline_4444 },
732 { kIndex_8_SkColorType, false, transform_scanline_memcpy },
reed@android.com8a1c16f2008-12-17 15:59:43 +0000733 };
734
735 for (int i = SK_ARRAY_COUNT(gMap) - 1; i >= 0; --i) {
reed0689d7b2014-06-14 05:30:20 -0700736 if (gMap[i].fColorType == ct && gMap[i].fHasAlpha == hasAlpha) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000737 return gMap[i].fProc;
738 }
739 }
740 sk_throw();
halcanary96fcdcc2015-08-27 07:41:13 -0700741 return nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000742}
743
744// return the minimum legal bitdepth (by png standards) for this many colortable
745// entries. SkBitmap always stores in 8bits per pixel, but for colorcount <= 16,
746// we can use fewer bits per in png
747static int computeBitDepth(int colorCount) {
748#if 0
749 int bits = SkNextLog2(colorCount);
750 SkASSERT(bits >= 1 && bits <= 8);
751 // now we need bits itself to be a power of 2 (e.g. 1, 2, 4, 8)
752 return SkNextPow2(bits);
753#else
754 // for the moment, we don't know how to pack bitdepth < 8
755 return 8;
756#endif
757}
758
759/* Pack palette[] with the corresponding colors, and if hasAlpha is true, also
760 pack trans[] and return the number of trans[] entries written. If hasAlpha
761 is false, the return value will always be 0.
rmistry@google.comd6176b02012-08-23 18:14:13 +0000762
reed@android.com8a1c16f2008-12-17 15:59:43 +0000763 Note: this routine takes care of unpremultiplying the RGB values when we
764 have alpha in the colortable, since png doesn't support premul colors
765*/
reed@android.com6f252972009-01-14 16:46:16 +0000766static inline int pack_palette(SkColorTable* ctable,
reed@android.comb50a60c2009-01-14 17:51:08 +0000767 png_color* SK_RESTRICT palette,
768 png_byte* SK_RESTRICT trans, bool hasAlpha) {
halcanary96fcdcc2015-08-27 07:41:13 -0700769 const SkPMColor* SK_RESTRICT colors = ctable ? ctable->readColors() : nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000770 const int ctCount = ctable->count();
771 int i, num_trans = 0;
772
773 if (hasAlpha) {
774 /* first see if we have some number of fully opaque at the end of the
775 ctable. PNG allows num_trans < num_palette, but all of the trans
776 entries must come first in the palette. If I was smarter, I'd
777 reorder the indices and ctable so that all non-opaque colors came
778 first in the palette. But, since that would slow down the encode,
779 I'm leaving the indices and ctable order as is, and just looking
780 at the tail of the ctable for opaqueness.
781 */
782 num_trans = ctCount;
783 for (i = ctCount - 1; i >= 0; --i) {
784 if (SkGetPackedA32(colors[i]) != 0xFF) {
785 break;
786 }
787 num_trans -= 1;
788 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000789
reed@android.com8a1c16f2008-12-17 15:59:43 +0000790 const SkUnPreMultiply::Scale* SK_RESTRICT table =
791 SkUnPreMultiply::GetScaleTable();
792
793 for (i = 0; i < num_trans; i++) {
794 const SkPMColor c = *colors++;
795 const unsigned a = SkGetPackedA32(c);
796 const SkUnPreMultiply::Scale s = table[a];
797 trans[i] = a;
798 palette[i].red = SkUnPreMultiply::ApplyScale(s, SkGetPackedR32(c));
799 palette[i].green = SkUnPreMultiply::ApplyScale(s,SkGetPackedG32(c));
800 palette[i].blue = SkUnPreMultiply::ApplyScale(s, SkGetPackedB32(c));
rmistry@google.comd6176b02012-08-23 18:14:13 +0000801 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000802 // now fall out of this if-block to use common code for the trailing
803 // opaque entries
804 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000805
reed@android.com8a1c16f2008-12-17 15:59:43 +0000806 // these (remaining) entries are opaque
807 for (i = num_trans; i < ctCount; i++) {
808 SkPMColor c = *colors++;
809 palette[i].red = SkGetPackedR32(c);
810 palette[i].green = SkGetPackedG32(c);
811 palette[i].blue = SkGetPackedB32(c);
812 }
813 return num_trans;
814}
815
816class SkPNGImageEncoder : public SkImageEncoder {
817protected:
mtklein36352bf2015-03-25 18:17:31 -0700818 bool onEncode(SkWStream* stream, const SkBitmap& bm, int quality) override;
tomhudson@google.com5c210c72011-07-28 21:06:40 +0000819private:
820 bool doEncode(SkWStream* stream, const SkBitmap& bm,
821 const bool& hasAlpha, int colorType,
reed0689d7b2014-06-14 05:30:20 -0700822 int bitDepth, SkColorType ct,
tomhudson@google.com5c210c72011-07-28 21:06:40 +0000823 png_color_8& sig_bit);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000824
825 typedef SkImageEncoder INHERITED;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000826};
827
halcanarydfd6c6e2015-12-07 14:07:31 -0800828bool SkPNGImageEncoder::onEncode(SkWStream* stream,
829 const SkBitmap& originalBitmap,
830 int /*quality*/) {
831 SkBitmap copy;
832 const SkBitmap* bitmap = &originalBitmap;
833 switch (originalBitmap.colorType()) {
834 case kIndex_8_SkColorType:
835 case kN32_SkColorType:
836 case kARGB_4444_SkColorType:
837 case kRGB_565_SkColorType:
838 break;
839 default:
840 // TODO(scroggo): support 8888-but-not-N32 natively.
841 // TODO(scroggo): support kGray_8 directly.
842 // TODO(scroggo): support Alpha_8 as Grayscale(black)+Alpha
843 if (originalBitmap.copyTo(&copy, kN32_SkColorType)) {
844 bitmap = &copy;
845 }
846 }
847 SkColorType ct = bitmap->colorType();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000848
halcanarydfd6c6e2015-12-07 14:07:31 -0800849 const bool hasAlpha = !bitmap->isOpaque();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000850 int colorType = PNG_COLOR_MASK_COLOR;
851 int bitDepth = 8; // default for color
852 png_color_8 sig_bit;
853
reed0689d7b2014-06-14 05:30:20 -0700854 switch (ct) {
855 case kIndex_8_SkColorType:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000856 colorType |= PNG_COLOR_MASK_PALETTE;
857 // fall through to the ARGB_8888 case
reed0689d7b2014-06-14 05:30:20 -0700858 case kN32_SkColorType:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000859 sig_bit.red = 8;
860 sig_bit.green = 8;
861 sig_bit.blue = 8;
862 sig_bit.alpha = 8;
863 break;
reed0689d7b2014-06-14 05:30:20 -0700864 case kARGB_4444_SkColorType:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000865 sig_bit.red = 4;
866 sig_bit.green = 4;
867 sig_bit.blue = 4;
868 sig_bit.alpha = 4;
869 break;
reed0689d7b2014-06-14 05:30:20 -0700870 case kRGB_565_SkColorType:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000871 sig_bit.red = 5;
872 sig_bit.green = 6;
873 sig_bit.blue = 5;
874 sig_bit.alpha = 0;
875 break;
876 default:
877 return false;
878 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000879
reed@android.com8a1c16f2008-12-17 15:59:43 +0000880 if (hasAlpha) {
881 // don't specify alpha if we're a palette, even if our ctable has alpha
882 if (!(colorType & PNG_COLOR_MASK_PALETTE)) {
883 colorType |= PNG_COLOR_MASK_ALPHA;
884 }
885 } else {
886 sig_bit.alpha = 0;
887 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000888
halcanarydfd6c6e2015-12-07 14:07:31 -0800889 SkAutoLockPixels alp(*bitmap);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000890 // readyToDraw checks for pixels (and colortable if that is required)
halcanarydfd6c6e2015-12-07 14:07:31 -0800891 if (!bitmap->readyToDraw()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000892 return false;
893 }
894
895 // we must do this after we have locked the pixels
halcanarydfd6c6e2015-12-07 14:07:31 -0800896 SkColorTable* ctable = bitmap->getColorTable();
bsalomon49f085d2014-09-05 13:34:00 -0700897 if (ctable) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000898 if (ctable->count() == 0) {
899 return false;
900 }
901 // check if we can store in fewer than 8 bits
902 bitDepth = computeBitDepth(ctable->count());
903 }
904
halcanarydfd6c6e2015-12-07 14:07:31 -0800905 return doEncode(stream, *bitmap, hasAlpha, colorType, bitDepth, ct, sig_bit);
tomhudson@google.com5c210c72011-07-28 21:06:40 +0000906}
907
908bool SkPNGImageEncoder::doEncode(SkWStream* stream, const SkBitmap& bitmap,
909 const bool& hasAlpha, int colorType,
reed0689d7b2014-06-14 05:30:20 -0700910 int bitDepth, SkColorType ct,
tomhudson@google.com5c210c72011-07-28 21:06:40 +0000911 png_color_8& sig_bit) {
912
reed@android.com8a1c16f2008-12-17 15:59:43 +0000913 png_structp png_ptr;
914 png_infop info_ptr;
915
halcanary96fcdcc2015-08-27 07:41:13 -0700916 png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, sk_error_fn,
917 nullptr);
918 if (nullptr == png_ptr) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000919 return false;
920 }
921
922 info_ptr = png_create_info_struct(png_ptr);
halcanary96fcdcc2015-08-27 07:41:13 -0700923 if (nullptr == info_ptr) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000924 png_destroy_write_struct(&png_ptr, png_infopp_NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000925 return false;
926 }
927
928 /* Set error handling. REQUIRED if you aren't supplying your own
929 * error handling functions in the png_create_write_struct() call.
930 */
931 if (setjmp(png_jmpbuf(png_ptr))) {
932 png_destroy_write_struct(&png_ptr, &info_ptr);
933 return false;
934 }
935
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000936 png_set_write_fn(png_ptr, (void*)stream, sk_write_fn, png_flush_ptr_NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000937
938 /* Set the image information here. Width and height are up to 2^31,
939 * bit_depth is one of 1, 2, 4, 8, or 16, but valid values also depend on
940 * the color_type selected. color_type is one of PNG_COLOR_TYPE_GRAY,
941 * PNG_COLOR_TYPE_GRAY_ALPHA, PNG_COLOR_TYPE_PALETTE, PNG_COLOR_TYPE_RGB,
942 * or PNG_COLOR_TYPE_RGB_ALPHA. interlace is either PNG_INTERLACE_NONE or
943 * PNG_INTERLACE_ADAM7, and the compression_type and filter_type MUST
944 * currently be PNG_COMPRESSION_TYPE_BASE and PNG_FILTER_TYPE_BASE. REQUIRED
945 */
946
947 png_set_IHDR(png_ptr, info_ptr, bitmap.width(), bitmap.height(),
948 bitDepth, colorType,
949 PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE,
950 PNG_FILTER_TYPE_BASE);
951
reed@android.com61898772009-07-07 19:38:01 +0000952 // set our colortable/trans arrays if needed
953 png_color paletteColors[256];
954 png_byte trans[256];
reed0689d7b2014-06-14 05:30:20 -0700955 if (kIndex_8_SkColorType == ct) {
reed@android.com61898772009-07-07 19:38:01 +0000956 SkColorTable* ct = bitmap.getColorTable();
957 int numTrans = pack_palette(ct, paletteColors, trans, hasAlpha);
958 png_set_PLTE(png_ptr, info_ptr, paletteColors, ct->count());
959 if (numTrans > 0) {
halcanary96fcdcc2015-08-27 07:41:13 -0700960 png_set_tRNS(png_ptr, info_ptr, trans, numTrans, nullptr);
reed@android.com61898772009-07-07 19:38:01 +0000961 }
962 }
kkinnunen93b255b2014-10-19 22:07:23 -0700963#ifdef PNG_sBIT_SUPPORTED
reed@android.com8a1c16f2008-12-17 15:59:43 +0000964 png_set_sBIT(png_ptr, info_ptr, &sig_bit);
kkinnunen93b255b2014-10-19 22:07:23 -0700965#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000966 png_write_info(png_ptr, info_ptr);
967
968 const char* srcImage = (const char*)bitmap.getPixels();
scroggo565901d2015-12-10 10:44:13 -0800969 SkAutoSTMalloc<1024, char> rowStorage(bitmap.width() << 2);
970 char* storage = rowStorage.get();
reed0689d7b2014-06-14 05:30:20 -0700971 transform_scanline_proc proc = choose_proc(ct, hasAlpha);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000972
973 for (int y = 0; y < bitmap.height(); y++) {
974 png_bytep row_ptr = (png_bytep)storage;
975 proc(srcImage, bitmap.width(), storage);
976 png_write_rows(png_ptr, &row_ptr, 1);
977 srcImage += bitmap.rowBytes();
978 }
979
980 png_write_end(png_ptr, info_ptr);
981
982 /* clean up after the write, and free any memory allocated */
983 png_destroy_write_struct(&png_ptr, &info_ptr);
984 return true;
985}
986
reed@android.com00bf85a2009-01-22 13:04:56 +0000987///////////////////////////////////////////////////////////////////////////////
robertphillips@google.comec51cb82012-03-23 18:13:47 +0000988DEFINE_DECODER_CREATOR(PNGImageDecoder);
989DEFINE_ENCODER_CREATOR(PNGImageEncoder);
990///////////////////////////////////////////////////////////////////////////////
reed@android.com00bf85a2009-01-22 13:04:56 +0000991
scroggo@google.comb5571b32013-09-25 21:34:24 +0000992static bool is_png(SkStreamRewindable* stream) {
reed@android.com00bf85a2009-01-22 13:04:56 +0000993 char buf[PNG_BYTES_TO_CHECK];
994 if (stream->read(buf, PNG_BYTES_TO_CHECK) == PNG_BYTES_TO_CHECK &&
995 !png_sig_cmp((png_bytep) buf, (png_size_t)0, PNG_BYTES_TO_CHECK)) {
scroggo@google.com39edf4c2013-04-25 17:33:51 +0000996 return true;
997 }
998 return false;
999}
1000
scroggo@google.comb5571b32013-09-25 21:34:24 +00001001SkImageDecoder* sk_libpng_dfactory(SkStreamRewindable* stream) {
scroggo@google.com39edf4c2013-04-25 17:33:51 +00001002 if (is_png(stream)) {
halcanary385fe4d2015-08-26 13:07:48 -07001003 return new SkPNGImageDecoder;
reed@android.com00bf85a2009-01-22 13:04:56 +00001004 }
halcanary96fcdcc2015-08-27 07:41:13 -07001005 return nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001006}
1007
scroggo@google.comb5571b32013-09-25 21:34:24 +00001008static SkImageDecoder::Format get_format_png(SkStreamRewindable* stream) {
scroggo@google.com39edf4c2013-04-25 17:33:51 +00001009 if (is_png(stream)) {
1010 return SkImageDecoder::kPNG_Format;
1011 }
1012 return SkImageDecoder::kUnknown_Format;
1013}
1014
reed@android.comdfee5792010-04-15 14:24:50 +00001015SkImageEncoder* sk_libpng_efactory(SkImageEncoder::Type t) {
halcanary96fcdcc2015-08-27 07:41:13 -07001016 return (SkImageEncoder::kPNG_Type == t) ? new SkPNGImageEncoder : nullptr;
reed@android.com00bf85a2009-01-22 13:04:56 +00001017}
1018
mtklein@google.combd6343b2013-09-04 17:20:18 +00001019static SkImageDecoder_DecodeReg gDReg(sk_libpng_dfactory);
1020static SkImageDecoder_FormatReg gFormatReg(get_format_png);
1021static SkImageEncoder_EncodeReg gEReg(sk_libpng_efactory);