blob: 897126324d7d78035639ef18c858fc07f428f7da [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() {
reed@google.com7af00462010-12-31 18:11:59 +000053 png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +000054 }
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) {
reed@google.com29f66362011-02-25 15:07:30 +000061 SkStream* sk_stream = (SkStream*)png_get_io_ptr(png_ptr);
reed@android.com8a1c16f2008-12-17 15:59:43 +000062 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;
reed@google.com7af00462010-12-31 18:11:59 +000086 png_read_rows(png_ptr, &tmp, NULL, 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +000087 }
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) {
reed@google.com7af00462010-12-31 18:11:59 +0000157 png_destroy_read_struct(&png_ptr, NULL, NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000158 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,
reed@google.com7af00462010-12-31 18:11:59 +0000191 &interlace_type, NULL, NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000192
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) {
reed@google.com29f66362011-02-25 15:07:30 +0000204 png_set_expand_gray_1_2_4_to_8(png_ptr);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000205 }
reed@google.com29f66362011-02-25 15:07:30 +0000206
reed@android.com8a1c16f2008-12-17 15:59:43 +0000207 /* 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) {
reed@google.com29f66362011-02-25 15:07:30 +0000221 png_color_8p sig_bit = NULL;
222 bool has_sbit = PNG_INFO_sBIT == png_get_sBIT(png_ptr, info_ptr,
223 &sig_bit);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000224#if 0
reed@google.com29f66362011-02-25 15:07:30 +0000225 if (has_sbit) {
226 SkDebugf("----- sBIT %d %d %d %d\n", sig_bit->red, sig_bit->green,
227 sig_bit->blue, sig_bit->alpha);
228 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000229#endif
230 // 0 seems to indicate no information available
reed@google.com29f66362011-02-25 15:07:30 +0000231 if (has_sbit && pos_le(sig_bit->red, SK_R16_BITS) &&
232 pos_le(sig_bit->green, SK_G16_BITS) &&
233 pos_le(sig_bit->blue, SK_B16_BITS)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000234 doDither = false;
235 }
236 }
237
238 if (color_type == PNG_COLOR_TYPE_PALETTE) {
reed@android.comb6137c32009-07-29 20:56:52 +0000239 bool paletteHasAlpha = hasTransparencyInPalette(png_ptr, info_ptr);
reed@android.com3f1f06a2010-03-03 21:04:12 +0000240 config = this->getPrefConfig(kIndex_SrcDepth, paletteHasAlpha);
241 // now see if we can upscale to their requested config
242 if (!canUpscalePaletteToConfig(config, paletteHasAlpha)) {
243 config = SkBitmap::kIndex8_Config;
reed@android.comb6137c32009-07-29 20:56:52 +0000244 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000245 } else {
246 png_color_16p transpColor = NULL;
247 int numTransp = 0;
248
249 png_get_tRNS(png_ptr, info_ptr, NULL, &numTransp, &transpColor);
250
251 bool valid = png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS);
252
253 if (valid && numTransp == 1 && transpColor != NULL) {
254 /* Compute our transparent color, which we'll match against later.
255 We don't really handle 16bit components properly here, since we
256 do our compare *after* the values have been knocked down to 8bit
257 which means we will find more matches than we should. The real
258 fix seems to be to see the actual 16bit components, do the
259 compare, and then knock it down to 8bits ourselves.
260 */
261 if (color_type & PNG_COLOR_MASK_COLOR) {
262 if (16 == bit_depth) {
263 theTranspColor = SkPackARGB32(0xFF, transpColor->red >> 8,
264 transpColor->green >> 8, transpColor->blue >> 8);
265 } else {
266 theTranspColor = SkPackARGB32(0xFF, transpColor->red,
267 transpColor->green, transpColor->blue);
268 }
269 } else { // gray
270 if (16 == bit_depth) {
271 theTranspColor = SkPackARGB32(0xFF, transpColor->gray >> 8,
272 transpColor->gray >> 8, transpColor->gray >> 8);
273 } else {
274 theTranspColor = SkPackARGB32(0xFF, transpColor->gray,
275 transpColor->gray, transpColor->gray);
276 }
277 }
278 }
279
280 if (valid ||
281 PNG_COLOR_TYPE_RGB_ALPHA == color_type ||
282 PNG_COLOR_TYPE_GRAY_ALPHA == color_type) {
283 hasAlpha = true;
reed@android.com3f1f06a2010-03-03 21:04:12 +0000284 }
285 config = this->getPrefConfig(k32Bit_SrcDepth, hasAlpha);
286 // now match the request against our capabilities
287 if (hasAlpha) {
288 if (config != SkBitmap::kARGB_4444_Config) {
289 config = SkBitmap::kARGB_8888_Config;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000290 }
reed@android.com3f1f06a2010-03-03 21:04:12 +0000291 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000292 if (config != SkBitmap::kRGB_565_Config &&
reed@android.com3f1f06a2010-03-03 21:04:12 +0000293 config != SkBitmap::kARGB_4444_Config) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000294 config = SkBitmap::kARGB_8888_Config;
295 }
296 }
297 }
reed@android.coma8a8b8b2009-05-04 15:00:11 +0000298
299 // sanity check for size
300 {
301 Sk64 size;
302 size.setMul(origWidth, origHeight);
303 if (size.isNeg() || !size.is32()) {
304 return false;
305 }
306 // now check that if we are 4-bytes per pixel, we also don't overflow
307 if (size.get32() > (0x7FFFFFFF >> 2)) {
308 return false;
309 }
310 }
311
reed@android.com8a1c16f2008-12-17 15:59:43 +0000312 if (!this->chooseFromOneChoice(config, origWidth, origHeight)) {
313 return false;
314 }
315
316 const int sampleSize = this->getSampleSize();
317 SkScaledBitmapSampler sampler(origWidth, origHeight, sampleSize);
318
319 decodedBitmap->setConfig(config, sampler.scaledWidth(),
320 sampler.scaledHeight(), 0);
321 if (SkImageDecoder::kDecodeBounds_Mode == mode) {
322 return true;
323 }
324
325 // from here down we are concerned with colortables and pixels
326
327 // we track if we actually see a non-opaque pixels, since sometimes a PNG sets its colortype
328 // to |= PNG_COLOR_MASK_ALPHA, but all of its pixels are in fact opaque. We care, since we
329 // draw lots faster if we can flag the bitmap has being opaque
330 bool reallyHasAlpha = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000331 SkColorTable* colorTable = NULL;
332
333 if (color_type == PNG_COLOR_TYPE_PALETTE) {
334 int num_palette;
335 png_colorp palette;
336 png_bytep trans;
337 int num_trans;
338
339 png_get_PLTE(png_ptr, info_ptr, &palette, &num_palette);
340
341 /* BUGGY IMAGE WORKAROUND
342
343 We hit some images (e.g. fruit_.png) who contain bytes that are == colortable_count
344 which is a problem since we use the byte as an index. To work around this we grow
345 the colortable by 1 (if its < 256) and duplicate the last color into that slot.
346 */
347 int colorCount = num_palette + (num_palette < 256);
348
349 colorTable = SkNEW_ARGS(SkColorTable, (colorCount));
350
351 SkPMColor* colorPtr = colorTable->lockColors();
352 if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
353 png_get_tRNS(png_ptr, info_ptr, &trans, &num_trans, NULL);
354 hasAlpha = (num_trans > 0);
355 } else {
356 num_trans = 0;
357 colorTable->setFlags(colorTable->getFlags() | SkColorTable::kColorsAreOpaque_Flag);
358 }
359 // check for bad images that might make us crash
360 if (num_trans > num_palette) {
361 num_trans = num_palette;
362 }
363
364 int index = 0;
365 int transLessThanFF = 0;
366
367 for (; index < num_trans; index++) {
368 transLessThanFF |= (int)*trans - 0xFF;
369 *colorPtr++ = SkPreMultiplyARGB(*trans++, palette->red, palette->green, palette->blue);
370 palette++;
371 }
372 reallyHasAlpha |= (transLessThanFF < 0);
373
374 for (; index < num_palette; index++) {
375 *colorPtr++ = SkPackARGB32(0xFF, palette->red, palette->green, palette->blue);
376 palette++;
377 }
378
379 // see BUGGY IMAGE WORKAROUND comment above
380 if (num_palette < 256) {
381 *colorPtr = colorPtr[-1];
382 }
383 colorTable->unlockColors(true);
384 }
385
386 SkAutoUnref aur(colorTable);
387
reed@android.com11344262009-07-08 20:09:23 +0000388 if (!this->allocPixelRef(decodedBitmap,
reed@android.comb6137c32009-07-29 20:56:52 +0000389 SkBitmap::kIndex8_Config == config ?
390 colorTable : NULL)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000391 return false;
392 }
393
394 SkAutoLockPixels alp(*decodedBitmap);
395
396 /* swap the RGBA or GA data to ARGB or AG (or BGRA to ABGR) */
397// if (color_type == PNG_COLOR_TYPE_RGB_ALPHA)
398// ; // png_set_swap_alpha(png_ptr);
399
400 /* swap bytes of 16 bit files to least significant byte first */
401 // png_set_swap(png_ptr);
402
403 /* Add filler (or alpha) byte (before/after each RGB triplet) */
404 if (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_GRAY) {
405 png_set_filler(png_ptr, 0xff, PNG_FILLER_AFTER);
406 }
407
408 /* Turn on interlace handling. REQUIRED if you are not using
409 * png_read_image(). To see how to handle interlacing passes,
410 * see the png_read_row() method below:
411 */
412 const int number_passes = interlace_type != PNG_INTERLACE_NONE ?
413 png_set_interlace_handling(png_ptr) : 1;
414
415 /* Optional call to gamma correct and add the background to the palette
416 * and update info structure. REQUIRED if you are expecting libpng to
417 * update the palette for you (ie you selected such a transform above).
418 */
419 png_read_update_info(png_ptr, info_ptr);
420
421 if (SkBitmap::kIndex8_Config == config && 1 == sampleSize) {
422 for (int i = 0; i < number_passes; i++) {
423 for (png_uint_32 y = 0; y < origHeight; y++) {
424 uint8_t* bmRow = decodedBitmap->getAddr8(0, y);
reed@google.com7af00462010-12-31 18:11:59 +0000425 png_read_rows(png_ptr, &bmRow, NULL, 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000426 }
427 }
428 } else {
429 SkScaledBitmapSampler::SrcConfig sc;
430 int srcBytesPerPixel = 4;
431
reed@android.com11344262009-07-08 20:09:23 +0000432 if (colorTable != NULL) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000433 sc = SkScaledBitmapSampler::kIndex;
434 srcBytesPerPixel = 1;
435 } else if (hasAlpha) {
436 sc = SkScaledBitmapSampler::kRGBA;
437 } else {
438 sc = SkScaledBitmapSampler::kRGBX;
439 }
reed@android.com11344262009-07-08 20:09:23 +0000440
441 /* We have to pass the colortable explicitly, since we may have one
442 even if our decodedBitmap doesn't, due to the request that we
443 upscale png's palette to a direct model
444 */
445 SkAutoLockColors ctLock(colorTable);
446 if (!sampler.begin(decodedBitmap, sc, doDither, ctLock.colors())) {
reed@android.com862e91b2009-04-28 15:27:07 +0000447 return false;
448 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000449 const int height = decodedBitmap->height();
450
reed@android.com862e91b2009-04-28 15:27:07 +0000451 if (number_passes > 1) {
452 SkAutoMalloc storage(origWidth * origHeight * srcBytesPerPixel);
453 uint8_t* base = (uint8_t*)storage.get();
454 size_t rb = origWidth * srcBytesPerPixel;
reed@android.coma8a8b8b2009-05-04 15:00:11 +0000455
reed@android.com862e91b2009-04-28 15:27:07 +0000456 for (int i = 0; i < number_passes; i++) {
457 uint8_t* row = base;
458 for (png_uint_32 y = 0; y < origHeight; y++) {
459 uint8_t* bmRow = row;
reed@google.com7af00462010-12-31 18:11:59 +0000460 png_read_rows(png_ptr, &bmRow, NULL, 1);
reed@android.com862e91b2009-04-28 15:27:07 +0000461 row += rb;
462 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000463 }
reed@android.com862e91b2009-04-28 15:27:07 +0000464 // now sample it
465 base += sampler.srcY0() * rb;
466 for (int y = 0; y < height; y++) {
467 reallyHasAlpha |= sampler.next(base);
468 base += sampler.srcDY() * rb;
469 }
470 } else {
471 SkAutoMalloc storage(origWidth * srcBytesPerPixel);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000472 uint8_t* srcRow = (uint8_t*)storage.get();
473 skip_src_rows(png_ptr, srcRow, sampler.srcY0());
474
475 for (int y = 0; y < height; y++) {
476 uint8_t* tmp = srcRow;
reed@google.com7af00462010-12-31 18:11:59 +0000477 png_read_rows(png_ptr, &tmp, NULL, 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000478 reallyHasAlpha |= sampler.next(srcRow);
479 if (y < height - 1) {
480 skip_src_rows(png_ptr, srcRow, sampler.srcDY() - 1);
481 }
482 }
reed@android.com862e91b2009-04-28 15:27:07 +0000483
reed@android.com8a1c16f2008-12-17 15:59:43 +0000484 // skip the rest of the rows (if any)
485 png_uint_32 read = (height - 1) * sampler.srcDY() +
486 sampler.srcY0() + 1;
487 SkASSERT(read <= origHeight);
488 skip_src_rows(png_ptr, srcRow, origHeight - read);
489 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000490 }
491
492 /* read rest of file, and get additional chunks in info_ptr - REQUIRED */
493 png_read_end(png_ptr, info_ptr);
494
495 if (0 != theTranspColor) {
496 reallyHasAlpha |= substituteTranspColor(decodedBitmap, theTranspColor);
497 }
498 decodedBitmap->setIsOpaque(!reallyHasAlpha);
499 return true;
500}
501
502///////////////////////////////////////////////////////////////////////////////
503
reed@android.com8a1c16f2008-12-17 15:59:43 +0000504#include "SkColorPriv.h"
505#include "SkUnPreMultiply.h"
506
507static void sk_write_fn(png_structp png_ptr, png_bytep data, png_size_t len) {
reed@google.com29f66362011-02-25 15:07:30 +0000508 SkWStream* sk_stream = (SkWStream*)png_get_io_ptr(png_ptr);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000509 if (!sk_stream->write(data, len)) {
510 png_error(png_ptr, "sk_write_fn Error!");
511 }
512}
513
514typedef void (*transform_scanline_proc)(const char* SK_RESTRICT src,
515 int width, char* SK_RESTRICT dst);
516
517static void transform_scanline_565(const char* SK_RESTRICT src, int width,
518 char* SK_RESTRICT dst) {
519 const uint16_t* SK_RESTRICT srcP = (const uint16_t*)src;
520 for (int i = 0; i < width; i++) {
521 unsigned c = *srcP++;
522 *dst++ = SkPacked16ToR32(c);
523 *dst++ = SkPacked16ToG32(c);
524 *dst++ = SkPacked16ToB32(c);
525 }
526}
527
528static void transform_scanline_888(const char* SK_RESTRICT src, int width,
529 char* SK_RESTRICT dst) {
530 const SkPMColor* SK_RESTRICT srcP = (const SkPMColor*)src;
531 for (int i = 0; i < width; i++) {
532 SkPMColor c = *srcP++;
533 *dst++ = SkGetPackedR32(c);
534 *dst++ = SkGetPackedG32(c);
535 *dst++ = SkGetPackedB32(c);
536 }
537}
538
539static void transform_scanline_444(const char* SK_RESTRICT src, int width,
540 char* SK_RESTRICT dst) {
541 const SkPMColor16* SK_RESTRICT srcP = (const SkPMColor16*)src;
542 for (int i = 0; i < width; i++) {
543 SkPMColor16 c = *srcP++;
544 *dst++ = SkPacked4444ToR32(c);
545 *dst++ = SkPacked4444ToG32(c);
546 *dst++ = SkPacked4444ToB32(c);
547 }
548}
549
550static void transform_scanline_8888(const char* SK_RESTRICT src, int width,
551 char* SK_RESTRICT dst) {
552 const SkPMColor* SK_RESTRICT srcP = (const SkPMColor*)src;
553 const SkUnPreMultiply::Scale* SK_RESTRICT table =
554 SkUnPreMultiply::GetScaleTable();
555
556 for (int i = 0; i < width; i++) {
557 SkPMColor c = *srcP++;
558 unsigned a = SkGetPackedA32(c);
559 unsigned r = SkGetPackedR32(c);
560 unsigned g = SkGetPackedG32(c);
561 unsigned b = SkGetPackedB32(c);
562
563 if (0 != a && 255 != a) {
564 SkUnPreMultiply::Scale scale = table[a];
565 r = SkUnPreMultiply::ApplyScale(scale, r);
566 g = SkUnPreMultiply::ApplyScale(scale, g);
567 b = SkUnPreMultiply::ApplyScale(scale, b);
568 }
569 *dst++ = r;
570 *dst++ = g;
571 *dst++ = b;
572 *dst++ = a;
573 }
574}
575
576static void transform_scanline_4444(const char* SK_RESTRICT src, int width,
577 char* SK_RESTRICT dst) {
578 const SkPMColor16* SK_RESTRICT srcP = (const SkPMColor16*)src;
579 const SkUnPreMultiply::Scale* SK_RESTRICT table =
580 SkUnPreMultiply::GetScaleTable();
581
582 for (int i = 0; i < width; i++) {
583 SkPMColor16 c = *srcP++;
584 unsigned a = SkPacked4444ToA32(c);
585 unsigned r = SkPacked4444ToR32(c);
586 unsigned g = SkPacked4444ToG32(c);
587 unsigned b = SkPacked4444ToB32(c);
588
589 if (0 != a && 255 != a) {
590 SkUnPreMultiply::Scale scale = table[a];
591 r = SkUnPreMultiply::ApplyScale(scale, r);
592 g = SkUnPreMultiply::ApplyScale(scale, g);
593 b = SkUnPreMultiply::ApplyScale(scale, b);
594 }
595 *dst++ = r;
596 *dst++ = g;
597 *dst++ = b;
598 *dst++ = a;
599 }
600}
601
602static void transform_scanline_index8(const char* SK_RESTRICT src, int width,
603 char* SK_RESTRICT dst) {
604 memcpy(dst, src, width);
605}
606
607static transform_scanline_proc choose_proc(SkBitmap::Config config,
608 bool hasAlpha) {
609 // we don't care about search on alpha if we're kIndex8, since only the
610 // colortable packing cares about that distinction, not the pixels
611 if (SkBitmap::kIndex8_Config == config) {
612 hasAlpha = false; // we store false in the table entries for kIndex8
613 }
614
615 static const struct {
616 SkBitmap::Config fConfig;
617 bool fHasAlpha;
618 transform_scanline_proc fProc;
619 } gMap[] = {
620 { SkBitmap::kRGB_565_Config, false, transform_scanline_565 },
621 { SkBitmap::kARGB_8888_Config, false, transform_scanline_888 },
622 { SkBitmap::kARGB_8888_Config, true, transform_scanline_8888 },
623 { SkBitmap::kARGB_4444_Config, false, transform_scanline_444 },
624 { SkBitmap::kARGB_4444_Config, true, transform_scanline_4444 },
625 { SkBitmap::kIndex8_Config, false, transform_scanline_index8 },
626 };
627
628 for (int i = SK_ARRAY_COUNT(gMap) - 1; i >= 0; --i) {
629 if (gMap[i].fConfig == config && gMap[i].fHasAlpha == hasAlpha) {
630 return gMap[i].fProc;
631 }
632 }
633 sk_throw();
634 return NULL;
635}
636
637// return the minimum legal bitdepth (by png standards) for this many colortable
638// entries. SkBitmap always stores in 8bits per pixel, but for colorcount <= 16,
639// we can use fewer bits per in png
640static int computeBitDepth(int colorCount) {
641#if 0
642 int bits = SkNextLog2(colorCount);
643 SkASSERT(bits >= 1 && bits <= 8);
644 // now we need bits itself to be a power of 2 (e.g. 1, 2, 4, 8)
645 return SkNextPow2(bits);
646#else
647 // for the moment, we don't know how to pack bitdepth < 8
648 return 8;
649#endif
650}
651
652/* Pack palette[] with the corresponding colors, and if hasAlpha is true, also
653 pack trans[] and return the number of trans[] entries written. If hasAlpha
654 is false, the return value will always be 0.
655
656 Note: this routine takes care of unpremultiplying the RGB values when we
657 have alpha in the colortable, since png doesn't support premul colors
658*/
reed@android.com6f252972009-01-14 16:46:16 +0000659static inline int pack_palette(SkColorTable* ctable,
reed@android.comb50a60c2009-01-14 17:51:08 +0000660 png_color* SK_RESTRICT palette,
661 png_byte* SK_RESTRICT trans, bool hasAlpha) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000662 SkAutoLockColors alc(ctable);
663 const SkPMColor* SK_RESTRICT colors = alc.colors();
664 const int ctCount = ctable->count();
665 int i, num_trans = 0;
666
667 if (hasAlpha) {
668 /* first see if we have some number of fully opaque at the end of the
669 ctable. PNG allows num_trans < num_palette, but all of the trans
670 entries must come first in the palette. If I was smarter, I'd
671 reorder the indices and ctable so that all non-opaque colors came
672 first in the palette. But, since that would slow down the encode,
673 I'm leaving the indices and ctable order as is, and just looking
674 at the tail of the ctable for opaqueness.
675 */
676 num_trans = ctCount;
677 for (i = ctCount - 1; i >= 0; --i) {
678 if (SkGetPackedA32(colors[i]) != 0xFF) {
679 break;
680 }
681 num_trans -= 1;
682 }
683
684 const SkUnPreMultiply::Scale* SK_RESTRICT table =
685 SkUnPreMultiply::GetScaleTable();
686
687 for (i = 0; i < num_trans; i++) {
688 const SkPMColor c = *colors++;
689 const unsigned a = SkGetPackedA32(c);
690 const SkUnPreMultiply::Scale s = table[a];
691 trans[i] = a;
692 palette[i].red = SkUnPreMultiply::ApplyScale(s, SkGetPackedR32(c));
693 palette[i].green = SkUnPreMultiply::ApplyScale(s,SkGetPackedG32(c));
694 palette[i].blue = SkUnPreMultiply::ApplyScale(s, SkGetPackedB32(c));
695 }
696 // now fall out of this if-block to use common code for the trailing
697 // opaque entries
698 }
699
700 // these (remaining) entries are opaque
701 for (i = num_trans; i < ctCount; i++) {
702 SkPMColor c = *colors++;
703 palette[i].red = SkGetPackedR32(c);
704 palette[i].green = SkGetPackedG32(c);
705 palette[i].blue = SkGetPackedB32(c);
706 }
707 return num_trans;
708}
709
710class SkPNGImageEncoder : public SkImageEncoder {
711protected:
712 virtual bool onEncode(SkWStream* stream, const SkBitmap& bm, int quality);
713};
714
715bool SkPNGImageEncoder::onEncode(SkWStream* stream, const SkBitmap& bitmap,
716 int /*quality*/) {
717 SkBitmap::Config config = bitmap.getConfig();
718
719 const bool hasAlpha = !bitmap.isOpaque();
720 int colorType = PNG_COLOR_MASK_COLOR;
721 int bitDepth = 8; // default for color
722 png_color_8 sig_bit;
723
724 switch (config) {
725 case SkBitmap::kIndex8_Config:
726 colorType |= PNG_COLOR_MASK_PALETTE;
727 // fall through to the ARGB_8888 case
728 case SkBitmap::kARGB_8888_Config:
729 sig_bit.red = 8;
730 sig_bit.green = 8;
731 sig_bit.blue = 8;
732 sig_bit.alpha = 8;
733 break;
734 case SkBitmap::kARGB_4444_Config:
735 sig_bit.red = 4;
736 sig_bit.green = 4;
737 sig_bit.blue = 4;
738 sig_bit.alpha = 4;
739 break;
740 case SkBitmap::kRGB_565_Config:
741 sig_bit.red = 5;
742 sig_bit.green = 6;
743 sig_bit.blue = 5;
744 sig_bit.alpha = 0;
745 break;
746 default:
747 return false;
748 }
749
750 if (hasAlpha) {
751 // don't specify alpha if we're a palette, even if our ctable has alpha
752 if (!(colorType & PNG_COLOR_MASK_PALETTE)) {
753 colorType |= PNG_COLOR_MASK_ALPHA;
754 }
755 } else {
756 sig_bit.alpha = 0;
757 }
758
759 SkAutoLockPixels alp(bitmap);
760 // readyToDraw checks for pixels (and colortable if that is required)
761 if (!bitmap.readyToDraw()) {
762 return false;
763 }
764
765 // we must do this after we have locked the pixels
766 SkColorTable* ctable = bitmap.getColorTable();
767 if (NULL != ctable) {
768 if (ctable->count() == 0) {
769 return false;
770 }
771 // check if we can store in fewer than 8 bits
772 bitDepth = computeBitDepth(ctable->count());
773 }
774
775 png_structp png_ptr;
776 png_infop info_ptr;
777
778 png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, sk_error_fn,
779 NULL);
780 if (NULL == png_ptr) {
781 return false;
782 }
783
784 info_ptr = png_create_info_struct(png_ptr);
785 if (NULL == info_ptr) {
reed@google.com7af00462010-12-31 18:11:59 +0000786 png_destroy_write_struct(&png_ptr, NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000787 return false;
788 }
789
790 /* Set error handling. REQUIRED if you aren't supplying your own
791 * error handling functions in the png_create_write_struct() call.
792 */
793 if (setjmp(png_jmpbuf(png_ptr))) {
794 png_destroy_write_struct(&png_ptr, &info_ptr);
795 return false;
796 }
797
reed@google.com7af00462010-12-31 18:11:59 +0000798 png_set_write_fn(png_ptr, (void*)stream, sk_write_fn, NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000799
800 /* Set the image information here. Width and height are up to 2^31,
801 * bit_depth is one of 1, 2, 4, 8, or 16, but valid values also depend on
802 * the color_type selected. color_type is one of PNG_COLOR_TYPE_GRAY,
803 * PNG_COLOR_TYPE_GRAY_ALPHA, PNG_COLOR_TYPE_PALETTE, PNG_COLOR_TYPE_RGB,
804 * or PNG_COLOR_TYPE_RGB_ALPHA. interlace is either PNG_INTERLACE_NONE or
805 * PNG_INTERLACE_ADAM7, and the compression_type and filter_type MUST
806 * currently be PNG_COMPRESSION_TYPE_BASE and PNG_FILTER_TYPE_BASE. REQUIRED
807 */
808
809 png_set_IHDR(png_ptr, info_ptr, bitmap.width(), bitmap.height(),
810 bitDepth, colorType,
811 PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE,
812 PNG_FILTER_TYPE_BASE);
813
reed@android.com61898772009-07-07 19:38:01 +0000814 // set our colortable/trans arrays if needed
815 png_color paletteColors[256];
816 png_byte trans[256];
817 if (SkBitmap::kIndex8_Config == config) {
818 SkColorTable* ct = bitmap.getColorTable();
819 int numTrans = pack_palette(ct, paletteColors, trans, hasAlpha);
820 png_set_PLTE(png_ptr, info_ptr, paletteColors, ct->count());
821 if (numTrans > 0) {
822 png_set_tRNS(png_ptr, info_ptr, trans, numTrans, NULL);
823 }
824 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000825
826 png_set_sBIT(png_ptr, info_ptr, &sig_bit);
827 png_write_info(png_ptr, info_ptr);
828
829 const char* srcImage = (const char*)bitmap.getPixels();
830 SkAutoSMalloc<1024> rowStorage(bitmap.width() << 2);
831 char* storage = (char*)rowStorage.get();
832 transform_scanline_proc proc = choose_proc(config, hasAlpha);
833
834 for (int y = 0; y < bitmap.height(); y++) {
835 png_bytep row_ptr = (png_bytep)storage;
836 proc(srcImage, bitmap.width(), storage);
837 png_write_rows(png_ptr, &row_ptr, 1);
838 srcImage += bitmap.rowBytes();
839 }
840
841 png_write_end(png_ptr, info_ptr);
842
843 /* clean up after the write, and free any memory allocated */
844 png_destroy_write_struct(&png_ptr, &info_ptr);
845 return true;
846}
847
reed@android.com00bf85a2009-01-22 13:04:56 +0000848///////////////////////////////////////////////////////////////////////////////
849
850#include "SkTRegistry.h"
851
reed@android.comdfee5792010-04-15 14:24:50 +0000852#ifdef SK_ENABLE_LIBPNG
853 SkImageDecoder* sk_libpng_dfactory(SkStream*);
854 SkImageEncoder* sk_libpng_efactory(SkImageEncoder::Type);
855#endif
856
857SkImageDecoder* sk_libpng_dfactory(SkStream* stream) {
reed@android.com00bf85a2009-01-22 13:04:56 +0000858 char buf[PNG_BYTES_TO_CHECK];
859 if (stream->read(buf, PNG_BYTES_TO_CHECK) == PNG_BYTES_TO_CHECK &&
860 !png_sig_cmp((png_bytep) buf, (png_size_t)0, PNG_BYTES_TO_CHECK)) {
861 return SkNEW(SkPNGImageDecoder);
862 }
863 return NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000864}
865
reed@android.comdfee5792010-04-15 14:24:50 +0000866SkImageEncoder* sk_libpng_efactory(SkImageEncoder::Type t) {
reed@android.com00bf85a2009-01-22 13:04:56 +0000867 return (SkImageEncoder::kPNG_Type == t) ? SkNEW(SkPNGImageEncoder) : NULL;
868}
869
reed@android.comdfee5792010-04-15 14:24:50 +0000870static SkTRegistry<SkImageEncoder*, SkImageEncoder::Type> gEReg(sk_libpng_efactory);
871static SkTRegistry<SkImageDecoder*, SkStream*> gDReg(sk_libpng_dfactory);