blob: cca69fac1049a2b0e31073075629fb7fb5a89b89 [file] [log] [blame]
epoger@google.comec3ed6a2011-07-28 14:26:00 +00001
2/*
3 * Copyright 2006 The Android Open Source Project
4 *
5 * Use of this source code is governed by a BSD-style license that can be
6 * found in the LICENSE file.
7 */
8
reed@android.com8a1c16f2008-12-17 15:59:43 +00009
10#include "SkImageDecoder.h"
reed@android.comb08eb2b2009-01-06 20:16:26 +000011#include "SkImageEncoder.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000012#include "SkColor.h"
13#include "SkColorPriv.h"
14#include "SkDither.h"
15#include "SkMath.h"
16#include "SkScaledBitmapSampler.h"
17#include "SkStream.h"
18#include "SkTemplates.h"
19#include "SkUtils.h"
epoger@google.com4ce738b2012-11-16 18:44:18 +000020#include "transform_scanline.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000021
22extern "C" {
23#include "png.h"
24}
25
26class SkPNGImageDecoder : public SkImageDecoder {
27public:
28 virtual Format getFormat() const {
29 return kPNG_Format;
30 }
rmistry@google.comd6176b02012-08-23 18:14:13 +000031
reed@android.com8a1c16f2008-12-17 15:59:43 +000032protected:
reed@android.com3f1f06a2010-03-03 21:04:12 +000033 virtual bool onDecode(SkStream* stream, SkBitmap* bm, Mode);
reed@android.com8a1c16f2008-12-17 15:59:43 +000034};
35
36#ifndef png_jmpbuf
37# define png_jmpbuf(png_ptr) ((png_ptr)->jmpbuf)
38#endif
39
40#define PNG_BYTES_TO_CHECK 4
41
42/* Automatically clean up after throwing an exception */
43struct PNGAutoClean {
44 PNGAutoClean(png_structp p, png_infop i): png_ptr(p), info_ptr(i) {}
45 ~PNGAutoClean() {
reed@google.com7af00462010-12-31 18:11:59 +000046 png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +000047 }
48private:
49 png_structp png_ptr;
50 png_infop info_ptr;
51};
52
reed@android.com8a1c16f2008-12-17 15:59:43 +000053static void sk_read_fn(png_structp png_ptr, png_bytep data, png_size_t length) {
reed@google.com29f66362011-02-25 15:07:30 +000054 SkStream* sk_stream = (SkStream*)png_get_io_ptr(png_ptr);
reed@android.com8a1c16f2008-12-17 15:59:43 +000055 size_t bytes = sk_stream->read(data, length);
56 if (bytes != length) {
57 png_error(png_ptr, "Read Error!");
58 }
59}
60
61static int sk_read_user_chunk(png_structp png_ptr, png_unknown_chunkp chunk) {
62 SkImageDecoder::Peeker* peeker =
63 (SkImageDecoder::Peeker*)png_get_user_chunk_ptr(png_ptr);
64 // peek() returning true means continue decoding
65 return peeker->peek((const char*)chunk->name, chunk->data, chunk->size) ?
66 1 : -1;
67}
68
69static void sk_error_fn(png_structp png_ptr, png_const_charp msg) {
70#if 0
71 SkDebugf("------ png error %s\n", msg);
72#endif
73 longjmp(png_jmpbuf(png_ptr), 1);
74}
75
76static void skip_src_rows(png_structp png_ptr, uint8_t storage[], int count) {
77 for (int i = 0; i < count; i++) {
78 uint8_t* tmp = storage;
reed@google.com7af00462010-12-31 18:11:59 +000079 png_read_rows(png_ptr, &tmp, NULL, 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +000080 }
81}
82
83static bool pos_le(int value, int max) {
84 return value > 0 && value <= max;
85}
86
87static bool substituteTranspColor(SkBitmap* bm, SkPMColor match) {
88 SkASSERT(bm->config() == SkBitmap::kARGB_8888_Config);
rmistry@google.comd6176b02012-08-23 18:14:13 +000089
reed@android.com8a1c16f2008-12-17 15:59:43 +000090 bool reallyHasAlpha = false;
91
92 for (int y = bm->height() - 1; y >= 0; --y) {
93 SkPMColor* p = bm->getAddr32(0, y);
94 for (int x = bm->width() - 1; x >= 0; --x) {
95 if (match == *p) {
96 *p = 0;
97 reallyHasAlpha = true;
98 }
99 p += 1;
100 }
101 }
102 return reallyHasAlpha;
103}
104
reed@android.com3f1f06a2010-03-03 21:04:12 +0000105static bool canUpscalePaletteToConfig(SkBitmap::Config dstConfig,
reed@android.comb6137c32009-07-29 20:56:52 +0000106 bool srcHasAlpha) {
reed@android.com3f1f06a2010-03-03 21:04:12 +0000107 switch (dstConfig) {
reed@android.comb6137c32009-07-29 20:56:52 +0000108 case SkBitmap::kARGB_8888_Config:
109 case SkBitmap::kARGB_4444_Config:
110 return true;
111 case SkBitmap::kRGB_565_Config:
112 // only return true if the src is opaque (since 565 is opaque)
113 return !srcHasAlpha;
114 default:
115 return false;
116 }
117}
118
119// call only if color_type is PALETTE. Returns true if the ctable has alpha
120static bool hasTransparencyInPalette(png_structp png_ptr, png_infop info_ptr) {
121 png_bytep trans;
122 int num_trans;
123
124 if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
125 png_get_tRNS(png_ptr, info_ptr, &trans, &num_trans, NULL);
126 return num_trans > 0;
127 }
128 return false;
reed@android.com11344262009-07-08 20:09:23 +0000129}
130
reed@android.com8a1c16f2008-12-17 15:59:43 +0000131bool SkPNGImageDecoder::onDecode(SkStream* sk_stream, SkBitmap* decodedBitmap,
reed@android.com3f1f06a2010-03-03 21:04:12 +0000132 Mode mode) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000133// SkAutoTrace apr("SkPNGImageDecoder::onDecode");
134
135 /* Create and initialize the png_struct with the desired error handler
136 * functions. If you want to use the default stderr and longjump method,
137 * you can supply NULL for the last three parameters. We also supply the
138 * the compiler header file version, so that we know if the application
139 * was compiled with a compatible version of the library. */
140 png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
141 NULL, sk_error_fn, NULL);
142 // png_voidp user_error_ptr, user_error_fn, user_warning_fn);
143 if (png_ptr == NULL) {
144 return false;
145 }
146
147 /* Allocate/initialize the memory for image information. */
148 png_infop info_ptr = png_create_info_struct(png_ptr);
149 if (info_ptr == NULL) {
reed@google.com7af00462010-12-31 18:11:59 +0000150 png_destroy_read_struct(&png_ptr, NULL, NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000151 return false;
152 }
153
154 PNGAutoClean autoClean(png_ptr, info_ptr);
155
156 /* Set error handling if you are using the setjmp/longjmp method (this is
157 * the normal method of doing things with libpng). REQUIRED unless you
158 * set up your own error handlers in the png_create_read_struct() earlier.
159 */
160 if (setjmp(png_jmpbuf(png_ptr))) {
161 return false;
162 }
163
164 /* If you are using replacement read functions, instead of calling
165 * png_init_io() here you would call:
166 */
167 png_set_read_fn(png_ptr, (void *)sk_stream, sk_read_fn);
168 /* where user_io_ptr is a structure you want available to the callbacks */
169 /* If we have already read some of the signature */
170// png_set_sig_bytes(png_ptr, 0 /* sig_read */ );
171
172 // hookup our peeker so we can see any user-chunks the caller may be interested in
173 png_set_keep_unknown_chunks(png_ptr, PNG_HANDLE_CHUNK_ALWAYS, (png_byte*)"", 0);
174 if (this->getPeeker()) {
175 png_set_read_user_chunk_fn(png_ptr, (png_voidp)this->getPeeker(), sk_read_user_chunk);
176 }
177
178 /* The call to png_read_info() gives us all of the information from the
179 * PNG file before the first IDAT (image data chunk). */
180 png_read_info(png_ptr, info_ptr);
181 png_uint_32 origWidth, origHeight;
182 int bit_depth, color_type, interlace_type;
183 png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bit_depth, &color_type,
reed@google.com7af00462010-12-31 18:11:59 +0000184 &interlace_type, NULL, NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000185
186 /* tell libpng to strip 16 bit/color files down to 8 bits/color */
187 if (bit_depth == 16) {
188 png_set_strip_16(png_ptr);
189 }
190 /* Extract multiple pixels with bit depths of 1, 2, and 4 from a single
191 * byte into separate bytes (useful for paletted and grayscale images). */
192 if (bit_depth < 8) {
193 png_set_packing(png_ptr);
194 }
195 /* Expand grayscale images to the full 8 bits from 1, 2, or 4 bits/pixel */
196 if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) {
reed@google.com29f66362011-02-25 15:07:30 +0000197 png_set_expand_gray_1_2_4_to_8(png_ptr);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000198 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000199
reed@android.com8a1c16f2008-12-17 15:59:43 +0000200 /* Make a grayscale image into RGB. */
201 if (color_type == PNG_COLOR_TYPE_GRAY ||
202 color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
203 png_set_gray_to_rgb(png_ptr);
204 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000205
reed@android.com8a1c16f2008-12-17 15:59:43 +0000206 SkBitmap::Config config;
207 bool hasAlpha = false;
208 bool doDither = this->getDitherImage();
209 SkPMColor theTranspColor = 0; // 0 tells us not to try to match
rmistry@google.comd6176b02012-08-23 18:14:13 +0000210
reed@android.com8a1c16f2008-12-17 15:59:43 +0000211 // check for sBIT chunk data, in case we should disable dithering because
212 // our data is not truely 8bits per component
213 if (doDither) {
reed@google.com29f66362011-02-25 15:07:30 +0000214 png_color_8p sig_bit = NULL;
215 bool has_sbit = PNG_INFO_sBIT == png_get_sBIT(png_ptr, info_ptr,
216 &sig_bit);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000217#if 0
reed@google.com29f66362011-02-25 15:07:30 +0000218 if (has_sbit) {
219 SkDebugf("----- sBIT %d %d %d %d\n", sig_bit->red, sig_bit->green,
220 sig_bit->blue, sig_bit->alpha);
221 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000222#endif
223 // 0 seems to indicate no information available
reed@google.com29f66362011-02-25 15:07:30 +0000224 if (has_sbit && pos_le(sig_bit->red, SK_R16_BITS) &&
225 pos_le(sig_bit->green, SK_G16_BITS) &&
226 pos_le(sig_bit->blue, SK_B16_BITS)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000227 doDither = false;
228 }
229 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000230
reed@android.com8a1c16f2008-12-17 15:59:43 +0000231 if (color_type == PNG_COLOR_TYPE_PALETTE) {
reed@android.comb6137c32009-07-29 20:56:52 +0000232 bool paletteHasAlpha = hasTransparencyInPalette(png_ptr, info_ptr);
reed@android.com3f1f06a2010-03-03 21:04:12 +0000233 config = this->getPrefConfig(kIndex_SrcDepth, paletteHasAlpha);
234 // now see if we can upscale to their requested config
235 if (!canUpscalePaletteToConfig(config, paletteHasAlpha)) {
236 config = SkBitmap::kIndex8_Config;
reed@android.comb6137c32009-07-29 20:56:52 +0000237 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000238 } else {
239 png_color_16p transpColor = NULL;
240 int numTransp = 0;
rmistry@google.comd6176b02012-08-23 18:14:13 +0000241
reed@android.com8a1c16f2008-12-17 15:59:43 +0000242 png_get_tRNS(png_ptr, info_ptr, NULL, &numTransp, &transpColor);
rmistry@google.comd6176b02012-08-23 18:14:13 +0000243
reed@android.com8a1c16f2008-12-17 15:59:43 +0000244 bool valid = png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS);
rmistry@google.comd6176b02012-08-23 18:14:13 +0000245
reed@android.com8a1c16f2008-12-17 15:59:43 +0000246 if (valid && numTransp == 1 && transpColor != NULL) {
247 /* Compute our transparent color, which we'll match against later.
248 We don't really handle 16bit components properly here, since we
249 do our compare *after* the values have been knocked down to 8bit
250 which means we will find more matches than we should. The real
251 fix seems to be to see the actual 16bit components, do the
252 compare, and then knock it down to 8bits ourselves.
253 */
254 if (color_type & PNG_COLOR_MASK_COLOR) {
255 if (16 == bit_depth) {
256 theTranspColor = SkPackARGB32(0xFF, transpColor->red >> 8,
257 transpColor->green >> 8, transpColor->blue >> 8);
258 } else {
259 theTranspColor = SkPackARGB32(0xFF, transpColor->red,
260 transpColor->green, transpColor->blue);
261 }
262 } else { // gray
263 if (16 == bit_depth) {
264 theTranspColor = SkPackARGB32(0xFF, transpColor->gray >> 8,
265 transpColor->gray >> 8, transpColor->gray >> 8);
266 } else {
267 theTranspColor = SkPackARGB32(0xFF, transpColor->gray,
268 transpColor->gray, transpColor->gray);
269 }
270 }
271 }
272
273 if (valid ||
274 PNG_COLOR_TYPE_RGB_ALPHA == color_type ||
275 PNG_COLOR_TYPE_GRAY_ALPHA == color_type) {
276 hasAlpha = true;
reed@android.com3f1f06a2010-03-03 21:04:12 +0000277 }
278 config = this->getPrefConfig(k32Bit_SrcDepth, hasAlpha);
279 // now match the request against our capabilities
280 if (hasAlpha) {
281 if (config != SkBitmap::kARGB_4444_Config) {
282 config = SkBitmap::kARGB_8888_Config;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000283 }
reed@android.com3f1f06a2010-03-03 21:04:12 +0000284 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000285 if (config != SkBitmap::kRGB_565_Config &&
reed@android.com3f1f06a2010-03-03 21:04:12 +0000286 config != SkBitmap::kARGB_4444_Config) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000287 config = SkBitmap::kARGB_8888_Config;
288 }
289 }
290 }
reed@android.coma8a8b8b2009-05-04 15:00:11 +0000291
292 // sanity check for size
293 {
294 Sk64 size;
295 size.setMul(origWidth, origHeight);
296 if (size.isNeg() || !size.is32()) {
297 return false;
298 }
299 // now check that if we are 4-bytes per pixel, we also don't overflow
300 if (size.get32() > (0x7FFFFFFF >> 2)) {
301 return false;
302 }
303 }
304
reed@android.com8a1c16f2008-12-17 15:59:43 +0000305 if (!this->chooseFromOneChoice(config, origWidth, origHeight)) {
306 return false;
307 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000308
reed@android.com8a1c16f2008-12-17 15:59:43 +0000309 const int sampleSize = this->getSampleSize();
310 SkScaledBitmapSampler sampler(origWidth, origHeight, sampleSize);
311
312 decodedBitmap->setConfig(config, sampler.scaledWidth(),
313 sampler.scaledHeight(), 0);
314 if (SkImageDecoder::kDecodeBounds_Mode == mode) {
315 return true;
316 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000317
reed@android.com8a1c16f2008-12-17 15:59:43 +0000318 // from here down we are concerned with colortables and pixels
319
320 // we track if we actually see a non-opaque pixels, since sometimes a PNG sets its colortype
321 // to |= PNG_COLOR_MASK_ALPHA, but all of its pixels are in fact opaque. We care, since we
322 // draw lots faster if we can flag the bitmap has being opaque
323 bool reallyHasAlpha = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000324 SkColorTable* colorTable = NULL;
325
326 if (color_type == PNG_COLOR_TYPE_PALETTE) {
327 int num_palette;
328 png_colorp palette;
329 png_bytep trans;
330 int num_trans;
331
332 png_get_PLTE(png_ptr, info_ptr, &palette, &num_palette);
rmistry@google.comd6176b02012-08-23 18:14:13 +0000333
reed@android.com8a1c16f2008-12-17 15:59:43 +0000334 /* BUGGY IMAGE WORKAROUND
rmistry@google.comd6176b02012-08-23 18:14:13 +0000335
reed@android.com8a1c16f2008-12-17 15:59:43 +0000336 We hit some images (e.g. fruit_.png) who contain bytes that are == colortable_count
337 which is a problem since we use the byte as an index. To work around this we grow
338 the colortable by 1 (if its < 256) and duplicate the last color into that slot.
339 */
340 int colorCount = num_palette + (num_palette < 256);
341
342 colorTable = SkNEW_ARGS(SkColorTable, (colorCount));
343
344 SkPMColor* colorPtr = colorTable->lockColors();
345 if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
346 png_get_tRNS(png_ptr, info_ptr, &trans, &num_trans, NULL);
347 hasAlpha = (num_trans > 0);
348 } else {
349 num_trans = 0;
350 colorTable->setFlags(colorTable->getFlags() | SkColorTable::kColorsAreOpaque_Flag);
rmistry@google.comd6176b02012-08-23 18:14:13 +0000351 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000352 // check for bad images that might make us crash
353 if (num_trans > num_palette) {
354 num_trans = num_palette;
355 }
356
357 int index = 0;
358 int transLessThanFF = 0;
359
360 for (; index < num_trans; index++) {
361 transLessThanFF |= (int)*trans - 0xFF;
362 *colorPtr++ = SkPreMultiplyARGB(*trans++, palette->red, palette->green, palette->blue);
363 palette++;
364 }
365 reallyHasAlpha |= (transLessThanFF < 0);
366
367 for (; index < num_palette; index++) {
368 *colorPtr++ = SkPackARGB32(0xFF, palette->red, palette->green, palette->blue);
369 palette++;
370 }
371
372 // see BUGGY IMAGE WORKAROUND comment above
373 if (num_palette < 256) {
374 *colorPtr = colorPtr[-1];
375 }
376 colorTable->unlockColors(true);
377 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000378
reed@android.com8a1c16f2008-12-17 15:59:43 +0000379 SkAutoUnref aur(colorTable);
380
reed@android.com11344262009-07-08 20:09:23 +0000381 if (!this->allocPixelRef(decodedBitmap,
reed@android.comb6137c32009-07-29 20:56:52 +0000382 SkBitmap::kIndex8_Config == config ?
383 colorTable : NULL)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000384 return false;
385 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000386
reed@android.com8a1c16f2008-12-17 15:59:43 +0000387 SkAutoLockPixels alp(*decodedBitmap);
388
389 /* swap the RGBA or GA data to ARGB or AG (or BGRA to ABGR) */
390// if (color_type == PNG_COLOR_TYPE_RGB_ALPHA)
391// ; // png_set_swap_alpha(png_ptr);
392
393 /* swap bytes of 16 bit files to least significant byte first */
394 // png_set_swap(png_ptr);
395
396 /* Add filler (or alpha) byte (before/after each RGB triplet) */
397 if (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_GRAY) {
398 png_set_filler(png_ptr, 0xff, PNG_FILLER_AFTER);
399 }
400
401 /* Turn on interlace handling. REQUIRED if you are not using
402 * png_read_image(). To see how to handle interlacing passes,
403 * see the png_read_row() method below:
404 */
rmistry@google.comd6176b02012-08-23 18:14:13 +0000405 const int number_passes = interlace_type != PNG_INTERLACE_NONE ?
reed@android.com8a1c16f2008-12-17 15:59:43 +0000406 png_set_interlace_handling(png_ptr) : 1;
407
408 /* Optional call to gamma correct and add the background to the palette
409 * and update info structure. REQUIRED if you are expecting libpng to
410 * update the palette for you (ie you selected such a transform above).
411 */
412 png_read_update_info(png_ptr, info_ptr);
413
414 if (SkBitmap::kIndex8_Config == config && 1 == sampleSize) {
415 for (int i = 0; i < number_passes; i++) {
416 for (png_uint_32 y = 0; y < origHeight; y++) {
417 uint8_t* bmRow = decodedBitmap->getAddr8(0, y);
reed@google.com7af00462010-12-31 18:11:59 +0000418 png_read_rows(png_ptr, &bmRow, NULL, 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000419 }
420 }
421 } else {
422 SkScaledBitmapSampler::SrcConfig sc;
423 int srcBytesPerPixel = 4;
rmistry@google.comd6176b02012-08-23 18:14:13 +0000424
reed@android.com11344262009-07-08 20:09:23 +0000425 if (colorTable != NULL) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000426 sc = SkScaledBitmapSampler::kIndex;
427 srcBytesPerPixel = 1;
428 } else if (hasAlpha) {
429 sc = SkScaledBitmapSampler::kRGBA;
430 } else {
431 sc = SkScaledBitmapSampler::kRGBX;
432 }
reed@android.com11344262009-07-08 20:09:23 +0000433
434 /* We have to pass the colortable explicitly, since we may have one
435 even if our decodedBitmap doesn't, due to the request that we
436 upscale png's palette to a direct model
437 */
438 SkAutoLockColors ctLock(colorTable);
439 if (!sampler.begin(decodedBitmap, sc, doDither, ctLock.colors())) {
reed@android.com862e91b2009-04-28 15:27:07 +0000440 return false;
441 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000442 const int height = decodedBitmap->height();
443
reed@android.com862e91b2009-04-28 15:27:07 +0000444 if (number_passes > 1) {
445 SkAutoMalloc storage(origWidth * origHeight * srcBytesPerPixel);
446 uint8_t* base = (uint8_t*)storage.get();
447 size_t rb = origWidth * srcBytesPerPixel;
reed@android.coma8a8b8b2009-05-04 15:00:11 +0000448
reed@android.com862e91b2009-04-28 15:27:07 +0000449 for (int i = 0; i < number_passes; i++) {
450 uint8_t* row = base;
451 for (png_uint_32 y = 0; y < origHeight; y++) {
452 uint8_t* bmRow = row;
reed@google.com7af00462010-12-31 18:11:59 +0000453 png_read_rows(png_ptr, &bmRow, NULL, 1);
reed@android.com862e91b2009-04-28 15:27:07 +0000454 row += rb;
455 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000456 }
reed@android.com862e91b2009-04-28 15:27:07 +0000457 // now sample it
458 base += sampler.srcY0() * rb;
459 for (int y = 0; y < height; y++) {
460 reallyHasAlpha |= sampler.next(base);
461 base += sampler.srcDY() * rb;
462 }
463 } else {
464 SkAutoMalloc storage(origWidth * srcBytesPerPixel);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000465 uint8_t* srcRow = (uint8_t*)storage.get();
466 skip_src_rows(png_ptr, srcRow, sampler.srcY0());
467
468 for (int y = 0; y < height; y++) {
469 uint8_t* tmp = srcRow;
reed@google.com7af00462010-12-31 18:11:59 +0000470 png_read_rows(png_ptr, &tmp, NULL, 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000471 reallyHasAlpha |= sampler.next(srcRow);
472 if (y < height - 1) {
473 skip_src_rows(png_ptr, srcRow, sampler.srcDY() - 1);
474 }
475 }
reed@android.com862e91b2009-04-28 15:27:07 +0000476
reed@android.com8a1c16f2008-12-17 15:59:43 +0000477 // skip the rest of the rows (if any)
478 png_uint_32 read = (height - 1) * sampler.srcDY() +
479 sampler.srcY0() + 1;
480 SkASSERT(read <= origHeight);
481 skip_src_rows(png_ptr, srcRow, origHeight - read);
482 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000483 }
484
485 /* read rest of file, and get additional chunks in info_ptr - REQUIRED */
486 png_read_end(png_ptr, info_ptr);
487
488 if (0 != theTranspColor) {
489 reallyHasAlpha |= substituteTranspColor(decodedBitmap, theTranspColor);
490 }
491 decodedBitmap->setIsOpaque(!reallyHasAlpha);
492 return true;
493}
494
495///////////////////////////////////////////////////////////////////////////////
496
reed@android.com8a1c16f2008-12-17 15:59:43 +0000497#include "SkColorPriv.h"
498#include "SkUnPreMultiply.h"
499
500static void sk_write_fn(png_structp png_ptr, png_bytep data, png_size_t len) {
reed@google.com29f66362011-02-25 15:07:30 +0000501 SkWStream* sk_stream = (SkWStream*)png_get_io_ptr(png_ptr);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000502 if (!sk_stream->write(data, len)) {
503 png_error(png_ptr, "sk_write_fn Error!");
504 }
505}
506
reed@android.com8a1c16f2008-12-17 15:59:43 +0000507static transform_scanline_proc choose_proc(SkBitmap::Config config,
508 bool hasAlpha) {
509 // we don't care about search on alpha if we're kIndex8, since only the
510 // colortable packing cares about that distinction, not the pixels
511 if (SkBitmap::kIndex8_Config == config) {
512 hasAlpha = false; // we store false in the table entries for kIndex8
513 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000514
reed@android.com8a1c16f2008-12-17 15:59:43 +0000515 static const struct {
516 SkBitmap::Config fConfig;
517 bool fHasAlpha;
518 transform_scanline_proc fProc;
519 } gMap[] = {
520 { SkBitmap::kRGB_565_Config, false, transform_scanline_565 },
521 { SkBitmap::kARGB_8888_Config, false, transform_scanline_888 },
522 { SkBitmap::kARGB_8888_Config, true, transform_scanline_8888 },
523 { SkBitmap::kARGB_4444_Config, false, transform_scanline_444 },
524 { SkBitmap::kARGB_4444_Config, true, transform_scanline_4444 },
epoger@google.com4ce738b2012-11-16 18:44:18 +0000525 { SkBitmap::kIndex8_Config, false, transform_scanline_memcpy },
reed@android.com8a1c16f2008-12-17 15:59:43 +0000526 };
527
528 for (int i = SK_ARRAY_COUNT(gMap) - 1; i >= 0; --i) {
529 if (gMap[i].fConfig == config && gMap[i].fHasAlpha == hasAlpha) {
530 return gMap[i].fProc;
531 }
532 }
533 sk_throw();
534 return NULL;
535}
536
537// return the minimum legal bitdepth (by png standards) for this many colortable
538// entries. SkBitmap always stores in 8bits per pixel, but for colorcount <= 16,
539// we can use fewer bits per in png
540static int computeBitDepth(int colorCount) {
541#if 0
542 int bits = SkNextLog2(colorCount);
543 SkASSERT(bits >= 1 && bits <= 8);
544 // now we need bits itself to be a power of 2 (e.g. 1, 2, 4, 8)
545 return SkNextPow2(bits);
546#else
547 // for the moment, we don't know how to pack bitdepth < 8
548 return 8;
549#endif
550}
551
552/* Pack palette[] with the corresponding colors, and if hasAlpha is true, also
553 pack trans[] and return the number of trans[] entries written. If hasAlpha
554 is false, the return value will always be 0.
rmistry@google.comd6176b02012-08-23 18:14:13 +0000555
reed@android.com8a1c16f2008-12-17 15:59:43 +0000556 Note: this routine takes care of unpremultiplying the RGB values when we
557 have alpha in the colortable, since png doesn't support premul colors
558*/
reed@android.com6f252972009-01-14 16:46:16 +0000559static inline int pack_palette(SkColorTable* ctable,
reed@android.comb50a60c2009-01-14 17:51:08 +0000560 png_color* SK_RESTRICT palette,
561 png_byte* SK_RESTRICT trans, bool hasAlpha) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000562 SkAutoLockColors alc(ctable);
563 const SkPMColor* SK_RESTRICT colors = alc.colors();
564 const int ctCount = ctable->count();
565 int i, num_trans = 0;
566
567 if (hasAlpha) {
568 /* first see if we have some number of fully opaque at the end of the
569 ctable. PNG allows num_trans < num_palette, but all of the trans
570 entries must come first in the palette. If I was smarter, I'd
571 reorder the indices and ctable so that all non-opaque colors came
572 first in the palette. But, since that would slow down the encode,
573 I'm leaving the indices and ctable order as is, and just looking
574 at the tail of the ctable for opaqueness.
575 */
576 num_trans = ctCount;
577 for (i = ctCount - 1; i >= 0; --i) {
578 if (SkGetPackedA32(colors[i]) != 0xFF) {
579 break;
580 }
581 num_trans -= 1;
582 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000583
reed@android.com8a1c16f2008-12-17 15:59:43 +0000584 const SkUnPreMultiply::Scale* SK_RESTRICT table =
585 SkUnPreMultiply::GetScaleTable();
586
587 for (i = 0; i < num_trans; i++) {
588 const SkPMColor c = *colors++;
589 const unsigned a = SkGetPackedA32(c);
590 const SkUnPreMultiply::Scale s = table[a];
591 trans[i] = a;
592 palette[i].red = SkUnPreMultiply::ApplyScale(s, SkGetPackedR32(c));
593 palette[i].green = SkUnPreMultiply::ApplyScale(s,SkGetPackedG32(c));
594 palette[i].blue = SkUnPreMultiply::ApplyScale(s, SkGetPackedB32(c));
rmistry@google.comd6176b02012-08-23 18:14:13 +0000595 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000596 // now fall out of this if-block to use common code for the trailing
597 // opaque entries
598 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000599
reed@android.com8a1c16f2008-12-17 15:59:43 +0000600 // these (remaining) entries are opaque
601 for (i = num_trans; i < ctCount; i++) {
602 SkPMColor c = *colors++;
603 palette[i].red = SkGetPackedR32(c);
604 palette[i].green = SkGetPackedG32(c);
605 palette[i].blue = SkGetPackedB32(c);
606 }
607 return num_trans;
608}
609
610class SkPNGImageEncoder : public SkImageEncoder {
611protected:
612 virtual bool onEncode(SkWStream* stream, const SkBitmap& bm, int quality);
tomhudson@google.com5c210c72011-07-28 21:06:40 +0000613private:
614 bool doEncode(SkWStream* stream, const SkBitmap& bm,
615 const bool& hasAlpha, int colorType,
616 int bitDepth, SkBitmap::Config config,
617 png_color_8& sig_bit);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000618};
619
620bool SkPNGImageEncoder::onEncode(SkWStream* stream, const SkBitmap& bitmap,
621 int /*quality*/) {
622 SkBitmap::Config config = bitmap.getConfig();
623
624 const bool hasAlpha = !bitmap.isOpaque();
625 int colorType = PNG_COLOR_MASK_COLOR;
626 int bitDepth = 8; // default for color
627 png_color_8 sig_bit;
628
629 switch (config) {
630 case SkBitmap::kIndex8_Config:
631 colorType |= PNG_COLOR_MASK_PALETTE;
632 // fall through to the ARGB_8888 case
633 case SkBitmap::kARGB_8888_Config:
634 sig_bit.red = 8;
635 sig_bit.green = 8;
636 sig_bit.blue = 8;
637 sig_bit.alpha = 8;
638 break;
639 case SkBitmap::kARGB_4444_Config:
640 sig_bit.red = 4;
641 sig_bit.green = 4;
642 sig_bit.blue = 4;
643 sig_bit.alpha = 4;
644 break;
645 case SkBitmap::kRGB_565_Config:
646 sig_bit.red = 5;
647 sig_bit.green = 6;
648 sig_bit.blue = 5;
649 sig_bit.alpha = 0;
650 break;
651 default:
652 return false;
653 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000654
reed@android.com8a1c16f2008-12-17 15:59:43 +0000655 if (hasAlpha) {
656 // don't specify alpha if we're a palette, even if our ctable has alpha
657 if (!(colorType & PNG_COLOR_MASK_PALETTE)) {
658 colorType |= PNG_COLOR_MASK_ALPHA;
659 }
660 } else {
661 sig_bit.alpha = 0;
662 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000663
reed@android.com8a1c16f2008-12-17 15:59:43 +0000664 SkAutoLockPixels alp(bitmap);
665 // readyToDraw checks for pixels (and colortable if that is required)
666 if (!bitmap.readyToDraw()) {
667 return false;
668 }
669
670 // we must do this after we have locked the pixels
671 SkColorTable* ctable = bitmap.getColorTable();
672 if (NULL != ctable) {
673 if (ctable->count() == 0) {
674 return false;
675 }
676 // check if we can store in fewer than 8 bits
677 bitDepth = computeBitDepth(ctable->count());
678 }
679
tomhudson@google.com5c210c72011-07-28 21:06:40 +0000680 return doEncode(stream, bitmap, hasAlpha, colorType,
681 bitDepth, config, sig_bit);
682}
683
684bool SkPNGImageEncoder::doEncode(SkWStream* stream, const SkBitmap& bitmap,
685 const bool& hasAlpha, int colorType,
686 int bitDepth, SkBitmap::Config config,
687 png_color_8& sig_bit) {
688
reed@android.com8a1c16f2008-12-17 15:59:43 +0000689 png_structp png_ptr;
690 png_infop info_ptr;
691
692 png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, sk_error_fn,
693 NULL);
694 if (NULL == png_ptr) {
695 return false;
696 }
697
698 info_ptr = png_create_info_struct(png_ptr);
699 if (NULL == info_ptr) {
reed@google.com7af00462010-12-31 18:11:59 +0000700 png_destroy_write_struct(&png_ptr, NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000701 return false;
702 }
703
704 /* Set error handling. REQUIRED if you aren't supplying your own
705 * error handling functions in the png_create_write_struct() call.
706 */
707 if (setjmp(png_jmpbuf(png_ptr))) {
708 png_destroy_write_struct(&png_ptr, &info_ptr);
709 return false;
710 }
711
reed@google.com7af00462010-12-31 18:11:59 +0000712 png_set_write_fn(png_ptr, (void*)stream, sk_write_fn, NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000713
714 /* Set the image information here. Width and height are up to 2^31,
715 * bit_depth is one of 1, 2, 4, 8, or 16, but valid values also depend on
716 * the color_type selected. color_type is one of PNG_COLOR_TYPE_GRAY,
717 * PNG_COLOR_TYPE_GRAY_ALPHA, PNG_COLOR_TYPE_PALETTE, PNG_COLOR_TYPE_RGB,
718 * or PNG_COLOR_TYPE_RGB_ALPHA. interlace is either PNG_INTERLACE_NONE or
719 * PNG_INTERLACE_ADAM7, and the compression_type and filter_type MUST
720 * currently be PNG_COMPRESSION_TYPE_BASE and PNG_FILTER_TYPE_BASE. REQUIRED
721 */
722
723 png_set_IHDR(png_ptr, info_ptr, bitmap.width(), bitmap.height(),
724 bitDepth, colorType,
725 PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE,
726 PNG_FILTER_TYPE_BASE);
727
reed@android.com61898772009-07-07 19:38:01 +0000728 // set our colortable/trans arrays if needed
729 png_color paletteColors[256];
730 png_byte trans[256];
731 if (SkBitmap::kIndex8_Config == config) {
732 SkColorTable* ct = bitmap.getColorTable();
733 int numTrans = pack_palette(ct, paletteColors, trans, hasAlpha);
734 png_set_PLTE(png_ptr, info_ptr, paletteColors, ct->count());
735 if (numTrans > 0) {
736 png_set_tRNS(png_ptr, info_ptr, trans, numTrans, NULL);
737 }
738 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000739
740 png_set_sBIT(png_ptr, info_ptr, &sig_bit);
741 png_write_info(png_ptr, info_ptr);
742
743 const char* srcImage = (const char*)bitmap.getPixels();
744 SkAutoSMalloc<1024> rowStorage(bitmap.width() << 2);
745 char* storage = (char*)rowStorage.get();
746 transform_scanline_proc proc = choose_proc(config, hasAlpha);
747
748 for (int y = 0; y < bitmap.height(); y++) {
749 png_bytep row_ptr = (png_bytep)storage;
750 proc(srcImage, bitmap.width(), storage);
751 png_write_rows(png_ptr, &row_ptr, 1);
752 srcImage += bitmap.rowBytes();
753 }
754
755 png_write_end(png_ptr, info_ptr);
756
757 /* clean up after the write, and free any memory allocated */
758 png_destroy_write_struct(&png_ptr, &info_ptr);
759 return true;
760}
761
reed@android.com00bf85a2009-01-22 13:04:56 +0000762///////////////////////////////////////////////////////////////////////////////
robertphillips@google.comec51cb82012-03-23 18:13:47 +0000763DEFINE_DECODER_CREATOR(PNGImageDecoder);
764DEFINE_ENCODER_CREATOR(PNGImageEncoder);
765///////////////////////////////////////////////////////////////////////////////
reed@android.com00bf85a2009-01-22 13:04:56 +0000766
767#include "SkTRegistry.h"
768
reed@android.comdfee5792010-04-15 14:24:50 +0000769#ifdef SK_ENABLE_LIBPNG
770 SkImageDecoder* sk_libpng_dfactory(SkStream*);
771 SkImageEncoder* sk_libpng_efactory(SkImageEncoder::Type);
772#endif
773
774SkImageDecoder* sk_libpng_dfactory(SkStream* stream) {
reed@android.com00bf85a2009-01-22 13:04:56 +0000775 char buf[PNG_BYTES_TO_CHECK];
776 if (stream->read(buf, PNG_BYTES_TO_CHECK) == PNG_BYTES_TO_CHECK &&
777 !png_sig_cmp((png_bytep) buf, (png_size_t)0, PNG_BYTES_TO_CHECK)) {
778 return SkNEW(SkPNGImageDecoder);
779 }
780 return NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000781}
782
reed@android.comdfee5792010-04-15 14:24:50 +0000783SkImageEncoder* sk_libpng_efactory(SkImageEncoder::Type t) {
reed@android.com00bf85a2009-01-22 13:04:56 +0000784 return (SkImageEncoder::kPNG_Type == t) ? SkNEW(SkPNGImageEncoder) : NULL;
785}
786
reed@android.comdfee5792010-04-15 14:24:50 +0000787static SkTRegistry<SkImageEncoder*, SkImageEncoder::Type> gEReg(sk_libpng_efactory);
788static SkTRegistry<SkImageDecoder*, SkStream*> gDReg(sk_libpng_dfactory);