blob: b30cd2509a6b6aa0a856b1ea2e67f0e106e108fc [file] [log] [blame]
epoger@google.comec3ed6a2011-07-28 14:26:00 +00001/*
2 * Copyright 2006 The Android Open Source Project
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
reed@android.comb08eb2b2009-01-06 20:16:26 +00008#include "SkImageEncoder.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +00009#include "SkColor.h"
10#include "SkColorPriv.h"
11#include "SkDither.h"
12#include "SkMath.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000013#include "SkStream.h"
14#include "SkTemplates.h"
15#include "SkUtils.h"
epoger@google.com4ce738b2012-11-16 18:44:18 +000016#include "transform_scanline.h"
djsollenb2a6fe72015-04-03 12:35:27 -070017
reed@android.com8a1c16f2008-12-17 15:59:43 +000018#include "png.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000019
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +000020/* These were dropped in libpng >= 1.4 */
21#ifndef png_infopp_NULL
halcanary96fcdcc2015-08-27 07:41:13 -070022#define png_infopp_NULL nullptr
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +000023#endif
24
25#ifndef png_bytepp_NULL
halcanary96fcdcc2015-08-27 07:41:13 -070026#define png_bytepp_NULL nullptr
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +000027#endif
28
29#ifndef int_p_NULL
halcanary96fcdcc2015-08-27 07:41:13 -070030#define int_p_NULL nullptr
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +000031#endif
32
33#ifndef png_flush_ptr_NULL
halcanary96fcdcc2015-08-27 07:41:13 -070034#define png_flush_ptr_NULL nullptr
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +000035#endif
36
halcanary@google.com2a103182013-10-14 12:49:15 +000037#define DEFAULT_FOR_SUPPRESS_PNG_IMAGE_DECODER_WARNINGS true
halcanary4e44efe2016-08-04 10:47:16 -070038// Suppress most PNG warnings when calling image decode functions.
39static const bool c_suppressPNGImageDecoderWarnings{
40 DEFAULT_FOR_SUPPRESS_PNG_IMAGE_DECODER_WARNINGS};
halcanary@google.com2a103182013-10-14 12:49:15 +000041
msarette8597a42016-03-24 10:41:47 -070042///////////////////////////////////////////////////////////////////////////////
commit-bot@chromium.orga936e372013-03-14 14:42:18 +000043
msarette8597a42016-03-24 10:41:47 -070044#include "SkColorPriv.h"
45#include "SkUnPreMultiply.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000046
47static void sk_error_fn(png_structp png_ptr, png_const_charp msg) {
scroggo04a0d3f2015-12-10 08:54:36 -080048 if (!c_suppressPNGImageDecoderWarnings) {
49 SkDEBUGF(("------ png error %s\n", msg));
50 }
reed@android.com8a1c16f2008-12-17 15:59:43 +000051 longjmp(png_jmpbuf(png_ptr), 1);
52}
53
reed@android.com8a1c16f2008-12-17 15:59:43 +000054static void sk_write_fn(png_structp png_ptr, png_bytep data, png_size_t len) {
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +000055 SkWStream* sk_stream = (SkWStream*)png_get_io_ptr(png_ptr);
reed@android.com8a1c16f2008-12-17 15:59:43 +000056 if (!sk_stream->write(data, len)) {
57 png_error(png_ptr, "sk_write_fn Error!");
58 }
59}
60
msarettf17b71f2016-09-12 14:30:03 -070061static transform_scanline_proc choose_proc(SkColorType ct, SkAlphaType alphaType) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000062 static const struct {
reed0689d7b2014-06-14 05:30:20 -070063 SkColorType fColorType;
msarettf17b71f2016-09-12 14:30:03 -070064 SkAlphaType fAlphaType;
reed@android.com8a1c16f2008-12-17 15:59:43 +000065 transform_scanline_proc fProc;
66 } gMap[] = {
msarettf17b71f2016-09-12 14:30:03 -070067 { kRGB_565_SkColorType, kOpaque_SkAlphaType, transform_scanline_565 },
68 { kRGBA_8888_SkColorType, kOpaque_SkAlphaType, transform_scanline_RGBX },
69 { kBGRA_8888_SkColorType, kOpaque_SkAlphaType, transform_scanline_BGRX },
70 { kRGBA_8888_SkColorType, kPremul_SkAlphaType, transform_scanline_rgbA },
71 { kBGRA_8888_SkColorType, kPremul_SkAlphaType, transform_scanline_bgrA },
72 { kRGBA_8888_SkColorType, kUnpremul_SkAlphaType, transform_scanline_memcpy },
73 { kBGRA_8888_SkColorType, kUnpremul_SkAlphaType, transform_scanline_BGRA },
74 { kARGB_4444_SkColorType, kOpaque_SkAlphaType, transform_scanline_444 },
75 { kARGB_4444_SkColorType, kPremul_SkAlphaType, transform_scanline_4444 },
76 { kIndex_8_SkColorType, kOpaque_SkAlphaType, transform_scanline_memcpy },
77 { kIndex_8_SkColorType, kPremul_SkAlphaType, transform_scanline_memcpy },
78 { kIndex_8_SkColorType, kUnpremul_SkAlphaType, transform_scanline_memcpy },
79 { kGray_8_SkColorType, kOpaque_SkAlphaType, transform_scanline_memcpy },
reed@android.com8a1c16f2008-12-17 15:59:43 +000080 };
81
msarettf17b71f2016-09-12 14:30:03 -070082 for (auto entry : gMap) {
83 if (entry.fColorType == ct && entry.fAlphaType == alphaType) {
84 return entry.fProc;
reed@android.com8a1c16f2008-12-17 15:59:43 +000085 }
86 }
87 sk_throw();
halcanary96fcdcc2015-08-27 07:41:13 -070088 return nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +000089}
90
91// return the minimum legal bitdepth (by png standards) for this many colortable
92// entries. SkBitmap always stores in 8bits per pixel, but for colorcount <= 16,
93// we can use fewer bits per in png
94static int computeBitDepth(int colorCount) {
95#if 0
96 int bits = SkNextLog2(colorCount);
97 SkASSERT(bits >= 1 && bits <= 8);
98 // now we need bits itself to be a power of 2 (e.g. 1, 2, 4, 8)
99 return SkNextPow2(bits);
100#else
101 // for the moment, we don't know how to pack bitdepth < 8
102 return 8;
103#endif
104}
105
106/* Pack palette[] with the corresponding colors, and if hasAlpha is true, also
107 pack trans[] and return the number of trans[] entries written. If hasAlpha
108 is false, the return value will always be 0.
rmistry@google.comd6176b02012-08-23 18:14:13 +0000109
reed@android.com8a1c16f2008-12-17 15:59:43 +0000110 Note: this routine takes care of unpremultiplying the RGB values when we
111 have alpha in the colortable, since png doesn't support premul colors
112*/
reed@android.com6f252972009-01-14 16:46:16 +0000113static inline int pack_palette(SkColorTable* ctable,
reed@android.comb50a60c2009-01-14 17:51:08 +0000114 png_color* SK_RESTRICT palette,
msarettf17b71f2016-09-12 14:30:03 -0700115 png_byte* SK_RESTRICT trans, SkAlphaType alphaType) {
halcanary96fcdcc2015-08-27 07:41:13 -0700116 const SkPMColor* SK_RESTRICT colors = ctable ? ctable->readColors() : nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000117 const int ctCount = ctable->count();
118 int i, num_trans = 0;
119
msarettf17b71f2016-09-12 14:30:03 -0700120 if (kOpaque_SkAlphaType != alphaType) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000121 /* first see if we have some number of fully opaque at the end of the
122 ctable. PNG allows num_trans < num_palette, but all of the trans
123 entries must come first in the palette. If I was smarter, I'd
124 reorder the indices and ctable so that all non-opaque colors came
125 first in the palette. But, since that would slow down the encode,
126 I'm leaving the indices and ctable order as is, and just looking
127 at the tail of the ctable for opaqueness.
128 */
129 num_trans = ctCount;
130 for (i = ctCount - 1; i >= 0; --i) {
131 if (SkGetPackedA32(colors[i]) != 0xFF) {
132 break;
133 }
134 num_trans -= 1;
135 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000136
msarettf17b71f2016-09-12 14:30:03 -0700137 if (kPremul_SkAlphaType == alphaType) {
138 const SkUnPreMultiply::Scale* SK_RESTRICT table = SkUnPreMultiply::GetScaleTable();
139 for (i = 0; i < num_trans; i++) {
140 const SkPMColor c = *colors++;
141 const unsigned a = SkGetPackedA32(c);
142 const SkUnPreMultiply::Scale s = table[a];
143 trans[i] = a;
144 palette[i].red = SkUnPreMultiply::ApplyScale(s, SkGetPackedR32(c));
145 palette[i].green = SkUnPreMultiply::ApplyScale(s,SkGetPackedG32(c));
146 palette[i].blue = SkUnPreMultiply::ApplyScale(s, SkGetPackedB32(c));
147 }
148 } else {
149 for (i = 0; i < num_trans; i++) {
150 const SkPMColor c = *colors++;
151 trans[i] = SkGetPackedA32(c);
152 palette[i].red = SkGetPackedR32(c);
153 palette[i].green = SkGetPackedG32(c);
154 palette[i].blue = SkGetPackedB32(c);
155 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000156 }
msarettf17b71f2016-09-12 14:30:03 -0700157
reed@android.com8a1c16f2008-12-17 15:59:43 +0000158 // now fall out of this if-block to use common code for the trailing
159 // opaque entries
160 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000161
reed@android.com8a1c16f2008-12-17 15:59:43 +0000162 // these (remaining) entries are opaque
163 for (i = num_trans; i < ctCount; i++) {
164 SkPMColor c = *colors++;
165 palette[i].red = SkGetPackedR32(c);
166 palette[i].green = SkGetPackedG32(c);
167 palette[i].blue = SkGetPackedB32(c);
168 }
169 return num_trans;
170}
171
172class SkPNGImageEncoder : public SkImageEncoder {
173protected:
mtklein36352bf2015-03-25 18:17:31 -0700174 bool onEncode(SkWStream* stream, const SkBitmap& bm, int quality) override;
tomhudson@google.com5c210c72011-07-28 21:06:40 +0000175private:
176 bool doEncode(SkWStream* stream, const SkBitmap& bm,
msarettf17b71f2016-09-12 14:30:03 -0700177 SkAlphaType alphaType, int colorType,
reed0689d7b2014-06-14 05:30:20 -0700178 int bitDepth, SkColorType ct,
tomhudson@google.com5c210c72011-07-28 21:06:40 +0000179 png_color_8& sig_bit);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000180
181 typedef SkImageEncoder INHERITED;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000182};
183
halcanarydfd6c6e2015-12-07 14:07:31 -0800184bool SkPNGImageEncoder::onEncode(SkWStream* stream,
185 const SkBitmap& originalBitmap,
186 int /*quality*/) {
187 SkBitmap copy;
188 const SkBitmap* bitmap = &originalBitmap;
189 switch (originalBitmap.colorType()) {
190 case kIndex_8_SkColorType:
msarett9b09cd82016-08-29 14:47:49 -0700191 case kGray_8_SkColorType:
msarettf17b71f2016-09-12 14:30:03 -0700192 case kRGBA_8888_SkColorType:
193 case kBGRA_8888_SkColorType:
halcanarydfd6c6e2015-12-07 14:07:31 -0800194 case kARGB_4444_SkColorType:
195 case kRGB_565_SkColorType:
196 break;
197 default:
halcanarydfd6c6e2015-12-07 14:07:31 -0800198 // TODO(scroggo): support Alpha_8 as Grayscale(black)+Alpha
199 if (originalBitmap.copyTo(&copy, kN32_SkColorType)) {
200 bitmap = &copy;
201 }
202 }
203 SkColorType ct = bitmap->colorType();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000204
msarettf17b71f2016-09-12 14:30:03 -0700205 const SkAlphaType alphaType = bitmap->alphaType();
206 switch (alphaType) {
207 case kUnpremul_SkAlphaType:
208 if (kARGB_4444_SkColorType == ct) {
209 return false;
210 }
211
212 break;
213 case kOpaque_SkAlphaType:
214 case kPremul_SkAlphaType:
215 break;
216 default:
217 return false;
218 }
219
220 const bool isOpaque = (kOpaque_SkAlphaType == alphaType);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000221 int bitDepth = 8; // default for color
222 png_color_8 sig_bit;
msarett9b09cd82016-08-29 14:47:49 -0700223 sk_bzero(&sig_bit, sizeof(png_color_8));
reed@android.com8a1c16f2008-12-17 15:59:43 +0000224
msarett9b09cd82016-08-29 14:47:49 -0700225 int colorType;
reed0689d7b2014-06-14 05:30:20 -0700226 switch (ct) {
227 case kIndex_8_SkColorType:
msarett9b09cd82016-08-29 14:47:49 -0700228 sig_bit.red = 8;
229 sig_bit.green = 8;
230 sig_bit.blue = 8;
231 sig_bit.alpha = 8;
232 colorType = PNG_COLOR_TYPE_PALETTE;
233 break;
234 case kGray_8_SkColorType:
235 sig_bit.gray = 8;
236 colorType = PNG_COLOR_TYPE_GRAY;
msarettf17b71f2016-09-12 14:30:03 -0700237 SkASSERT(isOpaque);
msarett9b09cd82016-08-29 14:47:49 -0700238 break;
msarettf17b71f2016-09-12 14:30:03 -0700239 case kRGBA_8888_SkColorType:
240 case kBGRA_8888_SkColorType:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000241 sig_bit.red = 8;
242 sig_bit.green = 8;
243 sig_bit.blue = 8;
244 sig_bit.alpha = 8;
msarettf17b71f2016-09-12 14:30:03 -0700245 colorType = isOpaque ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000246 break;
reed0689d7b2014-06-14 05:30:20 -0700247 case kARGB_4444_SkColorType:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000248 sig_bit.red = 4;
249 sig_bit.green = 4;
250 sig_bit.blue = 4;
251 sig_bit.alpha = 4;
msarettf17b71f2016-09-12 14:30:03 -0700252 colorType = isOpaque ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000253 break;
reed0689d7b2014-06-14 05:30:20 -0700254 case kRGB_565_SkColorType:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000255 sig_bit.red = 5;
256 sig_bit.green = 6;
257 sig_bit.blue = 5;
msarett9b09cd82016-08-29 14:47:49 -0700258 colorType = PNG_COLOR_TYPE_RGB;
msarettf17b71f2016-09-12 14:30:03 -0700259 SkASSERT(isOpaque);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000260 break;
261 default:
262 return false;
263 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000264
halcanarydfd6c6e2015-12-07 14:07:31 -0800265 SkAutoLockPixels alp(*bitmap);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000266 // readyToDraw checks for pixels (and colortable if that is required)
halcanarydfd6c6e2015-12-07 14:07:31 -0800267 if (!bitmap->readyToDraw()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000268 return false;
269 }
270
271 // we must do this after we have locked the pixels
halcanarydfd6c6e2015-12-07 14:07:31 -0800272 SkColorTable* ctable = bitmap->getColorTable();
bsalomon49f085d2014-09-05 13:34:00 -0700273 if (ctable) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000274 if (ctable->count() == 0) {
275 return false;
276 }
277 // check if we can store in fewer than 8 bits
278 bitDepth = computeBitDepth(ctable->count());
279 }
280
msarettf17b71f2016-09-12 14:30:03 -0700281 return doEncode(stream, *bitmap, alphaType, colorType, bitDepth, ct, sig_bit);
tomhudson@google.com5c210c72011-07-28 21:06:40 +0000282}
283
284bool SkPNGImageEncoder::doEncode(SkWStream* stream, const SkBitmap& bitmap,
msarettf17b71f2016-09-12 14:30:03 -0700285 SkAlphaType alphaType, int colorType,
reed0689d7b2014-06-14 05:30:20 -0700286 int bitDepth, SkColorType ct,
tomhudson@google.com5c210c72011-07-28 21:06:40 +0000287 png_color_8& sig_bit) {
288
reed@android.com8a1c16f2008-12-17 15:59:43 +0000289 png_structp png_ptr;
290 png_infop info_ptr;
291
halcanary96fcdcc2015-08-27 07:41:13 -0700292 png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, sk_error_fn,
293 nullptr);
294 if (nullptr == png_ptr) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000295 return false;
296 }
297
298 info_ptr = png_create_info_struct(png_ptr);
halcanary96fcdcc2015-08-27 07:41:13 -0700299 if (nullptr == info_ptr) {
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000300 png_destroy_write_struct(&png_ptr, png_infopp_NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000301 return false;
302 }
303
304 /* Set error handling. REQUIRED if you aren't supplying your own
305 * error handling functions in the png_create_write_struct() call.
306 */
307 if (setjmp(png_jmpbuf(png_ptr))) {
308 png_destroy_write_struct(&png_ptr, &info_ptr);
309 return false;
310 }
311
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000312 png_set_write_fn(png_ptr, (void*)stream, sk_write_fn, png_flush_ptr_NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000313
314 /* Set the image information here. Width and height are up to 2^31,
315 * bit_depth is one of 1, 2, 4, 8, or 16, but valid values also depend on
316 * the color_type selected. color_type is one of PNG_COLOR_TYPE_GRAY,
317 * PNG_COLOR_TYPE_GRAY_ALPHA, PNG_COLOR_TYPE_PALETTE, PNG_COLOR_TYPE_RGB,
318 * or PNG_COLOR_TYPE_RGB_ALPHA. interlace is either PNG_INTERLACE_NONE or
319 * PNG_INTERLACE_ADAM7, and the compression_type and filter_type MUST
320 * currently be PNG_COMPRESSION_TYPE_BASE and PNG_FILTER_TYPE_BASE. REQUIRED
321 */
322
323 png_set_IHDR(png_ptr, info_ptr, bitmap.width(), bitmap.height(),
324 bitDepth, colorType,
325 PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE,
326 PNG_FILTER_TYPE_BASE);
327
reed@android.com61898772009-07-07 19:38:01 +0000328 // set our colortable/trans arrays if needed
329 png_color paletteColors[256];
330 png_byte trans[256];
reed0689d7b2014-06-14 05:30:20 -0700331 if (kIndex_8_SkColorType == ct) {
msarettf17b71f2016-09-12 14:30:03 -0700332 SkColorTable* colorTable = bitmap.getColorTable();
333 int numTrans = pack_palette(colorTable, paletteColors, trans, alphaType);
334 png_set_PLTE(png_ptr, info_ptr, paletteColors, colorTable->count());
reed@android.com61898772009-07-07 19:38:01 +0000335 if (numTrans > 0) {
halcanary96fcdcc2015-08-27 07:41:13 -0700336 png_set_tRNS(png_ptr, info_ptr, trans, numTrans, nullptr);
reed@android.com61898772009-07-07 19:38:01 +0000337 }
338 }
msarett9b09cd82016-08-29 14:47:49 -0700339
reed@android.com8a1c16f2008-12-17 15:59:43 +0000340 png_set_sBIT(png_ptr, info_ptr, &sig_bit);
341 png_write_info(png_ptr, info_ptr);
342
343 const char* srcImage = (const char*)bitmap.getPixels();
scroggo565901d2015-12-10 10:44:13 -0800344 SkAutoSTMalloc<1024, char> rowStorage(bitmap.width() << 2);
345 char* storage = rowStorage.get();
msarettf17b71f2016-09-12 14:30:03 -0700346 transform_scanline_proc proc = choose_proc(ct, alphaType);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000347
348 for (int y = 0; y < bitmap.height(); y++) {
349 png_bytep row_ptr = (png_bytep)storage;
msarettf17b71f2016-09-12 14:30:03 -0700350 proc(storage, srcImage, bitmap.width(), SkColorTypeBytesPerPixel(ct));
reed@android.com8a1c16f2008-12-17 15:59:43 +0000351 png_write_rows(png_ptr, &row_ptr, 1);
352 srcImage += bitmap.rowBytes();
353 }
354
355 png_write_end(png_ptr, info_ptr);
356
357 /* clean up after the write, and free any memory allocated */
358 png_destroy_write_struct(&png_ptr, &info_ptr);
359 return true;
360}
361
reed@android.com00bf85a2009-01-22 13:04:56 +0000362///////////////////////////////////////////////////////////////////////////////
robertphillips@google.comec51cb82012-03-23 18:13:47 +0000363DEFINE_ENCODER_CREATOR(PNGImageEncoder);
364///////////////////////////////////////////////////////////////////////////////
reed@android.com00bf85a2009-01-22 13:04:56 +0000365
reed@android.comdfee5792010-04-15 14:24:50 +0000366SkImageEncoder* sk_libpng_efactory(SkImageEncoder::Type t) {
halcanary96fcdcc2015-08-27 07:41:13 -0700367 return (SkImageEncoder::kPNG_Type == t) ? new SkPNGImageEncoder : nullptr;
reed@android.com00bf85a2009-01-22 13:04:56 +0000368}
369
mtklein@google.combd6343b2013-09-04 17:20:18 +0000370static SkImageEncoder_EncodeReg gEReg(sk_libpng_efactory);