blob: cefbe5e71c24d18e7c21f3fee61f3130f15f357e [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"
20
21extern "C" {
22#include "png.h"
23}
24
25class SkPNGImageDecoder : public SkImageDecoder {
26public:
27 virtual Format getFormat() const {
28 return kPNG_Format;
29 }
30
31protected:
reed@android.com3f1f06a2010-03-03 21:04:12 +000032 virtual bool onDecode(SkStream* stream, SkBitmap* bm, Mode);
reed@android.com8a1c16f2008-12-17 15:59:43 +000033};
34
35#ifndef png_jmpbuf
36# define png_jmpbuf(png_ptr) ((png_ptr)->jmpbuf)
37#endif
38
39#define PNG_BYTES_TO_CHECK 4
40
41/* Automatically clean up after throwing an exception */
42struct PNGAutoClean {
43 PNGAutoClean(png_structp p, png_infop i): png_ptr(p), info_ptr(i) {}
44 ~PNGAutoClean() {
reed@google.com7af00462010-12-31 18:11:59 +000045 png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +000046 }
47private:
48 png_structp png_ptr;
49 png_infop info_ptr;
50};
51
reed@android.com8a1c16f2008-12-17 15:59:43 +000052static void sk_read_fn(png_structp png_ptr, png_bytep data, png_size_t length) {
reed@google.com29f66362011-02-25 15:07:30 +000053 SkStream* sk_stream = (SkStream*)png_get_io_ptr(png_ptr);
reed@android.com8a1c16f2008-12-17 15:59:43 +000054 size_t bytes = sk_stream->read(data, length);
55 if (bytes != length) {
56 png_error(png_ptr, "Read Error!");
57 }
58}
59
60static int sk_read_user_chunk(png_structp png_ptr, png_unknown_chunkp chunk) {
61 SkImageDecoder::Peeker* peeker =
62 (SkImageDecoder::Peeker*)png_get_user_chunk_ptr(png_ptr);
63 // peek() returning true means continue decoding
64 return peeker->peek((const char*)chunk->name, chunk->data, chunk->size) ?
65 1 : -1;
66}
67
68static void sk_error_fn(png_structp png_ptr, png_const_charp msg) {
69#if 0
70 SkDebugf("------ png error %s\n", msg);
71#endif
72 longjmp(png_jmpbuf(png_ptr), 1);
73}
74
75static void skip_src_rows(png_structp png_ptr, uint8_t storage[], int count) {
76 for (int i = 0; i < count; i++) {
77 uint8_t* tmp = storage;
reed@google.com7af00462010-12-31 18:11:59 +000078 png_read_rows(png_ptr, &tmp, NULL, 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +000079 }
80}
81
82static bool pos_le(int value, int max) {
83 return value > 0 && value <= max;
84}
85
86static bool substituteTranspColor(SkBitmap* bm, SkPMColor match) {
87 SkASSERT(bm->config() == SkBitmap::kARGB_8888_Config);
88
89 bool reallyHasAlpha = false;
90
91 for (int y = bm->height() - 1; y >= 0; --y) {
92 SkPMColor* p = bm->getAddr32(0, y);
93 for (int x = bm->width() - 1; x >= 0; --x) {
94 if (match == *p) {
95 *p = 0;
96 reallyHasAlpha = true;
97 }
98 p += 1;
99 }
100 }
101 return reallyHasAlpha;
102}
103
reed@android.com3f1f06a2010-03-03 21:04:12 +0000104static bool canUpscalePaletteToConfig(SkBitmap::Config dstConfig,
reed@android.comb6137c32009-07-29 20:56:52 +0000105 bool srcHasAlpha) {
reed@android.com3f1f06a2010-03-03 21:04:12 +0000106 switch (dstConfig) {
reed@android.comb6137c32009-07-29 20:56:52 +0000107 case SkBitmap::kARGB_8888_Config:
108 case SkBitmap::kARGB_4444_Config:
109 return true;
110 case SkBitmap::kRGB_565_Config:
111 // only return true if the src is opaque (since 565 is opaque)
112 return !srcHasAlpha;
113 default:
114 return false;
115 }
116}
117
118// call only if color_type is PALETTE. Returns true if the ctable has alpha
119static bool hasTransparencyInPalette(png_structp png_ptr, png_infop info_ptr) {
120 png_bytep trans;
121 int num_trans;
122
123 if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
124 png_get_tRNS(png_ptr, info_ptr, &trans, &num_trans, NULL);
125 return num_trans > 0;
126 }
127 return false;
reed@android.com11344262009-07-08 20:09:23 +0000128}
129
reed@android.com8a1c16f2008-12-17 15:59:43 +0000130bool SkPNGImageDecoder::onDecode(SkStream* sk_stream, SkBitmap* decodedBitmap,
reed@android.com3f1f06a2010-03-03 21:04:12 +0000131 Mode mode) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000132// SkAutoTrace apr("SkPNGImageDecoder::onDecode");
133
134 /* Create and initialize the png_struct with the desired error handler
135 * functions. If you want to use the default stderr and longjump method,
136 * you can supply NULL for the last three parameters. We also supply the
137 * the compiler header file version, so that we know if the application
138 * was compiled with a compatible version of the library. */
139 png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
140 NULL, sk_error_fn, NULL);
141 // png_voidp user_error_ptr, user_error_fn, user_warning_fn);
142 if (png_ptr == NULL) {
143 return false;
144 }
145
146 /* Allocate/initialize the memory for image information. */
147 png_infop info_ptr = png_create_info_struct(png_ptr);
148 if (info_ptr == NULL) {
reed@google.com7af00462010-12-31 18:11:59 +0000149 png_destroy_read_struct(&png_ptr, NULL, NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000150 return false;
151 }
152
153 PNGAutoClean autoClean(png_ptr, info_ptr);
154
155 /* Set error handling if you are using the setjmp/longjmp method (this is
156 * the normal method of doing things with libpng). REQUIRED unless you
157 * set up your own error handlers in the png_create_read_struct() earlier.
158 */
159 if (setjmp(png_jmpbuf(png_ptr))) {
160 return false;
161 }
162
163 /* If you are using replacement read functions, instead of calling
164 * png_init_io() here you would call:
165 */
166 png_set_read_fn(png_ptr, (void *)sk_stream, sk_read_fn);
167 /* where user_io_ptr is a structure you want available to the callbacks */
168 /* If we have already read some of the signature */
169// png_set_sig_bytes(png_ptr, 0 /* sig_read */ );
170
171 // hookup our peeker so we can see any user-chunks the caller may be interested in
172 png_set_keep_unknown_chunks(png_ptr, PNG_HANDLE_CHUNK_ALWAYS, (png_byte*)"", 0);
173 if (this->getPeeker()) {
174 png_set_read_user_chunk_fn(png_ptr, (png_voidp)this->getPeeker(), sk_read_user_chunk);
175 }
176
177 /* The call to png_read_info() gives us all of the information from the
178 * PNG file before the first IDAT (image data chunk). */
179 png_read_info(png_ptr, info_ptr);
180 png_uint_32 origWidth, origHeight;
181 int bit_depth, color_type, interlace_type;
182 png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bit_depth, &color_type,
reed@google.com7af00462010-12-31 18:11:59 +0000183 &interlace_type, NULL, NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000184
185 /* tell libpng to strip 16 bit/color files down to 8 bits/color */
186 if (bit_depth == 16) {
187 png_set_strip_16(png_ptr);
188 }
189 /* Extract multiple pixels with bit depths of 1, 2, and 4 from a single
190 * byte into separate bytes (useful for paletted and grayscale images). */
191 if (bit_depth < 8) {
192 png_set_packing(png_ptr);
193 }
194 /* Expand grayscale images to the full 8 bits from 1, 2, or 4 bits/pixel */
195 if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) {
reed@google.com29f66362011-02-25 15:07:30 +0000196 png_set_expand_gray_1_2_4_to_8(png_ptr);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000197 }
reed@google.com29f66362011-02-25 15:07:30 +0000198
reed@android.com8a1c16f2008-12-17 15:59:43 +0000199 /* Make a grayscale image into RGB. */
200 if (color_type == PNG_COLOR_TYPE_GRAY ||
201 color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
202 png_set_gray_to_rgb(png_ptr);
203 }
204
205 SkBitmap::Config config;
206 bool hasAlpha = false;
207 bool doDither = this->getDitherImage();
208 SkPMColor theTranspColor = 0; // 0 tells us not to try to match
209
210 // check for sBIT chunk data, in case we should disable dithering because
211 // our data is not truely 8bits per component
212 if (doDither) {
reed@google.com29f66362011-02-25 15:07:30 +0000213 png_color_8p sig_bit = NULL;
214 bool has_sbit = PNG_INFO_sBIT == png_get_sBIT(png_ptr, info_ptr,
215 &sig_bit);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000216#if 0
reed@google.com29f66362011-02-25 15:07:30 +0000217 if (has_sbit) {
218 SkDebugf("----- sBIT %d %d %d %d\n", sig_bit->red, sig_bit->green,
219 sig_bit->blue, sig_bit->alpha);
220 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000221#endif
222 // 0 seems to indicate no information available
reed@google.com29f66362011-02-25 15:07:30 +0000223 if (has_sbit && pos_le(sig_bit->red, SK_R16_BITS) &&
224 pos_le(sig_bit->green, SK_G16_BITS) &&
225 pos_le(sig_bit->blue, SK_B16_BITS)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000226 doDither = false;
227 }
228 }
229
230 if (color_type == PNG_COLOR_TYPE_PALETTE) {
reed@android.comb6137c32009-07-29 20:56:52 +0000231 bool paletteHasAlpha = hasTransparencyInPalette(png_ptr, info_ptr);
reed@android.com3f1f06a2010-03-03 21:04:12 +0000232 config = this->getPrefConfig(kIndex_SrcDepth, paletteHasAlpha);
233 // now see if we can upscale to their requested config
234 if (!canUpscalePaletteToConfig(config, paletteHasAlpha)) {
235 config = SkBitmap::kIndex8_Config;
reed@android.comb6137c32009-07-29 20:56:52 +0000236 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000237 } else {
238 png_color_16p transpColor = NULL;
239 int numTransp = 0;
240
241 png_get_tRNS(png_ptr, info_ptr, NULL, &numTransp, &transpColor);
242
243 bool valid = png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS);
244
245 if (valid && numTransp == 1 && transpColor != NULL) {
246 /* Compute our transparent color, which we'll match against later.
247 We don't really handle 16bit components properly here, since we
248 do our compare *after* the values have been knocked down to 8bit
249 which means we will find more matches than we should. The real
250 fix seems to be to see the actual 16bit components, do the
251 compare, and then knock it down to 8bits ourselves.
252 */
253 if (color_type & PNG_COLOR_MASK_COLOR) {
254 if (16 == bit_depth) {
255 theTranspColor = SkPackARGB32(0xFF, transpColor->red >> 8,
256 transpColor->green >> 8, transpColor->blue >> 8);
257 } else {
258 theTranspColor = SkPackARGB32(0xFF, transpColor->red,
259 transpColor->green, transpColor->blue);
260 }
261 } else { // gray
262 if (16 == bit_depth) {
263 theTranspColor = SkPackARGB32(0xFF, transpColor->gray >> 8,
264 transpColor->gray >> 8, transpColor->gray >> 8);
265 } else {
266 theTranspColor = SkPackARGB32(0xFF, transpColor->gray,
267 transpColor->gray, transpColor->gray);
268 }
269 }
270 }
271
272 if (valid ||
273 PNG_COLOR_TYPE_RGB_ALPHA == color_type ||
274 PNG_COLOR_TYPE_GRAY_ALPHA == color_type) {
275 hasAlpha = true;
reed@android.com3f1f06a2010-03-03 21:04:12 +0000276 }
277 config = this->getPrefConfig(k32Bit_SrcDepth, hasAlpha);
278 // now match the request against our capabilities
279 if (hasAlpha) {
280 if (config != SkBitmap::kARGB_4444_Config) {
281 config = SkBitmap::kARGB_8888_Config;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000282 }
reed@android.com3f1f06a2010-03-03 21:04:12 +0000283 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000284 if (config != SkBitmap::kRGB_565_Config &&
reed@android.com3f1f06a2010-03-03 21:04:12 +0000285 config != SkBitmap::kARGB_4444_Config) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000286 config = SkBitmap::kARGB_8888_Config;
287 }
288 }
289 }
reed@android.coma8a8b8b2009-05-04 15:00:11 +0000290
291 // sanity check for size
292 {
293 Sk64 size;
294 size.setMul(origWidth, origHeight);
295 if (size.isNeg() || !size.is32()) {
296 return false;
297 }
298 // now check that if we are 4-bytes per pixel, we also don't overflow
299 if (size.get32() > (0x7FFFFFFF >> 2)) {
300 return false;
301 }
302 }
303
reed@android.com8a1c16f2008-12-17 15:59:43 +0000304 if (!this->chooseFromOneChoice(config, origWidth, origHeight)) {
305 return false;
306 }
307
308 const int sampleSize = this->getSampleSize();
309 SkScaledBitmapSampler sampler(origWidth, origHeight, sampleSize);
310
311 decodedBitmap->setConfig(config, sampler.scaledWidth(),
312 sampler.scaledHeight(), 0);
313 if (SkImageDecoder::kDecodeBounds_Mode == mode) {
314 return true;
315 }
316
317 // from here down we are concerned with colortables and pixels
318
319 // we track if we actually see a non-opaque pixels, since sometimes a PNG sets its colortype
320 // to |= PNG_COLOR_MASK_ALPHA, but all of its pixels are in fact opaque. We care, since we
321 // draw lots faster if we can flag the bitmap has being opaque
322 bool reallyHasAlpha = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000323 SkColorTable* colorTable = NULL;
324
325 if (color_type == PNG_COLOR_TYPE_PALETTE) {
326 int num_palette;
327 png_colorp palette;
328 png_bytep trans;
329 int num_trans;
330
331 png_get_PLTE(png_ptr, info_ptr, &palette, &num_palette);
332
333 /* BUGGY IMAGE WORKAROUND
334
335 We hit some images (e.g. fruit_.png) who contain bytes that are == colortable_count
336 which is a problem since we use the byte as an index. To work around this we grow
337 the colortable by 1 (if its < 256) and duplicate the last color into that slot.
338 */
339 int colorCount = num_palette + (num_palette < 256);
340
341 colorTable = SkNEW_ARGS(SkColorTable, (colorCount));
342
343 SkPMColor* colorPtr = colorTable->lockColors();
344 if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
345 png_get_tRNS(png_ptr, info_ptr, &trans, &num_trans, NULL);
346 hasAlpha = (num_trans > 0);
347 } else {
348 num_trans = 0;
349 colorTable->setFlags(colorTable->getFlags() | SkColorTable::kColorsAreOpaque_Flag);
350 }
351 // check for bad images that might make us crash
352 if (num_trans > num_palette) {
353 num_trans = num_palette;
354 }
355
356 int index = 0;
357 int transLessThanFF = 0;
358
359 for (; index < num_trans; index++) {
360 transLessThanFF |= (int)*trans - 0xFF;
361 *colorPtr++ = SkPreMultiplyARGB(*trans++, palette->red, palette->green, palette->blue);
362 palette++;
363 }
364 reallyHasAlpha |= (transLessThanFF < 0);
365
366 for (; index < num_palette; index++) {
367 *colorPtr++ = SkPackARGB32(0xFF, palette->red, palette->green, palette->blue);
368 palette++;
369 }
370
371 // see BUGGY IMAGE WORKAROUND comment above
372 if (num_palette < 256) {
373 *colorPtr = colorPtr[-1];
374 }
375 colorTable->unlockColors(true);
376 }
377
378 SkAutoUnref aur(colorTable);
379
reed@android.com11344262009-07-08 20:09:23 +0000380 if (!this->allocPixelRef(decodedBitmap,
reed@android.comb6137c32009-07-29 20:56:52 +0000381 SkBitmap::kIndex8_Config == config ?
382 colorTable : NULL)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000383 return false;
384 }
385
386 SkAutoLockPixels alp(*decodedBitmap);
387
388 /* swap the RGBA or GA data to ARGB or AG (or BGRA to ABGR) */
389// if (color_type == PNG_COLOR_TYPE_RGB_ALPHA)
390// ; // png_set_swap_alpha(png_ptr);
391
392 /* swap bytes of 16 bit files to least significant byte first */
393 // png_set_swap(png_ptr);
394
395 /* Add filler (or alpha) byte (before/after each RGB triplet) */
396 if (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_GRAY) {
397 png_set_filler(png_ptr, 0xff, PNG_FILLER_AFTER);
398 }
399
400 /* Turn on interlace handling. REQUIRED if you are not using
401 * png_read_image(). To see how to handle interlacing passes,
402 * see the png_read_row() method below:
403 */
404 const int number_passes = interlace_type != PNG_INTERLACE_NONE ?
405 png_set_interlace_handling(png_ptr) : 1;
406
407 /* Optional call to gamma correct and add the background to the palette
408 * and update info structure. REQUIRED if you are expecting libpng to
409 * update the palette for you (ie you selected such a transform above).
410 */
411 png_read_update_info(png_ptr, info_ptr);
412
413 if (SkBitmap::kIndex8_Config == config && 1 == sampleSize) {
414 for (int i = 0; i < number_passes; i++) {
415 for (png_uint_32 y = 0; y < origHeight; y++) {
416 uint8_t* bmRow = decodedBitmap->getAddr8(0, y);
reed@google.com7af00462010-12-31 18:11:59 +0000417 png_read_rows(png_ptr, &bmRow, NULL, 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000418 }
419 }
420 } else {
421 SkScaledBitmapSampler::SrcConfig sc;
422 int srcBytesPerPixel = 4;
423
reed@android.com11344262009-07-08 20:09:23 +0000424 if (colorTable != NULL) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000425 sc = SkScaledBitmapSampler::kIndex;
426 srcBytesPerPixel = 1;
427 } else if (hasAlpha) {
428 sc = SkScaledBitmapSampler::kRGBA;
429 } else {
430 sc = SkScaledBitmapSampler::kRGBX;
431 }
reed@android.com11344262009-07-08 20:09:23 +0000432
433 /* We have to pass the colortable explicitly, since we may have one
434 even if our decodedBitmap doesn't, due to the request that we
435 upscale png's palette to a direct model
436 */
437 SkAutoLockColors ctLock(colorTable);
438 if (!sampler.begin(decodedBitmap, sc, doDither, ctLock.colors())) {
reed@android.com862e91b2009-04-28 15:27:07 +0000439 return false;
440 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000441 const int height = decodedBitmap->height();
442
reed@android.com862e91b2009-04-28 15:27:07 +0000443 if (number_passes > 1) {
444 SkAutoMalloc storage(origWidth * origHeight * srcBytesPerPixel);
445 uint8_t* base = (uint8_t*)storage.get();
446 size_t rb = origWidth * srcBytesPerPixel;
reed@android.coma8a8b8b2009-05-04 15:00:11 +0000447
reed@android.com862e91b2009-04-28 15:27:07 +0000448 for (int i = 0; i < number_passes; i++) {
449 uint8_t* row = base;
450 for (png_uint_32 y = 0; y < origHeight; y++) {
451 uint8_t* bmRow = row;
reed@google.com7af00462010-12-31 18:11:59 +0000452 png_read_rows(png_ptr, &bmRow, NULL, 1);
reed@android.com862e91b2009-04-28 15:27:07 +0000453 row += rb;
454 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000455 }
reed@android.com862e91b2009-04-28 15:27:07 +0000456 // now sample it
457 base += sampler.srcY0() * rb;
458 for (int y = 0; y < height; y++) {
459 reallyHasAlpha |= sampler.next(base);
460 base += sampler.srcDY() * rb;
461 }
462 } else {
463 SkAutoMalloc storage(origWidth * srcBytesPerPixel);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000464 uint8_t* srcRow = (uint8_t*)storage.get();
465 skip_src_rows(png_ptr, srcRow, sampler.srcY0());
466
467 for (int y = 0; y < height; y++) {
468 uint8_t* tmp = srcRow;
reed@google.com7af00462010-12-31 18:11:59 +0000469 png_read_rows(png_ptr, &tmp, NULL, 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000470 reallyHasAlpha |= sampler.next(srcRow);
471 if (y < height - 1) {
472 skip_src_rows(png_ptr, srcRow, sampler.srcDY() - 1);
473 }
474 }
reed@android.com862e91b2009-04-28 15:27:07 +0000475
reed@android.com8a1c16f2008-12-17 15:59:43 +0000476 // skip the rest of the rows (if any)
477 png_uint_32 read = (height - 1) * sampler.srcDY() +
478 sampler.srcY0() + 1;
479 SkASSERT(read <= origHeight);
480 skip_src_rows(png_ptr, srcRow, origHeight - read);
481 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000482 }
483
484 /* read rest of file, and get additional chunks in info_ptr - REQUIRED */
485 png_read_end(png_ptr, info_ptr);
486
487 if (0 != theTranspColor) {
488 reallyHasAlpha |= substituteTranspColor(decodedBitmap, theTranspColor);
489 }
490 decodedBitmap->setIsOpaque(!reallyHasAlpha);
491 return true;
492}
493
494///////////////////////////////////////////////////////////////////////////////
495
reed@android.com8a1c16f2008-12-17 15:59:43 +0000496#include "SkColorPriv.h"
497#include "SkUnPreMultiply.h"
498
499static void sk_write_fn(png_structp png_ptr, png_bytep data, png_size_t len) {
reed@google.com29f66362011-02-25 15:07:30 +0000500 SkWStream* sk_stream = (SkWStream*)png_get_io_ptr(png_ptr);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000501 if (!sk_stream->write(data, len)) {
502 png_error(png_ptr, "sk_write_fn Error!");
503 }
504}
505
506typedef void (*transform_scanline_proc)(const char* SK_RESTRICT src,
507 int width, char* SK_RESTRICT dst);
508
509static void transform_scanline_565(const char* SK_RESTRICT src, int width,
510 char* SK_RESTRICT dst) {
511 const uint16_t* SK_RESTRICT srcP = (const uint16_t*)src;
512 for (int i = 0; i < width; i++) {
513 unsigned c = *srcP++;
514 *dst++ = SkPacked16ToR32(c);
515 *dst++ = SkPacked16ToG32(c);
516 *dst++ = SkPacked16ToB32(c);
517 }
518}
519
520static void transform_scanline_888(const char* SK_RESTRICT src, int width,
521 char* SK_RESTRICT dst) {
522 const SkPMColor* SK_RESTRICT srcP = (const SkPMColor*)src;
523 for (int i = 0; i < width; i++) {
524 SkPMColor c = *srcP++;
525 *dst++ = SkGetPackedR32(c);
526 *dst++ = SkGetPackedG32(c);
527 *dst++ = SkGetPackedB32(c);
528 }
529}
530
531static void transform_scanline_444(const char* SK_RESTRICT src, int width,
532 char* SK_RESTRICT dst) {
533 const SkPMColor16* SK_RESTRICT srcP = (const SkPMColor16*)src;
534 for (int i = 0; i < width; i++) {
535 SkPMColor16 c = *srcP++;
536 *dst++ = SkPacked4444ToR32(c);
537 *dst++ = SkPacked4444ToG32(c);
538 *dst++ = SkPacked4444ToB32(c);
539 }
540}
541
542static void transform_scanline_8888(const char* SK_RESTRICT src, int width,
543 char* SK_RESTRICT dst) {
544 const SkPMColor* SK_RESTRICT srcP = (const SkPMColor*)src;
545 const SkUnPreMultiply::Scale* SK_RESTRICT table =
546 SkUnPreMultiply::GetScaleTable();
547
548 for (int i = 0; i < width; i++) {
549 SkPMColor c = *srcP++;
550 unsigned a = SkGetPackedA32(c);
551 unsigned r = SkGetPackedR32(c);
552 unsigned g = SkGetPackedG32(c);
553 unsigned b = SkGetPackedB32(c);
554
555 if (0 != a && 255 != a) {
556 SkUnPreMultiply::Scale scale = table[a];
557 r = SkUnPreMultiply::ApplyScale(scale, r);
558 g = SkUnPreMultiply::ApplyScale(scale, g);
559 b = SkUnPreMultiply::ApplyScale(scale, b);
560 }
561 *dst++ = r;
562 *dst++ = g;
563 *dst++ = b;
564 *dst++ = a;
565 }
566}
567
568static void transform_scanline_4444(const char* SK_RESTRICT src, int width,
569 char* SK_RESTRICT dst) {
570 const SkPMColor16* SK_RESTRICT srcP = (const SkPMColor16*)src;
571 const SkUnPreMultiply::Scale* SK_RESTRICT table =
572 SkUnPreMultiply::GetScaleTable();
573
574 for (int i = 0; i < width; i++) {
575 SkPMColor16 c = *srcP++;
576 unsigned a = SkPacked4444ToA32(c);
577 unsigned r = SkPacked4444ToR32(c);
578 unsigned g = SkPacked4444ToG32(c);
579 unsigned b = SkPacked4444ToB32(c);
580
581 if (0 != a && 255 != a) {
582 SkUnPreMultiply::Scale scale = table[a];
583 r = SkUnPreMultiply::ApplyScale(scale, r);
584 g = SkUnPreMultiply::ApplyScale(scale, g);
585 b = SkUnPreMultiply::ApplyScale(scale, b);
586 }
587 *dst++ = r;
588 *dst++ = g;
589 *dst++ = b;
590 *dst++ = a;
591 }
592}
593
594static void transform_scanline_index8(const char* SK_RESTRICT src, int width,
595 char* SK_RESTRICT dst) {
596 memcpy(dst, src, width);
597}
598
599static transform_scanline_proc choose_proc(SkBitmap::Config config,
600 bool hasAlpha) {
601 // we don't care about search on alpha if we're kIndex8, since only the
602 // colortable packing cares about that distinction, not the pixels
603 if (SkBitmap::kIndex8_Config == config) {
604 hasAlpha = false; // we store false in the table entries for kIndex8
605 }
606
607 static const struct {
608 SkBitmap::Config fConfig;
609 bool fHasAlpha;
610 transform_scanline_proc fProc;
611 } gMap[] = {
612 { SkBitmap::kRGB_565_Config, false, transform_scanline_565 },
613 { SkBitmap::kARGB_8888_Config, false, transform_scanline_888 },
614 { SkBitmap::kARGB_8888_Config, true, transform_scanline_8888 },
615 { SkBitmap::kARGB_4444_Config, false, transform_scanline_444 },
616 { SkBitmap::kARGB_4444_Config, true, transform_scanline_4444 },
617 { SkBitmap::kIndex8_Config, false, transform_scanline_index8 },
618 };
619
620 for (int i = SK_ARRAY_COUNT(gMap) - 1; i >= 0; --i) {
621 if (gMap[i].fConfig == config && gMap[i].fHasAlpha == hasAlpha) {
622 return gMap[i].fProc;
623 }
624 }
625 sk_throw();
626 return NULL;
627}
628
629// return the minimum legal bitdepth (by png standards) for this many colortable
630// entries. SkBitmap always stores in 8bits per pixel, but for colorcount <= 16,
631// we can use fewer bits per in png
632static int computeBitDepth(int colorCount) {
633#if 0
634 int bits = SkNextLog2(colorCount);
635 SkASSERT(bits >= 1 && bits <= 8);
636 // now we need bits itself to be a power of 2 (e.g. 1, 2, 4, 8)
637 return SkNextPow2(bits);
638#else
639 // for the moment, we don't know how to pack bitdepth < 8
640 return 8;
641#endif
642}
643
644/* Pack palette[] with the corresponding colors, and if hasAlpha is true, also
645 pack trans[] and return the number of trans[] entries written. If hasAlpha
646 is false, the return value will always be 0.
647
648 Note: this routine takes care of unpremultiplying the RGB values when we
649 have alpha in the colortable, since png doesn't support premul colors
650*/
reed@android.com6f252972009-01-14 16:46:16 +0000651static inline int pack_palette(SkColorTable* ctable,
reed@android.comb50a60c2009-01-14 17:51:08 +0000652 png_color* SK_RESTRICT palette,
653 png_byte* SK_RESTRICT trans, bool hasAlpha) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000654 SkAutoLockColors alc(ctable);
655 const SkPMColor* SK_RESTRICT colors = alc.colors();
656 const int ctCount = ctable->count();
657 int i, num_trans = 0;
658
659 if (hasAlpha) {
660 /* first see if we have some number of fully opaque at the end of the
661 ctable. PNG allows num_trans < num_palette, but all of the trans
662 entries must come first in the palette. If I was smarter, I'd
663 reorder the indices and ctable so that all non-opaque colors came
664 first in the palette. But, since that would slow down the encode,
665 I'm leaving the indices and ctable order as is, and just looking
666 at the tail of the ctable for opaqueness.
667 */
668 num_trans = ctCount;
669 for (i = ctCount - 1; i >= 0; --i) {
670 if (SkGetPackedA32(colors[i]) != 0xFF) {
671 break;
672 }
673 num_trans -= 1;
674 }
675
676 const SkUnPreMultiply::Scale* SK_RESTRICT table =
677 SkUnPreMultiply::GetScaleTable();
678
679 for (i = 0; i < num_trans; i++) {
680 const SkPMColor c = *colors++;
681 const unsigned a = SkGetPackedA32(c);
682 const SkUnPreMultiply::Scale s = table[a];
683 trans[i] = a;
684 palette[i].red = SkUnPreMultiply::ApplyScale(s, SkGetPackedR32(c));
685 palette[i].green = SkUnPreMultiply::ApplyScale(s,SkGetPackedG32(c));
686 palette[i].blue = SkUnPreMultiply::ApplyScale(s, SkGetPackedB32(c));
687 }
688 // now fall out of this if-block to use common code for the trailing
689 // opaque entries
690 }
691
692 // these (remaining) entries are opaque
693 for (i = num_trans; i < ctCount; i++) {
694 SkPMColor c = *colors++;
695 palette[i].red = SkGetPackedR32(c);
696 palette[i].green = SkGetPackedG32(c);
697 palette[i].blue = SkGetPackedB32(c);
698 }
699 return num_trans;
700}
701
702class SkPNGImageEncoder : public SkImageEncoder {
703protected:
704 virtual bool onEncode(SkWStream* stream, const SkBitmap& bm, int quality);
tomhudson@google.com5c210c72011-07-28 21:06:40 +0000705private:
706 bool doEncode(SkWStream* stream, const SkBitmap& bm,
707 const bool& hasAlpha, int colorType,
708 int bitDepth, SkBitmap::Config config,
709 png_color_8& sig_bit);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000710};
711
712bool SkPNGImageEncoder::onEncode(SkWStream* stream, const SkBitmap& bitmap,
713 int /*quality*/) {
714 SkBitmap::Config config = bitmap.getConfig();
715
716 const bool hasAlpha = !bitmap.isOpaque();
717 int colorType = PNG_COLOR_MASK_COLOR;
718 int bitDepth = 8; // default for color
719 png_color_8 sig_bit;
720
721 switch (config) {
722 case SkBitmap::kIndex8_Config:
723 colorType |= PNG_COLOR_MASK_PALETTE;
724 // fall through to the ARGB_8888 case
725 case SkBitmap::kARGB_8888_Config:
726 sig_bit.red = 8;
727 sig_bit.green = 8;
728 sig_bit.blue = 8;
729 sig_bit.alpha = 8;
730 break;
731 case SkBitmap::kARGB_4444_Config:
732 sig_bit.red = 4;
733 sig_bit.green = 4;
734 sig_bit.blue = 4;
735 sig_bit.alpha = 4;
736 break;
737 case SkBitmap::kRGB_565_Config:
738 sig_bit.red = 5;
739 sig_bit.green = 6;
740 sig_bit.blue = 5;
741 sig_bit.alpha = 0;
742 break;
743 default:
744 return false;
745 }
746
747 if (hasAlpha) {
748 // don't specify alpha if we're a palette, even if our ctable has alpha
749 if (!(colorType & PNG_COLOR_MASK_PALETTE)) {
750 colorType |= PNG_COLOR_MASK_ALPHA;
751 }
752 } else {
753 sig_bit.alpha = 0;
754 }
755
756 SkAutoLockPixels alp(bitmap);
757 // readyToDraw checks for pixels (and colortable if that is required)
758 if (!bitmap.readyToDraw()) {
759 return false;
760 }
761
762 // we must do this after we have locked the pixels
763 SkColorTable* ctable = bitmap.getColorTable();
764 if (NULL != ctable) {
765 if (ctable->count() == 0) {
766 return false;
767 }
768 // check if we can store in fewer than 8 bits
769 bitDepth = computeBitDepth(ctable->count());
770 }
771
tomhudson@google.com5c210c72011-07-28 21:06:40 +0000772 return doEncode(stream, bitmap, hasAlpha, colorType,
773 bitDepth, config, sig_bit);
774}
775
776bool SkPNGImageEncoder::doEncode(SkWStream* stream, const SkBitmap& bitmap,
777 const bool& hasAlpha, int colorType,
778 int bitDepth, SkBitmap::Config config,
779 png_color_8& sig_bit) {
780
reed@android.com8a1c16f2008-12-17 15:59:43 +0000781 png_structp png_ptr;
782 png_infop info_ptr;
783
784 png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, sk_error_fn,
785 NULL);
786 if (NULL == png_ptr) {
787 return false;
788 }
789
790 info_ptr = png_create_info_struct(png_ptr);
791 if (NULL == info_ptr) {
reed@google.com7af00462010-12-31 18:11:59 +0000792 png_destroy_write_struct(&png_ptr, NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000793 return false;
794 }
795
796 /* Set error handling. REQUIRED if you aren't supplying your own
797 * error handling functions in the png_create_write_struct() call.
798 */
799 if (setjmp(png_jmpbuf(png_ptr))) {
800 png_destroy_write_struct(&png_ptr, &info_ptr);
801 return false;
802 }
803
reed@google.com7af00462010-12-31 18:11:59 +0000804 png_set_write_fn(png_ptr, (void*)stream, sk_write_fn, NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000805
806 /* Set the image information here. Width and height are up to 2^31,
807 * bit_depth is one of 1, 2, 4, 8, or 16, but valid values also depend on
808 * the color_type selected. color_type is one of PNG_COLOR_TYPE_GRAY,
809 * PNG_COLOR_TYPE_GRAY_ALPHA, PNG_COLOR_TYPE_PALETTE, PNG_COLOR_TYPE_RGB,
810 * or PNG_COLOR_TYPE_RGB_ALPHA. interlace is either PNG_INTERLACE_NONE or
811 * PNG_INTERLACE_ADAM7, and the compression_type and filter_type MUST
812 * currently be PNG_COMPRESSION_TYPE_BASE and PNG_FILTER_TYPE_BASE. REQUIRED
813 */
814
815 png_set_IHDR(png_ptr, info_ptr, bitmap.width(), bitmap.height(),
816 bitDepth, colorType,
817 PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE,
818 PNG_FILTER_TYPE_BASE);
819
reed@android.com61898772009-07-07 19:38:01 +0000820 // set our colortable/trans arrays if needed
821 png_color paletteColors[256];
822 png_byte trans[256];
823 if (SkBitmap::kIndex8_Config == config) {
824 SkColorTable* ct = bitmap.getColorTable();
825 int numTrans = pack_palette(ct, paletteColors, trans, hasAlpha);
826 png_set_PLTE(png_ptr, info_ptr, paletteColors, ct->count());
827 if (numTrans > 0) {
828 png_set_tRNS(png_ptr, info_ptr, trans, numTrans, NULL);
829 }
830 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000831
832 png_set_sBIT(png_ptr, info_ptr, &sig_bit);
833 png_write_info(png_ptr, info_ptr);
834
835 const char* srcImage = (const char*)bitmap.getPixels();
836 SkAutoSMalloc<1024> rowStorage(bitmap.width() << 2);
837 char* storage = (char*)rowStorage.get();
838 transform_scanline_proc proc = choose_proc(config, hasAlpha);
839
840 for (int y = 0; y < bitmap.height(); y++) {
841 png_bytep row_ptr = (png_bytep)storage;
842 proc(srcImage, bitmap.width(), storage);
843 png_write_rows(png_ptr, &row_ptr, 1);
844 srcImage += bitmap.rowBytes();
845 }
846
847 png_write_end(png_ptr, info_ptr);
848
849 /* clean up after the write, and free any memory allocated */
850 png_destroy_write_struct(&png_ptr, &info_ptr);
851 return true;
852}
853
reed@android.com00bf85a2009-01-22 13:04:56 +0000854///////////////////////////////////////////////////////////////////////////////
855
856#include "SkTRegistry.h"
857
reed@android.comdfee5792010-04-15 14:24:50 +0000858#ifdef SK_ENABLE_LIBPNG
859 SkImageDecoder* sk_libpng_dfactory(SkStream*);
860 SkImageEncoder* sk_libpng_efactory(SkImageEncoder::Type);
861#endif
862
863SkImageDecoder* sk_libpng_dfactory(SkStream* stream) {
reed@android.com00bf85a2009-01-22 13:04:56 +0000864 char buf[PNG_BYTES_TO_CHECK];
865 if (stream->read(buf, PNG_BYTES_TO_CHECK) == PNG_BYTES_TO_CHECK &&
866 !png_sig_cmp((png_bytep) buf, (png_size_t)0, PNG_BYTES_TO_CHECK)) {
867 return SkNEW(SkPNGImageDecoder);
868 }
869 return NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000870}
871
reed@android.comdfee5792010-04-15 14:24:50 +0000872SkImageEncoder* sk_libpng_efactory(SkImageEncoder::Type t) {
reed@android.com00bf85a2009-01-22 13:04:56 +0000873 return (SkImageEncoder::kPNG_Type == t) ? SkNEW(SkPNGImageEncoder) : NULL;
874}
875
reed@android.comdfee5792010-04-15 14:24:50 +0000876static SkTRegistry<SkImageEncoder*, SkImageEncoder::Type> gEReg(sk_libpng_efactory);
877static SkTRegistry<SkImageDecoder*, SkStream*> gDReg(sk_libpng_dfactory);