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