blob: e28ae12dc0d779f3a94158c02a4181ec55649bb2 [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 Sarett5df93de2017-03-22 21:52:47 +000015#include "SkImageEncoderFns.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"
Matt Sarett74de1f62017-03-21 13:03:26 -040018#include "SkString.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000019#include "SkTemplates.h"
Hal Canary1fcc4042016-11-30 17:07:59 -050020#include "SkUnPreMultiply.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000021#include "SkUtils.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 Sarett5df93de2017-03-22 21:52:47 +000042static void set_icc(png_structp png_ptr, png_infop info_ptr, sk_sp<SkData> icc) {
Matt Sarett687b6562017-03-21 10:06:45 -040043#if PNG_LIBPNG_VER_MAJOR > 1 || (PNG_LIBPNG_VER_MAJOR == 1 && PNG_LIBPNG_VER_MINOR >= 5)
Matt Sarett74de1f62017-03-21 13:03:26 -040044 const char* name = "Skia";
Matt Sarett687b6562017-03-21 10:06:45 -040045 png_const_bytep iccPtr = icc->bytes();
46#else
Matt Sarett74de1f62017-03-21 13:03:26 -040047 SkString str("Skia");
48 char* name = str.writable_str();
Matt Sarett687b6562017-03-21 10:06:45 -040049 png_charp iccPtr = (png_charp) icc->writable_data();
50#endif
Matt Sarett74de1f62017-03-21 13:03:26 -040051 png_set_iCCP(png_ptr, info_ptr, name, 0, iccPtr, icc->size());
Matt Sarett687b6562017-03-21 10:06:45 -040052}
53
Matt Sarett84014f02017-01-10 11:28:54 -050054static transform_scanline_proc choose_proc(const SkImageInfo& info) {
55 const bool isGammaEncoded = info.gammaCloseToSRGB();
56 switch (info.colorType()) {
57 case kRGBA_8888_SkColorType:
58 switch (info.alphaType()) {
59 case kOpaque_SkAlphaType:
60 return transform_scanline_RGBX;
61 case kUnpremul_SkAlphaType:
62 return transform_scanline_memcpy;
63 case kPremul_SkAlphaType:
64 return isGammaEncoded ? transform_scanline_srgbA :
65 transform_scanline_rgbA;
66 default:
67 SkASSERT(false);
68 return nullptr;
69 }
70 case kBGRA_8888_SkColorType:
71 switch (info.alphaType()) {
72 case kOpaque_SkAlphaType:
73 return transform_scanline_BGRX;
74 case kUnpremul_SkAlphaType:
75 return transform_scanline_BGRA;
76 case kPremul_SkAlphaType:
77 return isGammaEncoded ? transform_scanline_sbgrA :
78 transform_scanline_bgrA;
79 default:
80 SkASSERT(false);
81 return nullptr;
82 }
83 case kRGB_565_SkColorType:
84 return transform_scanline_565;
85 case kARGB_4444_SkColorType:
86 switch (info.alphaType()) {
87 case kOpaque_SkAlphaType:
88 return transform_scanline_444;
89 case kPremul_SkAlphaType:
90 // 4444 is assumed to be legacy premul.
91 return transform_scanline_4444;
92 default:
93 SkASSERT(false);
94 return nullptr;
95 }
96 case kIndex_8_SkColorType:
97 case kGray_8_SkColorType:
98 return transform_scanline_memcpy;
Matt Sarett1da27ef2017-01-19 17:14:07 -050099 case kRGBA_F16_SkColorType:
100 switch (info.alphaType()) {
101 case kOpaque_SkAlphaType:
102 case kUnpremul_SkAlphaType:
103 return transform_scanline_F16;
104 case kPremul_SkAlphaType:
105 return transform_scanline_F16_premul;
106 default:
107 SkASSERT(false);
108 return nullptr;
109 }
Matt Sarett84014f02017-01-10 11:28:54 -0500110 default:
111 SkASSERT(false);
112 return nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000113 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000114}
115
msarettfed03342016-09-13 07:08:15 -0700116/* Pack palette[] with the corresponding colors, and if the image has alpha, also
117 pack trans[] and return the number of alphas[] entries written. If the image is
118 opaque, the return value will always be 0.
reed@android.com8a1c16f2008-12-17 15:59:43 +0000119*/
msarettfed03342016-09-13 07:08:15 -0700120static inline int pack_palette(SkColorTable* ctable, png_color* SK_RESTRICT palette,
Matt Sarett84014f02017-01-10 11:28:54 -0500121 png_byte* SK_RESTRICT alphas, const SkImageInfo& info) {
122 const SkPMColor* colors = ctable->readColors();
msarettfed03342016-09-13 07:08:15 -0700123 const int count = ctable->count();
Matt Sarett84014f02017-01-10 11:28:54 -0500124 SkPMColor storage[256];
125 if (kPremul_SkAlphaType == info.alphaType()) {
126 // Unpremultiply the colors.
127 const SkImageInfo rgbaInfo = info.makeColorType(kRGBA_8888_SkColorType);
128 transform_scanline_proc proc = choose_proc(rgbaInfo);
Matt Sarett62bb2802017-01-23 12:28:02 -0500129 proc((char*) storage, (const char*) colors, ctable->count(), 4, nullptr);
Matt Sarett84014f02017-01-10 11:28:54 -0500130 colors = storage;
131 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000132
Matt Sarett84014f02017-01-10 11:28:54 -0500133 int numWithAlpha = 0;
134 if (kOpaque_SkAlphaType != info.alphaType()) {
msarettfed03342016-09-13 07:08:15 -0700135 // PNG requires that all non-opaque colors come first in the palette. Write these first.
136 for (int i = 0; i < count; i++) {
137 uint8_t alpha = SkGetPackedA32(colors[i]);
138 if (0xFF != alpha) {
139 alphas[numWithAlpha] = alpha;
Matt Sarett84014f02017-01-10 11:28:54 -0500140 palette[numWithAlpha].red = SkGetPackedR32(colors[i]);
141 palette[numWithAlpha].green = SkGetPackedG32(colors[i]);
142 palette[numWithAlpha].blue = SkGetPackedB32(colors[i]);
msarettfed03342016-09-13 07:08:15 -0700143 numWithAlpha++;
msarettf17b71f2016-09-12 14:30:03 -0700144 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000145 }
msarettf17b71f2016-09-12 14:30:03 -0700146
reed@android.com8a1c16f2008-12-17 15:59:43 +0000147 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000148
msarettfed03342016-09-13 07:08:15 -0700149 if (0 == numWithAlpha) {
150 // All of the entries are opaque.
151 for (int i = 0; i < count; i++) {
152 SkPMColor c = *colors++;
153 palette[i].red = SkGetPackedR32(c);
154 palette[i].green = SkGetPackedG32(c);
155 palette[i].blue = SkGetPackedB32(c);
156 }
157 } else {
158 // We have already written the non-opaque colors. Now just write the opaque colors.
159 int currIndex = numWithAlpha;
160 int i = 0;
161 while (currIndex != count) {
162 uint8_t alpha = SkGetPackedA32(colors[i]);
163 if (0xFF == alpha) {
164 palette[currIndex].red = SkGetPackedR32(colors[i]);
165 palette[currIndex].green = SkGetPackedG32(colors[i]);
166 palette[currIndex].blue = SkGetPackedB32(colors[i]);
167 currIndex++;
168 }
169
170 i++;
171 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000172 }
msarettfed03342016-09-13 07:08:15 -0700173
174 return numWithAlpha;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000175}
176
Hal Canary1fcc4042016-11-30 17:07:59 -0500177static bool do_encode(SkWStream*, const SkPixmap&, int, int, png_color_8&);
commit-bot@chromium.orga936e372013-03-14 14:42:18 +0000178
Matt Sarett84014f02017-01-10 11:28:54 -0500179bool SkEncodeImageAsPNG(SkWStream* stream, const SkPixmap& src, const SkEncodeOptions& opts) {
180 SkASSERT(!src.colorSpace() || src.colorSpace()->gammaCloseToSRGB() ||
181 src.colorSpace()->gammaIsLinear());
182
183 SkPixmap pixmap = src;
Matt Sarett0e032be2017-03-15 17:50:08 -0400184 if (SkEncodeOptions::ColorBehavior::kLegacy == opts.fColorBehavior) {
Matt Sarett84014f02017-01-10 11:28:54 -0500185 pixmap.setColorSpace(nullptr);
186 } else {
187 if (!pixmap.colorSpace()) {
188 return false;
189 }
190 }
191
Hal Canary1fcc4042016-11-30 17:07:59 -0500192 if (!pixmap.addr() || pixmap.info().isEmpty()) {
193 return false;
194 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000195
Matt Sarett1da27ef2017-01-19 17:14:07 -0500196 const SkColorType colorType = pixmap.colorType();
Hal Canary1fcc4042016-11-30 17:07:59 -0500197 const SkAlphaType alphaType = pixmap.alphaType();
msarettf17b71f2016-09-12 14:30:03 -0700198 switch (alphaType) {
199 case kUnpremul_SkAlphaType:
Matt Sarett84014f02017-01-10 11:28:54 -0500200 if (kARGB_4444_SkColorType == colorType) {
msarettf17b71f2016-09-12 14:30:03 -0700201 return false;
202 }
203
204 break;
205 case kOpaque_SkAlphaType:
206 case kPremul_SkAlphaType:
207 break;
208 default:
209 return false;
210 }
211
212 const bool isOpaque = (kOpaque_SkAlphaType == alphaType);
Matt Sarett1da27ef2017-01-19 17:14:07 -0500213 int bitDepth = 8;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000214 png_color_8 sig_bit;
msarett9b09cd82016-08-29 14:47:49 -0700215 sk_bzero(&sig_bit, sizeof(png_color_8));
Matt Sarett84014f02017-01-10 11:28:54 -0500216 int pngColorType;
217 switch (colorType) {
Matt Sarett1da27ef2017-01-19 17:14:07 -0500218 case kRGBA_F16_SkColorType:
219 if (!pixmap.colorSpace() || !pixmap.colorSpace()->gammaIsLinear()) {
220 return false;
221 }
222
223 sig_bit.red = 16;
224 sig_bit.green = 16;
225 sig_bit.blue = 16;
226 sig_bit.alpha = 16;
227 bitDepth = 16;
228 pngColorType = isOpaque ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA;
229 break;
reed0689d7b2014-06-14 05:30:20 -0700230 case kIndex_8_SkColorType:
msarett9b09cd82016-08-29 14:47:49 -0700231 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 = PNG_COLOR_TYPE_PALETTE;
msarett9b09cd82016-08-29 14:47:49 -0700236 break;
237 case kGray_8_SkColorType:
238 sig_bit.gray = 8;
Matt Sarett84014f02017-01-10 11:28:54 -0500239 pngColorType = PNG_COLOR_TYPE_GRAY;
msarettf17b71f2016-09-12 14:30:03 -0700240 SkASSERT(isOpaque);
msarett9b09cd82016-08-29 14:47:49 -0700241 break;
msarettf17b71f2016-09-12 14:30:03 -0700242 case kRGBA_8888_SkColorType:
243 case kBGRA_8888_SkColorType:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000244 sig_bit.red = 8;
245 sig_bit.green = 8;
246 sig_bit.blue = 8;
247 sig_bit.alpha = 8;
Matt Sarett84014f02017-01-10 11:28:54 -0500248 pngColorType = isOpaque ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000249 break;
reed0689d7b2014-06-14 05:30:20 -0700250 case kARGB_4444_SkColorType:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000251 sig_bit.red = 4;
252 sig_bit.green = 4;
253 sig_bit.blue = 4;
254 sig_bit.alpha = 4;
Matt Sarett84014f02017-01-10 11:28:54 -0500255 pngColorType = isOpaque ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000256 break;
reed0689d7b2014-06-14 05:30:20 -0700257 case kRGB_565_SkColorType:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000258 sig_bit.red = 5;
259 sig_bit.green = 6;
260 sig_bit.blue = 5;
Matt Sarett84014f02017-01-10 11:28:54 -0500261 pngColorType = PNG_COLOR_TYPE_RGB;
msarettf17b71f2016-09-12 14:30:03 -0700262 SkASSERT(isOpaque);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000263 break;
264 default:
265 return false;
266 }
Matt Sarett1da27ef2017-01-19 17:14:07 -0500267
Matt Sarett84014f02017-01-10 11:28:54 -0500268 if (kIndex_8_SkColorType == colorType) {
Hal Canary1fcc4042016-11-30 17:07:59 -0500269 SkColorTable* ctable = pixmap.ctable();
270 if (!ctable || ctable->count() == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000271 return false;
272 }
Matt Sarett32bf4492017-01-02 09:56:02 -0500273
274 // Currently, we always use 8-bit indices for paletted pngs.
275 // When ctable->count() <= 16, we could potentially use 1, 2,
276 // or 4 bit indices.
reed@android.com8a1c16f2008-12-17 15:59:43 +0000277 }
Matt Sarett1da27ef2017-01-19 17:14:07 -0500278
Matt Sarett84014f02017-01-10 11:28:54 -0500279 return do_encode(stream, pixmap, pngColorType, bitDepth, sig_bit);
tomhudson@google.com5c210c72011-07-28 21:06:40 +0000280}
281
Matt Sarett1da27ef2017-01-19 17:14:07 -0500282static int num_components(int pngColorType) {
283 switch (pngColorType) {
284 case PNG_COLOR_TYPE_PALETTE:
285 case PNG_COLOR_TYPE_GRAY:
286 return 1;
287 case PNG_COLOR_TYPE_RGB:
288 return 3;
289 case PNG_COLOR_TYPE_RGBA:
290 return 4;
291 default:
292 SkASSERT(false);
293 return 0;
294 }
295}
296
Hal Canary1fcc4042016-11-30 17:07:59 -0500297static bool do_encode(SkWStream* stream, const SkPixmap& pixmap,
Matt Sarett84014f02017-01-10 11:28:54 -0500298 int pngColorType, int bitDepth, png_color_8& sig_bit) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000299 png_structp png_ptr;
300 png_infop info_ptr;
301
Hal Canary1fcc4042016-11-30 17:07:59 -0500302 png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, sk_error_fn, nullptr);
halcanary96fcdcc2015-08-27 07:41:13 -0700303 if (nullptr == png_ptr) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000304 return false;
305 }
306
307 info_ptr = png_create_info_struct(png_ptr);
halcanary96fcdcc2015-08-27 07:41:13 -0700308 if (nullptr == info_ptr) {
Matt Sarett32bf4492017-01-02 09:56:02 -0500309 png_destroy_write_struct(&png_ptr, nullptr);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000310 return false;
311 }
312
313 /* Set error handling. REQUIRED if you aren't supplying your own
314 * error handling functions in the png_create_write_struct() call.
315 */
316 if (setjmp(png_jmpbuf(png_ptr))) {
317 png_destroy_write_struct(&png_ptr, &info_ptr);
318 return false;
319 }
320
Matt Sarett32bf4492017-01-02 09:56:02 -0500321 png_set_write_fn(png_ptr, (void*)stream, sk_write_fn, nullptr);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000322
323 /* Set the image information here. Width and height are up to 2^31,
324 * bit_depth is one of 1, 2, 4, 8, or 16, but valid values also depend on
325 * the color_type selected. color_type is one of PNG_COLOR_TYPE_GRAY,
326 * PNG_COLOR_TYPE_GRAY_ALPHA, PNG_COLOR_TYPE_PALETTE, PNG_COLOR_TYPE_RGB,
327 * or PNG_COLOR_TYPE_RGB_ALPHA. interlace is either PNG_INTERLACE_NONE or
328 * PNG_INTERLACE_ADAM7, and the compression_type and filter_type MUST
329 * currently be PNG_COMPRESSION_TYPE_BASE and PNG_FILTER_TYPE_BASE. REQUIRED
330 */
331
Hal Canary1fcc4042016-11-30 17:07:59 -0500332 png_set_IHDR(png_ptr, info_ptr, pixmap.width(), pixmap.height(),
Matt Sarett84014f02017-01-10 11:28:54 -0500333 bitDepth, pngColorType,
reed@android.com8a1c16f2008-12-17 15:59:43 +0000334 PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE,
335 PNG_FILTER_TYPE_BASE);
336
reed@android.com61898772009-07-07 19:38:01 +0000337 // set our colortable/trans arrays if needed
338 png_color paletteColors[256];
339 png_byte trans[256];
Matt Sarett84014f02017-01-10 11:28:54 -0500340 if (kIndex_8_SkColorType == pixmap.colorType()) {
Hal Canary1fcc4042016-11-30 17:07:59 -0500341 SkColorTable* colorTable = pixmap.ctable();
msarettfed03342016-09-13 07:08:15 -0700342 SkASSERT(colorTable);
Matt Sarett84014f02017-01-10 11:28:54 -0500343 int numTrans = pack_palette(colorTable, paletteColors, trans, pixmap.info());
msarettf17b71f2016-09-12 14:30:03 -0700344 png_set_PLTE(png_ptr, info_ptr, paletteColors, colorTable->count());
reed@android.com61898772009-07-07 19:38:01 +0000345 if (numTrans > 0) {
halcanary96fcdcc2015-08-27 07:41:13 -0700346 png_set_tRNS(png_ptr, info_ptr, trans, numTrans, nullptr);
reed@android.com61898772009-07-07 19:38:01 +0000347 }
348 }
msarett9b09cd82016-08-29 14:47:49 -0700349
Matt Sarett0e032be2017-03-15 17:50:08 -0400350 if (pixmap.colorSpace()) {
Matt Sarett0e032be2017-03-15 17:50:08 -0400351 if (pixmap.colorSpace()->isSRGB()) {
352 png_set_sRGB(png_ptr, info_ptr, PNG_sRGB_INTENT_PERCEPTUAL);
Matt Sarett5df93de2017-03-22 21:52:47 +0000353 } else {
354 sk_sp<SkData> icc = icc_from_color_space(*pixmap.colorSpace());
355 if (icc) {
356 set_icc(png_ptr, info_ptr, std::move(icc));
357 }
Matt Sarett0e032be2017-03-15 17:50:08 -0400358 }
Matt Sarett0e032be2017-03-15 17:50:08 -0400359 }
360
reed@android.com8a1c16f2008-12-17 15:59:43 +0000361 png_set_sBIT(png_ptr, info_ptr, &sig_bit);
362 png_write_info(png_ptr, info_ptr);
Matt Sarett1da27ef2017-01-19 17:14:07 -0500363 int pngBytesPerPixel = num_components(pngColorType) * (bitDepth / 8);
364 if (kRGBA_F16_SkColorType == pixmap.colorType() && kOpaque_SkAlphaType == pixmap.alphaType()) {
365 // For kOpaque, kRGBA_F16, we will keep the row as RGBA and tell libpng
366 // to skip the alpha channel.
367 png_set_filler(png_ptr, 0, PNG_FILLER_AFTER);
368 pngBytesPerPixel = 8;
369 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000370
Matt Sarett1da27ef2017-01-19 17:14:07 -0500371 SkAutoSTMalloc<1024, char> rowStorage(pixmap.width() * pngBytesPerPixel);
scroggo565901d2015-12-10 10:44:13 -0800372 char* storage = rowStorage.get();
Matt Sarett1da27ef2017-01-19 17:14:07 -0500373 const char* srcImage = (const char*)pixmap.addr();
Matt Sarett84014f02017-01-10 11:28:54 -0500374 transform_scanline_proc proc = choose_proc(pixmap.info());
Hal Canary1fcc4042016-11-30 17:07:59 -0500375 for (int y = 0; y < pixmap.height(); y++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000376 png_bytep row_ptr = (png_bytep)storage;
Matt Sarett62bb2802017-01-23 12:28:02 -0500377 proc(storage, srcImage, pixmap.width(), SkColorTypeBytesPerPixel(pixmap.colorType()),
378 nullptr);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000379 png_write_rows(png_ptr, &row_ptr, 1);
Hal Canary1fcc4042016-11-30 17:07:59 -0500380 srcImage += pixmap.rowBytes();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000381 }
382
383 png_write_end(png_ptr, info_ptr);
384
385 /* clean up after the write, and free any memory allocated */
386 png_destroy_write_struct(&png_ptr, &info_ptr);
387 return true;
388}
389
Hal Canary1fcc4042016-11-30 17:07:59 -0500390#endif