blob: 48aca8bb1e620e15f8754ffbf631669232684fda [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"
15#include "SkMath.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000016#include "SkStream.h"
17#include "SkTemplates.h"
Hal Canary1fcc4042016-11-30 17:07:59 -050018#include "SkUnPreMultiply.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000019#include "SkUtils.h"
epoger@google.com4ce738b2012-11-16 18:44:18 +000020#include "transform_scanline.h"
djsollenb2a6fe72015-04-03 12:35:27 -070021
reed@android.com8a1c16f2008-12-17 15:59:43 +000022#include "png.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000023
halcanary4e44efe2016-08-04 10:47:16 -070024// Suppress most PNG warnings when calling image decode functions.
Matt Sarett32bf4492017-01-02 09:56:02 -050025static const bool c_suppressPNGImageDecoderWarnings = true;
commit-bot@chromium.orga936e372013-03-14 14:42:18 +000026
reed@android.com8a1c16f2008-12-17 15:59:43 +000027static void sk_error_fn(png_structp png_ptr, png_const_charp msg) {
scroggo04a0d3f2015-12-10 08:54:36 -080028 if (!c_suppressPNGImageDecoderWarnings) {
29 SkDEBUGF(("------ png error %s\n", msg));
30 }
reed@android.com8a1c16f2008-12-17 15:59:43 +000031 longjmp(png_jmpbuf(png_ptr), 1);
32}
33
reed@android.com8a1c16f2008-12-17 15:59:43 +000034static void sk_write_fn(png_structp png_ptr, png_bytep data, png_size_t len) {
commit-bot@chromium.orgc1c56952013-05-02 13:41:34 +000035 SkWStream* sk_stream = (SkWStream*)png_get_io_ptr(png_ptr);
reed@android.com8a1c16f2008-12-17 15:59:43 +000036 if (!sk_stream->write(data, len)) {
37 png_error(png_ptr, "sk_write_fn Error!");
38 }
39}
40
Matt Sarett84014f02017-01-10 11:28:54 -050041static transform_scanline_proc choose_proc(const SkImageInfo& info) {
42 const bool isGammaEncoded = info.gammaCloseToSRGB();
43 switch (info.colorType()) {
44 case kRGBA_8888_SkColorType:
45 switch (info.alphaType()) {
46 case kOpaque_SkAlphaType:
47 return transform_scanline_RGBX;
48 case kUnpremul_SkAlphaType:
49 return transform_scanline_memcpy;
50 case kPremul_SkAlphaType:
51 return isGammaEncoded ? transform_scanline_srgbA :
52 transform_scanline_rgbA;
53 default:
54 SkASSERT(false);
55 return nullptr;
56 }
57 case kBGRA_8888_SkColorType:
58 switch (info.alphaType()) {
59 case kOpaque_SkAlphaType:
60 return transform_scanline_BGRX;
61 case kUnpremul_SkAlphaType:
62 return transform_scanline_BGRA;
63 case kPremul_SkAlphaType:
64 return isGammaEncoded ? transform_scanline_sbgrA :
65 transform_scanline_bgrA;
66 default:
67 SkASSERT(false);
68 return nullptr;
69 }
70 case kRGB_565_SkColorType:
71 return transform_scanline_565;
72 case kARGB_4444_SkColorType:
73 switch (info.alphaType()) {
74 case kOpaque_SkAlphaType:
75 return transform_scanline_444;
76 case kPremul_SkAlphaType:
77 // 4444 is assumed to be legacy premul.
78 return transform_scanline_4444;
79 default:
80 SkASSERT(false);
81 return nullptr;
82 }
83 case kIndex_8_SkColorType:
84 case kGray_8_SkColorType:
85 return transform_scanline_memcpy;
Matt Sarett1da27ef2017-01-19 17:14:07 -050086 case kRGBA_F16_SkColorType:
87 switch (info.alphaType()) {
88 case kOpaque_SkAlphaType:
89 case kUnpremul_SkAlphaType:
90 return transform_scanline_F16;
91 case kPremul_SkAlphaType:
92 return transform_scanline_F16_premul;
93 default:
94 SkASSERT(false);
95 return nullptr;
96 }
Matt Sarett84014f02017-01-10 11:28:54 -050097 default:
98 SkASSERT(false);
99 return nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000100 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000101}
102
msarettfed03342016-09-13 07:08:15 -0700103/* Pack palette[] with the corresponding colors, and if the image has alpha, also
104 pack trans[] and return the number of alphas[] entries written. If the image is
105 opaque, the return value will always be 0.
reed@android.com8a1c16f2008-12-17 15:59:43 +0000106*/
msarettfed03342016-09-13 07:08:15 -0700107static inline int pack_palette(SkColorTable* ctable, png_color* SK_RESTRICT palette,
Matt Sarett84014f02017-01-10 11:28:54 -0500108 png_byte* SK_RESTRICT alphas, const SkImageInfo& info) {
109 const SkPMColor* colors = ctable->readColors();
msarettfed03342016-09-13 07:08:15 -0700110 const int count = ctable->count();
Matt Sarett84014f02017-01-10 11:28:54 -0500111 SkPMColor storage[256];
112 if (kPremul_SkAlphaType == info.alphaType()) {
113 // Unpremultiply the colors.
114 const SkImageInfo rgbaInfo = info.makeColorType(kRGBA_8888_SkColorType);
115 transform_scanline_proc proc = choose_proc(rgbaInfo);
116 proc((char*) storage, (const char*) colors, ctable->count(), 4);
117 colors = storage;
118 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000119
Matt Sarett84014f02017-01-10 11:28:54 -0500120 int numWithAlpha = 0;
121 if (kOpaque_SkAlphaType != info.alphaType()) {
msarettfed03342016-09-13 07:08:15 -0700122 // PNG requires that all non-opaque colors come first in the palette. Write these first.
123 for (int i = 0; i < count; i++) {
124 uint8_t alpha = SkGetPackedA32(colors[i]);
125 if (0xFF != alpha) {
126 alphas[numWithAlpha] = alpha;
Matt Sarett84014f02017-01-10 11:28:54 -0500127 palette[numWithAlpha].red = SkGetPackedR32(colors[i]);
128 palette[numWithAlpha].green = SkGetPackedG32(colors[i]);
129 palette[numWithAlpha].blue = SkGetPackedB32(colors[i]);
msarettfed03342016-09-13 07:08:15 -0700130 numWithAlpha++;
msarettf17b71f2016-09-12 14:30:03 -0700131 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000132 }
msarettf17b71f2016-09-12 14:30:03 -0700133
reed@android.com8a1c16f2008-12-17 15:59:43 +0000134 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000135
msarettfed03342016-09-13 07:08:15 -0700136 if (0 == numWithAlpha) {
137 // All of the entries are opaque.
138 for (int i = 0; i < count; i++) {
139 SkPMColor c = *colors++;
140 palette[i].red = SkGetPackedR32(c);
141 palette[i].green = SkGetPackedG32(c);
142 palette[i].blue = SkGetPackedB32(c);
143 }
144 } else {
145 // We have already written the non-opaque colors. Now just write the opaque colors.
146 int currIndex = numWithAlpha;
147 int i = 0;
148 while (currIndex != count) {
149 uint8_t alpha = SkGetPackedA32(colors[i]);
150 if (0xFF == alpha) {
151 palette[currIndex].red = SkGetPackedR32(colors[i]);
152 palette[currIndex].green = SkGetPackedG32(colors[i]);
153 palette[currIndex].blue = SkGetPackedB32(colors[i]);
154 currIndex++;
155 }
156
157 i++;
158 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000159 }
msarettfed03342016-09-13 07:08:15 -0700160
161 return numWithAlpha;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000162}
163
Hal Canary1fcc4042016-11-30 17:07:59 -0500164static bool do_encode(SkWStream*, const SkPixmap&, int, int, png_color_8&);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000165
Matt Sarett84014f02017-01-10 11:28:54 -0500166bool SkEncodeImageAsPNG(SkWStream* stream, const SkPixmap& src, const SkEncodeOptions& opts) {
167 SkASSERT(!src.colorSpace() || src.colorSpace()->gammaCloseToSRGB() ||
168 src.colorSpace()->gammaIsLinear());
169
170 SkPixmap pixmap = src;
171 if (SkEncodeOptions::PremulBehavior::kLegacy == opts.fPremulBehavior) {
172 pixmap.setColorSpace(nullptr);
173 } else {
174 if (!pixmap.colorSpace()) {
175 return false;
176 }
177 }
178
Hal Canary1fcc4042016-11-30 17:07:59 -0500179 if (!pixmap.addr() || pixmap.info().isEmpty()) {
180 return false;
181 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000182
Matt Sarett1da27ef2017-01-19 17:14:07 -0500183 const SkColorType colorType = pixmap.colorType();
Hal Canary1fcc4042016-11-30 17:07:59 -0500184 const SkAlphaType alphaType = pixmap.alphaType();
msarettf17b71f2016-09-12 14:30:03 -0700185 switch (alphaType) {
186 case kUnpremul_SkAlphaType:
Matt Sarett84014f02017-01-10 11:28:54 -0500187 if (kARGB_4444_SkColorType == colorType) {
msarettf17b71f2016-09-12 14:30:03 -0700188 return false;
189 }
190
191 break;
192 case kOpaque_SkAlphaType:
193 case kPremul_SkAlphaType:
194 break;
195 default:
196 return false;
197 }
198
199 const bool isOpaque = (kOpaque_SkAlphaType == alphaType);
Matt Sarett1da27ef2017-01-19 17:14:07 -0500200 int bitDepth = 8;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000201 png_color_8 sig_bit;
msarett9b09cd82016-08-29 14:47:49 -0700202 sk_bzero(&sig_bit, sizeof(png_color_8));
Matt Sarett84014f02017-01-10 11:28:54 -0500203 int pngColorType;
204 switch (colorType) {
Matt Sarett1da27ef2017-01-19 17:14:07 -0500205 case kRGBA_F16_SkColorType:
206 if (!pixmap.colorSpace() || !pixmap.colorSpace()->gammaIsLinear()) {
207 return false;
208 }
209
210 sig_bit.red = 16;
211 sig_bit.green = 16;
212 sig_bit.blue = 16;
213 sig_bit.alpha = 16;
214 bitDepth = 16;
215 pngColorType = isOpaque ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA;
216 break;
reed0689d7b2014-06-14 05:30:20 -0700217 case kIndex_8_SkColorType:
msarett9b09cd82016-08-29 14:47:49 -0700218 sig_bit.red = 8;
219 sig_bit.green = 8;
220 sig_bit.blue = 8;
221 sig_bit.alpha = 8;
Matt Sarett84014f02017-01-10 11:28:54 -0500222 pngColorType = PNG_COLOR_TYPE_PALETTE;
msarett9b09cd82016-08-29 14:47:49 -0700223 break;
224 case kGray_8_SkColorType:
225 sig_bit.gray = 8;
Matt Sarett84014f02017-01-10 11:28:54 -0500226 pngColorType = PNG_COLOR_TYPE_GRAY;
msarettf17b71f2016-09-12 14:30:03 -0700227 SkASSERT(isOpaque);
msarett9b09cd82016-08-29 14:47:49 -0700228 break;
msarettf17b71f2016-09-12 14:30:03 -0700229 case kRGBA_8888_SkColorType:
230 case kBGRA_8888_SkColorType:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000231 sig_bit.red = 8;
232 sig_bit.green = 8;
233 sig_bit.blue = 8;
234 sig_bit.alpha = 8;
Matt Sarett84014f02017-01-10 11:28:54 -0500235 pngColorType = isOpaque ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000236 break;
reed0689d7b2014-06-14 05:30:20 -0700237 case kARGB_4444_SkColorType:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000238 sig_bit.red = 4;
239 sig_bit.green = 4;
240 sig_bit.blue = 4;
241 sig_bit.alpha = 4;
Matt Sarett84014f02017-01-10 11:28:54 -0500242 pngColorType = isOpaque ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000243 break;
reed0689d7b2014-06-14 05:30:20 -0700244 case kRGB_565_SkColorType:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000245 sig_bit.red = 5;
246 sig_bit.green = 6;
247 sig_bit.blue = 5;
Matt Sarett84014f02017-01-10 11:28:54 -0500248 pngColorType = PNG_COLOR_TYPE_RGB;
msarettf17b71f2016-09-12 14:30:03 -0700249 SkASSERT(isOpaque);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000250 break;
251 default:
252 return false;
253 }
Matt Sarett1da27ef2017-01-19 17:14:07 -0500254
Matt Sarett84014f02017-01-10 11:28:54 -0500255 if (kIndex_8_SkColorType == colorType) {
Hal Canary1fcc4042016-11-30 17:07:59 -0500256 SkColorTable* ctable = pixmap.ctable();
257 if (!ctable || ctable->count() == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000258 return false;
259 }
Matt Sarett32bf4492017-01-02 09:56:02 -0500260
261 // Currently, we always use 8-bit indices for paletted pngs.
262 // When ctable->count() <= 16, we could potentially use 1, 2,
263 // or 4 bit indices.
reed@android.com8a1c16f2008-12-17 15:59:43 +0000264 }
Matt Sarett1da27ef2017-01-19 17:14:07 -0500265
Matt Sarett84014f02017-01-10 11:28:54 -0500266 return do_encode(stream, pixmap, pngColorType, bitDepth, sig_bit);
tomhudson@google.com5c210c72011-07-28 21:06:40 +0000267}
268
Matt Sarett1da27ef2017-01-19 17:14:07 -0500269static int num_components(int pngColorType) {
270 switch (pngColorType) {
271 case PNG_COLOR_TYPE_PALETTE:
272 case PNG_COLOR_TYPE_GRAY:
273 return 1;
274 case PNG_COLOR_TYPE_RGB:
275 return 3;
276 case PNG_COLOR_TYPE_RGBA:
277 return 4;
278 default:
279 SkASSERT(false);
280 return 0;
281 }
282}
283
Hal Canary1fcc4042016-11-30 17:07:59 -0500284static bool do_encode(SkWStream* stream, const SkPixmap& pixmap,
Matt Sarett84014f02017-01-10 11:28:54 -0500285 int pngColorType, int bitDepth, png_color_8& sig_bit) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000286 png_structp png_ptr;
287 png_infop info_ptr;
288
Hal Canary1fcc4042016-11-30 17:07:59 -0500289 png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, sk_error_fn, nullptr);
halcanary96fcdcc2015-08-27 07:41:13 -0700290 if (nullptr == png_ptr) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000291 return false;
292 }
293
294 info_ptr = png_create_info_struct(png_ptr);
halcanary96fcdcc2015-08-27 07:41:13 -0700295 if (nullptr == info_ptr) {
Matt Sarett32bf4492017-01-02 09:56:02 -0500296 png_destroy_write_struct(&png_ptr, nullptr);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000297 return false;
298 }
299
300 /* Set error handling. REQUIRED if you aren't supplying your own
301 * error handling functions in the png_create_write_struct() call.
302 */
303 if (setjmp(png_jmpbuf(png_ptr))) {
304 png_destroy_write_struct(&png_ptr, &info_ptr);
305 return false;
306 }
307
Matt Sarett32bf4492017-01-02 09:56:02 -0500308 png_set_write_fn(png_ptr, (void*)stream, sk_write_fn, nullptr);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000309
310 /* Set the image information here. Width and height are up to 2^31,
311 * bit_depth is one of 1, 2, 4, 8, or 16, but valid values also depend on
312 * the color_type selected. color_type is one of PNG_COLOR_TYPE_GRAY,
313 * PNG_COLOR_TYPE_GRAY_ALPHA, PNG_COLOR_TYPE_PALETTE, PNG_COLOR_TYPE_RGB,
314 * or PNG_COLOR_TYPE_RGB_ALPHA. interlace is either PNG_INTERLACE_NONE or
315 * PNG_INTERLACE_ADAM7, and the compression_type and filter_type MUST
316 * currently be PNG_COMPRESSION_TYPE_BASE and PNG_FILTER_TYPE_BASE. REQUIRED
317 */
318
Hal Canary1fcc4042016-11-30 17:07:59 -0500319 png_set_IHDR(png_ptr, info_ptr, pixmap.width(), pixmap.height(),
Matt Sarett84014f02017-01-10 11:28:54 -0500320 bitDepth, pngColorType,
reed@android.com8a1c16f2008-12-17 15:59:43 +0000321 PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE,
322 PNG_FILTER_TYPE_BASE);
323
reed@android.com61898772009-07-07 19:38:01 +0000324 // set our colortable/trans arrays if needed
325 png_color paletteColors[256];
326 png_byte trans[256];
Matt Sarett84014f02017-01-10 11:28:54 -0500327 if (kIndex_8_SkColorType == pixmap.colorType()) {
Hal Canary1fcc4042016-11-30 17:07:59 -0500328 SkColorTable* colorTable = pixmap.ctable();
msarettfed03342016-09-13 07:08:15 -0700329 SkASSERT(colorTable);
Matt Sarett84014f02017-01-10 11:28:54 -0500330 int numTrans = pack_palette(colorTable, paletteColors, trans, pixmap.info());
msarettf17b71f2016-09-12 14:30:03 -0700331 png_set_PLTE(png_ptr, info_ptr, paletteColors, colorTable->count());
reed@android.com61898772009-07-07 19:38:01 +0000332 if (numTrans > 0) {
halcanary96fcdcc2015-08-27 07:41:13 -0700333 png_set_tRNS(png_ptr, info_ptr, trans, numTrans, nullptr);
reed@android.com61898772009-07-07 19:38:01 +0000334 }
335 }
msarett9b09cd82016-08-29 14:47:49 -0700336
reed@android.com8a1c16f2008-12-17 15:59:43 +0000337 png_set_sBIT(png_ptr, info_ptr, &sig_bit);
338 png_write_info(png_ptr, info_ptr);
Matt Sarett1da27ef2017-01-19 17:14:07 -0500339 int pngBytesPerPixel = num_components(pngColorType) * (bitDepth / 8);
340 if (kRGBA_F16_SkColorType == pixmap.colorType() && kOpaque_SkAlphaType == pixmap.alphaType()) {
341 // For kOpaque, kRGBA_F16, we will keep the row as RGBA and tell libpng
342 // to skip the alpha channel.
343 png_set_filler(png_ptr, 0, PNG_FILLER_AFTER);
344 pngBytesPerPixel = 8;
345 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000346
Matt Sarett1da27ef2017-01-19 17:14:07 -0500347 SkAutoSTMalloc<1024, char> rowStorage(pixmap.width() * pngBytesPerPixel);
scroggo565901d2015-12-10 10:44:13 -0800348 char* storage = rowStorage.get();
Matt Sarett1da27ef2017-01-19 17:14:07 -0500349 const char* srcImage = (const char*)pixmap.addr();
Matt Sarett84014f02017-01-10 11:28:54 -0500350 transform_scanline_proc proc = choose_proc(pixmap.info());
Hal Canary1fcc4042016-11-30 17:07:59 -0500351 for (int y = 0; y < pixmap.height(); y++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000352 png_bytep row_ptr = (png_bytep)storage;
Matt Sarett84014f02017-01-10 11:28:54 -0500353 proc(storage, srcImage, pixmap.width(), SkColorTypeBytesPerPixel(pixmap.colorType()));
reed@android.com8a1c16f2008-12-17 15:59:43 +0000354 png_write_rows(png_ptr, &row_ptr, 1);
Hal Canary1fcc4042016-11-30 17:07:59 -0500355 srcImage += pixmap.rowBytes();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000356 }
357
358 png_write_end(png_ptr, info_ptr);
359
360 /* clean up after the write, and free any memory allocated */
361 png_destroy_write_struct(&png_ptr, &info_ptr);
362 return true;
363}
364
Hal Canary1fcc4042016-11-30 17:07:59 -0500365#endif