blob: d752bf86bab45f8ed63b1c8ca1dd860dbcce7e7c [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
msarettf17b71f2016-09-12 14:30:03 -070041static transform_scanline_proc choose_proc(SkColorType ct, SkAlphaType alphaType) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000042 static const struct {
reed0689d7b2014-06-14 05:30:20 -070043 SkColorType fColorType;
msarettf17b71f2016-09-12 14:30:03 -070044 SkAlphaType fAlphaType;
reed@android.com8a1c16f2008-12-17 15:59:43 +000045 transform_scanline_proc fProc;
46 } gMap[] = {
msarettf17b71f2016-09-12 14:30:03 -070047 { kRGB_565_SkColorType, kOpaque_SkAlphaType, transform_scanline_565 },
48 { kRGBA_8888_SkColorType, kOpaque_SkAlphaType, transform_scanline_RGBX },
49 { kBGRA_8888_SkColorType, kOpaque_SkAlphaType, transform_scanline_BGRX },
50 { kRGBA_8888_SkColorType, kPremul_SkAlphaType, transform_scanline_rgbA },
51 { kBGRA_8888_SkColorType, kPremul_SkAlphaType, transform_scanline_bgrA },
52 { kRGBA_8888_SkColorType, kUnpremul_SkAlphaType, transform_scanline_memcpy },
53 { kBGRA_8888_SkColorType, kUnpremul_SkAlphaType, transform_scanline_BGRA },
54 { kARGB_4444_SkColorType, kOpaque_SkAlphaType, transform_scanline_444 },
55 { kARGB_4444_SkColorType, kPremul_SkAlphaType, transform_scanline_4444 },
56 { kIndex_8_SkColorType, kOpaque_SkAlphaType, transform_scanline_memcpy },
57 { kIndex_8_SkColorType, kPremul_SkAlphaType, transform_scanline_memcpy },
58 { kIndex_8_SkColorType, kUnpremul_SkAlphaType, transform_scanline_memcpy },
59 { kGray_8_SkColorType, kOpaque_SkAlphaType, transform_scanline_memcpy },
reed@android.com8a1c16f2008-12-17 15:59:43 +000060 };
61
msarettf17b71f2016-09-12 14:30:03 -070062 for (auto entry : gMap) {
63 if (entry.fColorType == ct && entry.fAlphaType == alphaType) {
64 return entry.fProc;
reed@android.com8a1c16f2008-12-17 15:59:43 +000065 }
66 }
67 sk_throw();
halcanary96fcdcc2015-08-27 07:41:13 -070068 return nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +000069}
70
msarettfed03342016-09-13 07:08:15 -070071/* Pack palette[] with the corresponding colors, and if the image has alpha, also
72 pack trans[] and return the number of alphas[] entries written. If the image is
73 opaque, the return value will always be 0.
reed@android.com8a1c16f2008-12-17 15:59:43 +000074*/
msarettfed03342016-09-13 07:08:15 -070075static inline int pack_palette(SkColorTable* ctable, png_color* SK_RESTRICT palette,
76 png_byte* SK_RESTRICT alphas, SkAlphaType alphaType) {
77 const SkPMColor* SK_RESTRICT colors = ctable->readColors();
78 const int count = ctable->count();
79 int numWithAlpha = 0;
msarettf17b71f2016-09-12 14:30:03 -070080 if (kOpaque_SkAlphaType != alphaType) {
msarettfed03342016-09-13 07:08:15 -070081 auto getUnpremulColor = [alphaType](uint8_t color, uint8_t alpha) {
82 if (kPremul_SkAlphaType == alphaType) {
83 const SkUnPreMultiply::Scale* table = SkUnPreMultiply::GetScaleTable();
84 const SkUnPreMultiply::Scale scale = table[alpha];
85 return (uint8_t) SkUnPreMultiply::ApplyScale(scale, color);
86 } else {
87 return color;
reed@android.com8a1c16f2008-12-17 15:59:43 +000088 }
msarettfed03342016-09-13 07:08:15 -070089 };
rmistry@google.comd6176b02012-08-23 18:14:13 +000090
msarettfed03342016-09-13 07:08:15 -070091 // PNG requires that all non-opaque colors come first in the palette. Write these first.
92 for (int i = 0; i < count; i++) {
93 uint8_t alpha = SkGetPackedA32(colors[i]);
94 if (0xFF != alpha) {
95 alphas[numWithAlpha] = alpha;
96 palette[numWithAlpha].red = getUnpremulColor(SkGetPackedR32(colors[i]), alpha);
97 palette[numWithAlpha].green = getUnpremulColor(SkGetPackedG32(colors[i]), alpha);
98 palette[numWithAlpha].blue = getUnpremulColor(SkGetPackedB32(colors[i]), alpha);
99 numWithAlpha++;
msarettf17b71f2016-09-12 14:30:03 -0700100 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000101 }
msarettf17b71f2016-09-12 14:30:03 -0700102
reed@android.com8a1c16f2008-12-17 15:59:43 +0000103 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000104
msarettfed03342016-09-13 07:08:15 -0700105 if (0 == numWithAlpha) {
106 // All of the entries are opaque.
107 for (int i = 0; i < count; i++) {
108 SkPMColor c = *colors++;
109 palette[i].red = SkGetPackedR32(c);
110 palette[i].green = SkGetPackedG32(c);
111 palette[i].blue = SkGetPackedB32(c);
112 }
113 } else {
114 // We have already written the non-opaque colors. Now just write the opaque colors.
115 int currIndex = numWithAlpha;
116 int i = 0;
117 while (currIndex != count) {
118 uint8_t alpha = SkGetPackedA32(colors[i]);
119 if (0xFF == alpha) {
120 palette[currIndex].red = SkGetPackedR32(colors[i]);
121 palette[currIndex].green = SkGetPackedG32(colors[i]);
122 palette[currIndex].blue = SkGetPackedB32(colors[i]);
123 currIndex++;
124 }
125
126 i++;
127 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000128 }
msarettfed03342016-09-13 07:08:15 -0700129
130 return numWithAlpha;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000131}
132
Hal Canary1fcc4042016-11-30 17:07:59 -0500133static bool do_encode(SkWStream*, const SkPixmap&, int, int, png_color_8&);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000134
Hal Canary1fcc4042016-11-30 17:07:59 -0500135bool SkEncodeImageAsPNG(SkWStream* stream, const SkPixmap& pixmap) {
136 if (!pixmap.addr() || pixmap.info().isEmpty()) {
137 return false;
138 }
139 const SkColorType ct = pixmap.colorType();
msarett029819b2016-09-12 15:49:37 -0700140 switch (ct) {
halcanarydfd6c6e2015-12-07 14:07:31 -0800141 case kIndex_8_SkColorType:
msarett9b09cd82016-08-29 14:47:49 -0700142 case kGray_8_SkColorType:
msarettf17b71f2016-09-12 14:30:03 -0700143 case kRGBA_8888_SkColorType:
144 case kBGRA_8888_SkColorType:
halcanarydfd6c6e2015-12-07 14:07:31 -0800145 case kARGB_4444_SkColorType:
146 case kRGB_565_SkColorType:
147 break;
148 default:
msarett029819b2016-09-12 15:49:37 -0700149 return false;
halcanarydfd6c6e2015-12-07 14:07:31 -0800150 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000151
Hal Canary1fcc4042016-11-30 17:07:59 -0500152 const SkAlphaType alphaType = pixmap.alphaType();
msarettf17b71f2016-09-12 14:30:03 -0700153 switch (alphaType) {
154 case kUnpremul_SkAlphaType:
155 if (kARGB_4444_SkColorType == ct) {
156 return false;
157 }
158
159 break;
160 case kOpaque_SkAlphaType:
161 case kPremul_SkAlphaType:
162 break;
163 default:
164 return false;
165 }
166
167 const bool isOpaque = (kOpaque_SkAlphaType == alphaType);
Matt Sarett32bf4492017-01-02 09:56:02 -0500168 const int bitDepth = 8;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000169 png_color_8 sig_bit;
msarett9b09cd82016-08-29 14:47:49 -0700170 sk_bzero(&sig_bit, sizeof(png_color_8));
reed@android.com8a1c16f2008-12-17 15:59:43 +0000171
msarett9b09cd82016-08-29 14:47:49 -0700172 int colorType;
reed0689d7b2014-06-14 05:30:20 -0700173 switch (ct) {
174 case kIndex_8_SkColorType:
msarett9b09cd82016-08-29 14:47:49 -0700175 sig_bit.red = 8;
176 sig_bit.green = 8;
177 sig_bit.blue = 8;
178 sig_bit.alpha = 8;
179 colorType = PNG_COLOR_TYPE_PALETTE;
180 break;
181 case kGray_8_SkColorType:
182 sig_bit.gray = 8;
183 colorType = PNG_COLOR_TYPE_GRAY;
msarettf17b71f2016-09-12 14:30:03 -0700184 SkASSERT(isOpaque);
msarett9b09cd82016-08-29 14:47:49 -0700185 break;
msarettf17b71f2016-09-12 14:30:03 -0700186 case kRGBA_8888_SkColorType:
187 case kBGRA_8888_SkColorType:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000188 sig_bit.red = 8;
189 sig_bit.green = 8;
190 sig_bit.blue = 8;
191 sig_bit.alpha = 8;
msarettf17b71f2016-09-12 14:30:03 -0700192 colorType = isOpaque ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000193 break;
reed0689d7b2014-06-14 05:30:20 -0700194 case kARGB_4444_SkColorType:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000195 sig_bit.red = 4;
196 sig_bit.green = 4;
197 sig_bit.blue = 4;
198 sig_bit.alpha = 4;
msarettf17b71f2016-09-12 14:30:03 -0700199 colorType = isOpaque ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000200 break;
reed0689d7b2014-06-14 05:30:20 -0700201 case kRGB_565_SkColorType:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000202 sig_bit.red = 5;
203 sig_bit.green = 6;
204 sig_bit.blue = 5;
msarett9b09cd82016-08-29 14:47:49 -0700205 colorType = PNG_COLOR_TYPE_RGB;
msarettf17b71f2016-09-12 14:30:03 -0700206 SkASSERT(isOpaque);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000207 break;
208 default:
209 return false;
210 }
Hal Canary1fcc4042016-11-30 17:07:59 -0500211 if (kIndex_8_SkColorType == ct) {
212 SkColorTable* ctable = pixmap.ctable();
213 if (!ctable || ctable->count() == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000214 return false;
215 }
Matt Sarett32bf4492017-01-02 09:56:02 -0500216
217 // Currently, we always use 8-bit indices for paletted pngs.
218 // When ctable->count() <= 16, we could potentially use 1, 2,
219 // or 4 bit indices.
reed@android.com8a1c16f2008-12-17 15:59:43 +0000220 }
Hal Canary1fcc4042016-11-30 17:07:59 -0500221 return do_encode(stream, pixmap, colorType, bitDepth, sig_bit);
tomhudson@google.com5c210c72011-07-28 21:06:40 +0000222}
223
Hal Canary1fcc4042016-11-30 17:07:59 -0500224static bool do_encode(SkWStream* stream, const SkPixmap& pixmap,
225 int colorType, int bitDepth, png_color_8& sig_bit) {
226 SkAlphaType alphaType = pixmap.alphaType();
227 SkColorType ct = pixmap.colorType();
tomhudson@google.com5c210c72011-07-28 21:06:40 +0000228
reed@android.com8a1c16f2008-12-17 15:59:43 +0000229 png_structp png_ptr;
230 png_infop info_ptr;
231
Hal Canary1fcc4042016-11-30 17:07:59 -0500232 png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, sk_error_fn, nullptr);
halcanary96fcdcc2015-08-27 07:41:13 -0700233 if (nullptr == png_ptr) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000234 return false;
235 }
236
237 info_ptr = png_create_info_struct(png_ptr);
halcanary96fcdcc2015-08-27 07:41:13 -0700238 if (nullptr == info_ptr) {
Matt Sarett32bf4492017-01-02 09:56:02 -0500239 png_destroy_write_struct(&png_ptr, nullptr);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000240 return false;
241 }
242
243 /* Set error handling. REQUIRED if you aren't supplying your own
244 * error handling functions in the png_create_write_struct() call.
245 */
246 if (setjmp(png_jmpbuf(png_ptr))) {
247 png_destroy_write_struct(&png_ptr, &info_ptr);
248 return false;
249 }
250
Matt Sarett32bf4492017-01-02 09:56:02 -0500251 png_set_write_fn(png_ptr, (void*)stream, sk_write_fn, nullptr);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000252
253 /* Set the image information here. Width and height are up to 2^31,
254 * bit_depth is one of 1, 2, 4, 8, or 16, but valid values also depend on
255 * the color_type selected. color_type is one of PNG_COLOR_TYPE_GRAY,
256 * PNG_COLOR_TYPE_GRAY_ALPHA, PNG_COLOR_TYPE_PALETTE, PNG_COLOR_TYPE_RGB,
257 * or PNG_COLOR_TYPE_RGB_ALPHA. interlace is either PNG_INTERLACE_NONE or
258 * PNG_INTERLACE_ADAM7, and the compression_type and filter_type MUST
259 * currently be PNG_COMPRESSION_TYPE_BASE and PNG_FILTER_TYPE_BASE. REQUIRED
260 */
261
Hal Canary1fcc4042016-11-30 17:07:59 -0500262 png_set_IHDR(png_ptr, info_ptr, pixmap.width(), pixmap.height(),
reed@android.com8a1c16f2008-12-17 15:59:43 +0000263 bitDepth, colorType,
264 PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE,
265 PNG_FILTER_TYPE_BASE);
266
reed@android.com61898772009-07-07 19:38:01 +0000267 // set our colortable/trans arrays if needed
268 png_color paletteColors[256];
269 png_byte trans[256];
reed0689d7b2014-06-14 05:30:20 -0700270 if (kIndex_8_SkColorType == ct) {
Hal Canary1fcc4042016-11-30 17:07:59 -0500271 SkColorTable* colorTable = pixmap.ctable();
msarettfed03342016-09-13 07:08:15 -0700272 SkASSERT(colorTable);
msarettf17b71f2016-09-12 14:30:03 -0700273 int numTrans = pack_palette(colorTable, paletteColors, trans, alphaType);
274 png_set_PLTE(png_ptr, info_ptr, paletteColors, colorTable->count());
reed@android.com61898772009-07-07 19:38:01 +0000275 if (numTrans > 0) {
halcanary96fcdcc2015-08-27 07:41:13 -0700276 png_set_tRNS(png_ptr, info_ptr, trans, numTrans, nullptr);
reed@android.com61898772009-07-07 19:38:01 +0000277 }
278 }
msarett9b09cd82016-08-29 14:47:49 -0700279
reed@android.com8a1c16f2008-12-17 15:59:43 +0000280 png_set_sBIT(png_ptr, info_ptr, &sig_bit);
281 png_write_info(png_ptr, info_ptr);
282
Hal Canary1fcc4042016-11-30 17:07:59 -0500283 const char* srcImage = (const char*)pixmap.addr();
284 SkAutoSTMalloc<1024, char> rowStorage(pixmap.width() << 2);
scroggo565901d2015-12-10 10:44:13 -0800285 char* storage = rowStorage.get();
msarettf17b71f2016-09-12 14:30:03 -0700286 transform_scanline_proc proc = choose_proc(ct, alphaType);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000287
Hal Canary1fcc4042016-11-30 17:07:59 -0500288 for (int y = 0; y < pixmap.height(); y++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000289 png_bytep row_ptr = (png_bytep)storage;
Hal Canary1fcc4042016-11-30 17:07:59 -0500290 proc(storage, srcImage, pixmap.width(), SkColorTypeBytesPerPixel(ct));
reed@android.com8a1c16f2008-12-17 15:59:43 +0000291 png_write_rows(png_ptr, &row_ptr, 1);
Hal Canary1fcc4042016-11-30 17:07:59 -0500292 srcImage += pixmap.rowBytes();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000293 }
294
295 png_write_end(png_ptr, info_ptr);
296
297 /* clean up after the write, and free any memory allocated */
298 png_destroy_write_struct(&png_ptr, &info_ptr);
299 return true;
300}
301
Hal Canary1fcc4042016-11-30 17:07:59 -0500302#endif