blob: dd4602ad02ae4337fa1e13d87bd38a47a065ac93 [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
Hal Canarydb683012016-11-23 08:55:18 -07008#include "SkImageEncoderPriv.h"
Hal Canary1fcc4042016-11-30 17:07:59 -05009
10#ifdef SK_HAS_PNG_LIBRARY
11
reed@android.com8a1c16f2008-12-17 15:59:43 +000012#include "SkColor.h"
13#include "SkColorPriv.h"
14#include "SkDither.h"
Matt Sarett0e032be2017-03-15 17:50:08 -040015#include "SkICC.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000016#include "SkMath.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000017#include "SkStream.h"
18#include "SkTemplates.h"
Hal Canary1fcc4042016-11-30 17:07:59 -050019#include "SkUnPreMultiply.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000020#include "SkUtils.h"
epoger@google.com4ce738b2012-11-16 18:44:18 +000021#include "transform_scanline.h"
djsollenb2a6fe72015-04-03 12:35:27 -070022
reed@android.com8a1c16f2008-12-17 15:59:43 +000023#include "png.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000024
halcanary4e44efe2016-08-04 10:47:16 -070025// Suppress most PNG warnings when calling image decode functions.
Matt Sarett32bf4492017-01-02 09:56:02 -050026static const bool c_suppressPNGImageDecoderWarnings = true;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +000027
reed@android.com8a1c16f2008-12-17 15:59:43 +000028static void sk_error_fn(png_structp png_ptr, png_const_charp msg) {
scroggo04a0d3f2015-12-10 08:54:36 -080029 if (!c_suppressPNGImageDecoderWarnings) {
30 SkDEBUGF(("------ png error %s\n", msg));
31 }
reed@android.com8a1c16f2008-12-17 15:59:43 +000032 longjmp(png_jmpbuf(png_ptr), 1);
33}
34
reed@android.com8a1c16f2008-12-17 15:59:43 +000035static void sk_write_fn(png_structp png_ptr, png_bytep data, png_size_t len) {
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +000036 SkWStream* sk_stream = (SkWStream*)png_get_io_ptr(png_ptr);
reed@android.com8a1c16f2008-12-17 15:59:43 +000037 if (!sk_stream->write(data, len)) {
38 png_error(png_ptr, "sk_write_fn Error!");
39 }
40}
41
Matt Sarett687b6562017-03-21 10:06:45 -040042static void set_icc(png_structp png_ptr, png_infop info_ptr, const SkColorSpaceTransferFn& fn,
43 const SkMatrix44& toXYZD50) {
44 sk_sp<SkData> icc = SkICC::WriteToICC(fn, toXYZD50);
45#if PNG_LIBPNG_VER_MAJOR > 1 || (PNG_LIBPNG_VER_MAJOR == 1 && PNG_LIBPNG_VER_MINOR >= 5)
46 png_const_bytep iccPtr = icc->bytes();
47#else
48 png_charp iccPtr = (png_charp) icc->writable_data();
49#endif
50 png_set_iCCP(png_ptr, info_ptr, "Skia", 0, iccPtr, icc->size());
51}
52
Matt Sarett84014f02017-01-10 11:28:54 -050053static transform_scanline_proc choose_proc(const SkImageInfo& info) {
54 const bool isGammaEncoded = info.gammaCloseToSRGB();
55 switch (info.colorType()) {
56 case kRGBA_8888_SkColorType:
57 switch (info.alphaType()) {
58 case kOpaque_SkAlphaType:
59 return transform_scanline_RGBX;
60 case kUnpremul_SkAlphaType:
61 return transform_scanline_memcpy;
62 case kPremul_SkAlphaType:
63 return isGammaEncoded ? transform_scanline_srgbA :
64 transform_scanline_rgbA;
65 default:
66 SkASSERT(false);
67 return nullptr;
68 }
69 case kBGRA_8888_SkColorType:
70 switch (info.alphaType()) {
71 case kOpaque_SkAlphaType:
72 return transform_scanline_BGRX;
73 case kUnpremul_SkAlphaType:
74 return transform_scanline_BGRA;
75 case kPremul_SkAlphaType:
76 return isGammaEncoded ? transform_scanline_sbgrA :
77 transform_scanline_bgrA;
78 default:
79 SkASSERT(false);
80 return nullptr;
81 }
82 case kRGB_565_SkColorType:
83 return transform_scanline_565;
84 case kARGB_4444_SkColorType:
85 switch (info.alphaType()) {
86 case kOpaque_SkAlphaType:
87 return transform_scanline_444;
88 case kPremul_SkAlphaType:
89 // 4444 is assumed to be legacy premul.
90 return transform_scanline_4444;
91 default:
92 SkASSERT(false);
93 return nullptr;
94 }
95 case kIndex_8_SkColorType:
96 case kGray_8_SkColorType:
97 return transform_scanline_memcpy;
Matt Sarett1da27ef2017-01-19 17:14:07 -050098 case kRGBA_F16_SkColorType:
99 switch (info.alphaType()) {
100 case kOpaque_SkAlphaType:
101 case kUnpremul_SkAlphaType:
102 return transform_scanline_F16;
103 case kPremul_SkAlphaType:
104 return transform_scanline_F16_premul;
105 default:
106 SkASSERT(false);
107 return nullptr;
108 }
Matt Sarett84014f02017-01-10 11:28:54 -0500109 default:
110 SkASSERT(false);
111 return nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000112 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000113}
114
msarettfed03342016-09-13 07:08:15 -0700115/* Pack palette[] with the corresponding colors, and if the image has alpha, also
116 pack trans[] and return the number of alphas[] entries written. If the image is
117 opaque, the return value will always be 0.
reed@android.com8a1c16f2008-12-17 15:59:43 +0000118*/
msarettfed03342016-09-13 07:08:15 -0700119static inline int pack_palette(SkColorTable* ctable, png_color* SK_RESTRICT palette,
Matt Sarett84014f02017-01-10 11:28:54 -0500120 png_byte* SK_RESTRICT alphas, const SkImageInfo& info) {
121 const SkPMColor* colors = ctable->readColors();
msarettfed03342016-09-13 07:08:15 -0700122 const int count = ctable->count();
Matt Sarett84014f02017-01-10 11:28:54 -0500123 SkPMColor storage[256];
124 if (kPremul_SkAlphaType == info.alphaType()) {
125 // Unpremultiply the colors.
126 const SkImageInfo rgbaInfo = info.makeColorType(kRGBA_8888_SkColorType);
127 transform_scanline_proc proc = choose_proc(rgbaInfo);
Matt Sarett62bb2802017-01-23 12:28:02 -0500128 proc((char*) storage, (const char*) colors, ctable->count(), 4, nullptr);
Matt Sarett84014f02017-01-10 11:28:54 -0500129 colors = storage;
130 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000131
Matt Sarett84014f02017-01-10 11:28:54 -0500132 int numWithAlpha = 0;
133 if (kOpaque_SkAlphaType != info.alphaType()) {
msarettfed03342016-09-13 07:08:15 -0700134 // PNG requires that all non-opaque colors come first in the palette. Write these first.
135 for (int i = 0; i < count; i++) {
136 uint8_t alpha = SkGetPackedA32(colors[i]);
137 if (0xFF != alpha) {
138 alphas[numWithAlpha] = alpha;
Matt Sarett84014f02017-01-10 11:28:54 -0500139 palette[numWithAlpha].red = SkGetPackedR32(colors[i]);
140 palette[numWithAlpha].green = SkGetPackedG32(colors[i]);
141 palette[numWithAlpha].blue = SkGetPackedB32(colors[i]);
msarettfed03342016-09-13 07:08:15 -0700142 numWithAlpha++;
msarettf17b71f2016-09-12 14:30:03 -0700143 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000144 }
msarettf17b71f2016-09-12 14:30:03 -0700145
reed@android.com8a1c16f2008-12-17 15:59:43 +0000146 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000147
msarettfed03342016-09-13 07:08:15 -0700148 if (0 == numWithAlpha) {
149 // All of the entries are opaque.
150 for (int i = 0; i < count; i++) {
151 SkPMColor c = *colors++;
152 palette[i].red = SkGetPackedR32(c);
153 palette[i].green = SkGetPackedG32(c);
154 palette[i].blue = SkGetPackedB32(c);
155 }
156 } else {
157 // We have already written the non-opaque colors. Now just write the opaque colors.
158 int currIndex = numWithAlpha;
159 int i = 0;
160 while (currIndex != count) {
161 uint8_t alpha = SkGetPackedA32(colors[i]);
162 if (0xFF == alpha) {
163 palette[currIndex].red = SkGetPackedR32(colors[i]);
164 palette[currIndex].green = SkGetPackedG32(colors[i]);
165 palette[currIndex].blue = SkGetPackedB32(colors[i]);
166 currIndex++;
167 }
168
169 i++;
170 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000171 }
msarettfed03342016-09-13 07:08:15 -0700172
173 return numWithAlpha;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000174}
175
Hal Canary1fcc4042016-11-30 17:07:59 -0500176static bool do_encode(SkWStream*, const SkPixmap&, int, int, png_color_8&);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000177
Matt Sarett84014f02017-01-10 11:28:54 -0500178bool SkEncodeImageAsPNG(SkWStream* stream, const SkPixmap& src, const SkEncodeOptions& opts) {
179 SkASSERT(!src.colorSpace() || src.colorSpace()->gammaCloseToSRGB() ||
180 src.colorSpace()->gammaIsLinear());
181
182 SkPixmap pixmap = src;
Matt Sarett0e032be2017-03-15 17:50:08 -0400183 if (SkEncodeOptions::ColorBehavior::kLegacy == opts.fColorBehavior) {
Matt Sarett84014f02017-01-10 11:28:54 -0500184 pixmap.setColorSpace(nullptr);
185 } else {
186 if (!pixmap.colorSpace()) {
187 return false;
188 }
189 }
190
Hal Canary1fcc4042016-11-30 17:07:59 -0500191 if (!pixmap.addr() || pixmap.info().isEmpty()) {
192 return false;
193 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000194
Matt Sarett1da27ef2017-01-19 17:14:07 -0500195 const SkColorType colorType = pixmap.colorType();
Hal Canary1fcc4042016-11-30 17:07:59 -0500196 const SkAlphaType alphaType = pixmap.alphaType();
msarettf17b71f2016-09-12 14:30:03 -0700197 switch (alphaType) {
198 case kUnpremul_SkAlphaType:
Matt Sarett84014f02017-01-10 11:28:54 -0500199 if (kARGB_4444_SkColorType == colorType) {
msarettf17b71f2016-09-12 14:30:03 -0700200 return false;
201 }
202
203 break;
204 case kOpaque_SkAlphaType:
205 case kPremul_SkAlphaType:
206 break;
207 default:
208 return false;
209 }
210
211 const bool isOpaque = (kOpaque_SkAlphaType == alphaType);
Matt Sarett1da27ef2017-01-19 17:14:07 -0500212 int bitDepth = 8;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000213 png_color_8 sig_bit;
msarett9b09cd82016-08-29 14:47:49 -0700214 sk_bzero(&sig_bit, sizeof(png_color_8));
Matt Sarett84014f02017-01-10 11:28:54 -0500215 int pngColorType;
216 switch (colorType) {
Matt Sarett1da27ef2017-01-19 17:14:07 -0500217 case kRGBA_F16_SkColorType:
218 if (!pixmap.colorSpace() || !pixmap.colorSpace()->gammaIsLinear()) {
219 return false;
220 }
221
222 sig_bit.red = 16;
223 sig_bit.green = 16;
224 sig_bit.blue = 16;
225 sig_bit.alpha = 16;
226 bitDepth = 16;
227 pngColorType = isOpaque ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA;
228 break;
reed0689d7b2014-06-14 05:30:20 -0700229 case kIndex_8_SkColorType:
msarett9b09cd82016-08-29 14:47:49 -0700230 sig_bit.red = 8;
231 sig_bit.green = 8;
232 sig_bit.blue = 8;
233 sig_bit.alpha = 8;
Matt Sarett84014f02017-01-10 11:28:54 -0500234 pngColorType = PNG_COLOR_TYPE_PALETTE;
msarett9b09cd82016-08-29 14:47:49 -0700235 break;
236 case kGray_8_SkColorType:
237 sig_bit.gray = 8;
Matt Sarett84014f02017-01-10 11:28:54 -0500238 pngColorType = PNG_COLOR_TYPE_GRAY;
msarettf17b71f2016-09-12 14:30:03 -0700239 SkASSERT(isOpaque);
msarett9b09cd82016-08-29 14:47:49 -0700240 break;
msarettf17b71f2016-09-12 14:30:03 -0700241 case kRGBA_8888_SkColorType:
242 case kBGRA_8888_SkColorType:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000243 sig_bit.red = 8;
244 sig_bit.green = 8;
245 sig_bit.blue = 8;
246 sig_bit.alpha = 8;
Matt Sarett84014f02017-01-10 11:28:54 -0500247 pngColorType = isOpaque ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000248 break;
reed0689d7b2014-06-14 05:30:20 -0700249 case kARGB_4444_SkColorType:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000250 sig_bit.red = 4;
251 sig_bit.green = 4;
252 sig_bit.blue = 4;
253 sig_bit.alpha = 4;
Matt Sarett84014f02017-01-10 11:28:54 -0500254 pngColorType = isOpaque ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000255 break;
reed0689d7b2014-06-14 05:30:20 -0700256 case kRGB_565_SkColorType:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000257 sig_bit.red = 5;
258 sig_bit.green = 6;
259 sig_bit.blue = 5;
Matt Sarett84014f02017-01-10 11:28:54 -0500260 pngColorType = PNG_COLOR_TYPE_RGB;
msarettf17b71f2016-09-12 14:30:03 -0700261 SkASSERT(isOpaque);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000262 break;
263 default:
264 return false;
265 }
Matt Sarett1da27ef2017-01-19 17:14:07 -0500266
Matt Sarett84014f02017-01-10 11:28:54 -0500267 if (kIndex_8_SkColorType == colorType) {
Hal Canary1fcc4042016-11-30 17:07:59 -0500268 SkColorTable* ctable = pixmap.ctable();
269 if (!ctable || ctable->count() == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000270 return false;
271 }
Matt Sarett32bf4492017-01-02 09:56:02 -0500272
273 // Currently, we always use 8-bit indices for paletted pngs.
274 // When ctable->count() <= 16, we could potentially use 1, 2,
275 // or 4 bit indices.
reed@android.com8a1c16f2008-12-17 15:59:43 +0000276 }
Matt Sarett1da27ef2017-01-19 17:14:07 -0500277
Matt Sarett84014f02017-01-10 11:28:54 -0500278 return do_encode(stream, pixmap, pngColorType, bitDepth, sig_bit);
tomhudson@google.com5c210c72011-07-28 21:06:40 +0000279}
280
Matt Sarett1da27ef2017-01-19 17:14:07 -0500281static int num_components(int pngColorType) {
282 switch (pngColorType) {
283 case PNG_COLOR_TYPE_PALETTE:
284 case PNG_COLOR_TYPE_GRAY:
285 return 1;
286 case PNG_COLOR_TYPE_RGB:
287 return 3;
288 case PNG_COLOR_TYPE_RGBA:
289 return 4;
290 default:
291 SkASSERT(false);
292 return 0;
293 }
294}
295
Hal Canary1fcc4042016-11-30 17:07:59 -0500296static bool do_encode(SkWStream* stream, const SkPixmap& pixmap,
Matt Sarett84014f02017-01-10 11:28:54 -0500297 int pngColorType, int bitDepth, png_color_8& sig_bit) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000298 png_structp png_ptr;
299 png_infop info_ptr;
300
Hal Canary1fcc4042016-11-30 17:07:59 -0500301 png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, sk_error_fn, nullptr);
halcanary96fcdcc2015-08-27 07:41:13 -0700302 if (nullptr == png_ptr) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000303 return false;
304 }
305
306 info_ptr = png_create_info_struct(png_ptr);
halcanary96fcdcc2015-08-27 07:41:13 -0700307 if (nullptr == info_ptr) {
Matt Sarett32bf4492017-01-02 09:56:02 -0500308 png_destroy_write_struct(&png_ptr, nullptr);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000309 return false;
310 }
311
312 /* Set error handling. REQUIRED if you aren't supplying your own
313 * error handling functions in the png_create_write_struct() call.
314 */
315 if (setjmp(png_jmpbuf(png_ptr))) {
316 png_destroy_write_struct(&png_ptr, &info_ptr);
317 return false;
318 }
319
Matt Sarett32bf4492017-01-02 09:56:02 -0500320 png_set_write_fn(png_ptr, (void*)stream, sk_write_fn, nullptr);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000321
322 /* Set the image information here. Width and height are up to 2^31,
323 * bit_depth is one of 1, 2, 4, 8, or 16, but valid values also depend on
324 * the color_type selected. color_type is one of PNG_COLOR_TYPE_GRAY,
325 * PNG_COLOR_TYPE_GRAY_ALPHA, PNG_COLOR_TYPE_PALETTE, PNG_COLOR_TYPE_RGB,
326 * or PNG_COLOR_TYPE_RGB_ALPHA. interlace is either PNG_INTERLACE_NONE or
327 * PNG_INTERLACE_ADAM7, and the compression_type and filter_type MUST
328 * currently be PNG_COMPRESSION_TYPE_BASE and PNG_FILTER_TYPE_BASE. REQUIRED
329 */
330
Hal Canary1fcc4042016-11-30 17:07:59 -0500331 png_set_IHDR(png_ptr, info_ptr, pixmap.width(), pixmap.height(),
Matt Sarett84014f02017-01-10 11:28:54 -0500332 bitDepth, pngColorType,
reed@android.com8a1c16f2008-12-17 15:59:43 +0000333 PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE,
334 PNG_FILTER_TYPE_BASE);
335
reed@android.com61898772009-07-07 19:38:01 +0000336 // set our colortable/trans arrays if needed
337 png_color paletteColors[256];
338 png_byte trans[256];
Matt Sarett84014f02017-01-10 11:28:54 -0500339 if (kIndex_8_SkColorType == pixmap.colorType()) {
Hal Canary1fcc4042016-11-30 17:07:59 -0500340 SkColorTable* colorTable = pixmap.ctable();
msarettfed03342016-09-13 07:08:15 -0700341 SkASSERT(colorTable);
Matt Sarett84014f02017-01-10 11:28:54 -0500342 int numTrans = pack_palette(colorTable, paletteColors, trans, pixmap.info());
msarettf17b71f2016-09-12 14:30:03 -0700343 png_set_PLTE(png_ptr, info_ptr, paletteColors, colorTable->count());
reed@android.com61898772009-07-07 19:38:01 +0000344 if (numTrans > 0) {
halcanary96fcdcc2015-08-27 07:41:13 -0700345 png_set_tRNS(png_ptr, info_ptr, trans, numTrans, nullptr);
reed@android.com61898772009-07-07 19:38:01 +0000346 }
347 }
msarett9b09cd82016-08-29 14:47:49 -0700348
Matt Sarett0e032be2017-03-15 17:50:08 -0400349 if (pixmap.colorSpace()) {
350 SkColorSpaceTransferFn fn;
351 SkMatrix44 toXYZD50(SkMatrix44::kUninitialized_Constructor);
352 if (pixmap.colorSpace()->isSRGB()) {
353 png_set_sRGB(png_ptr, info_ptr, PNG_sRGB_INTENT_PERCEPTUAL);
354 } else if (pixmap.colorSpace()->isNumericalTransferFn(&fn) &&
355 pixmap.colorSpace()->toXYZD50(&toXYZD50))
356 {
Matt Sarett687b6562017-03-21 10:06:45 -0400357 set_icc(png_ptr, info_ptr, fn, toXYZD50);
Matt Sarett0e032be2017-03-15 17:50:08 -0400358 }
359
360 // TODO: Should we support writing ICC profiles for additional color spaces?
361 }
362
reed@android.com8a1c16f2008-12-17 15:59:43 +0000363 png_set_sBIT(png_ptr, info_ptr, &sig_bit);
364 png_write_info(png_ptr, info_ptr);
Matt Sarett1da27ef2017-01-19 17:14:07 -0500365 int pngBytesPerPixel = num_components(pngColorType) * (bitDepth / 8);
366 if (kRGBA_F16_SkColorType == pixmap.colorType() && kOpaque_SkAlphaType == pixmap.alphaType()) {
367 // For kOpaque, kRGBA_F16, we will keep the row as RGBA and tell libpng
368 // to skip the alpha channel.
369 png_set_filler(png_ptr, 0, PNG_FILLER_AFTER);
370 pngBytesPerPixel = 8;
371 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000372
Matt Sarett1da27ef2017-01-19 17:14:07 -0500373 SkAutoSTMalloc<1024, char> rowStorage(pixmap.width() * pngBytesPerPixel);
scroggo565901d2015-12-10 10:44:13 -0800374 char* storage = rowStorage.get();
Matt Sarett1da27ef2017-01-19 17:14:07 -0500375 const char* srcImage = (const char*)pixmap.addr();
Matt Sarett84014f02017-01-10 11:28:54 -0500376 transform_scanline_proc proc = choose_proc(pixmap.info());
Hal Canary1fcc4042016-11-30 17:07:59 -0500377 for (int y = 0; y < pixmap.height(); y++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000378 png_bytep row_ptr = (png_bytep)storage;
Matt Sarett62bb2802017-01-23 12:28:02 -0500379 proc(storage, srcImage, pixmap.width(), SkColorTypeBytesPerPixel(pixmap.colorType()),
380 nullptr);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000381 png_write_rows(png_ptr, &row_ptr, 1);
Hal Canary1fcc4042016-11-30 17:07:59 -0500382 srcImage += pixmap.rowBytes();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000383 }
384
385 png_write_end(png_ptr, info_ptr);
386
387 /* clean up after the write, and free any memory allocated */
388 png_destroy_write_struct(&png_ptr, &info_ptr);
389 return true;
390}
391
Hal Canary1fcc4042016-11-30 17:07:59 -0500392#endif