1 /*
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
8 #include "SkImageEncoderPriv.h"
9
10 #ifdef SK_HAS_PNG_LIBRARY
11
12 #include "SkColorTable.h"
13 #include "SkImageEncoderFns.h"
14 #include "SkImageInfoPriv.h"
15 #include "SkStream.h"
16 #include "SkString.h"
17 #include "SkPngEncoder.h"
18
19 #include "png.h"
20
21 static_assert(PNG_FILTER_NONE == (int)SkPngEncoder::FilterFlag::kNone, "Skia libpng filter err.");
22 static_assert(PNG_FILTER_SUB == (int)SkPngEncoder::FilterFlag::kSub, "Skia libpng filter err.");
23 static_assert(PNG_FILTER_UP == (int)SkPngEncoder::FilterFlag::kUp, "Skia libpng filter err.");
24 static_assert(PNG_FILTER_AVG == (int)SkPngEncoder::FilterFlag::kAvg, "Skia libpng filter err.");
25 static_assert(PNG_FILTER_PAETH == (int)SkPngEncoder::FilterFlag::kPaeth, "Skia libpng filter err.");
26 static_assert(PNG_ALL_FILTERS == (int)SkPngEncoder::FilterFlag::kAll, "Skia libpng filter err.");
27
28 static constexpr bool kSuppressPngEncodeWarnings = true;
29
sk_error_fn(png_structp png_ptr,png_const_charp msg)30 static void sk_error_fn(png_structp png_ptr, png_const_charp msg) {
31 if (!kSuppressPngEncodeWarnings) {
32 SkDebugf("libpng encode error: %s\n", msg);
33 }
34
35 longjmp(png_jmpbuf(png_ptr), 1);
36 }
37
sk_write_fn(png_structp png_ptr,png_bytep data,png_size_t len)38 static void sk_write_fn(png_structp png_ptr, png_bytep data, png_size_t len) {
39 SkWStream* stream = (SkWStream*)png_get_io_ptr(png_ptr);
40 if (!stream->write(data, len)) {
41 png_error(png_ptr, "sk_write_fn cannot write to stream");
42 }
43 }
44
45 class SkPngEncoderMgr final : SkNoncopyable {
46 public:
47
48 /*
49 * Create the decode manager
50 * Does not take ownership of stream
51 */
52 static std::unique_ptr<SkPngEncoderMgr> Make(SkWStream* stream);
53
54 bool setHeader(const SkImageInfo& srcInfo, const SkPngEncoder::Options& options);
55 bool setColorSpace(const SkImageInfo& info);
56 bool writeInfo(const SkImageInfo& srcInfo);
57 void chooseProc(const SkImageInfo& srcInfo, SkTransferFunctionBehavior unpremulBehavior);
58
pngPtr()59 png_structp pngPtr() { return fPngPtr; }
infoPtr()60 png_infop infoPtr() { return fInfoPtr; }
pngBytesPerPixel() const61 int pngBytesPerPixel() const { return fPngBytesPerPixel; }
proc() const62 transform_scanline_proc proc() const { return fProc; }
63
~SkPngEncoderMgr()64 ~SkPngEncoderMgr() {
65 png_destroy_write_struct(&fPngPtr, &fInfoPtr);
66 }
67
68 private:
69
SkPngEncoderMgr(png_structp pngPtr,png_infop infoPtr)70 SkPngEncoderMgr(png_structp pngPtr, png_infop infoPtr)
71 : fPngPtr(pngPtr)
72 , fInfoPtr(infoPtr)
73 {}
74
75 png_structp fPngPtr;
76 png_infop fInfoPtr;
77 int fPngBytesPerPixel;
78 transform_scanline_proc fProc;
79 };
80
Make(SkWStream * stream)81 std::unique_ptr<SkPngEncoderMgr> SkPngEncoderMgr::Make(SkWStream* stream) {
82 png_structp pngPtr =
83 png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, sk_error_fn, nullptr);
84 if (!pngPtr) {
85 return nullptr;
86 }
87
88 png_infop infoPtr = png_create_info_struct(pngPtr);
89 if (!infoPtr) {
90 png_destroy_write_struct(&pngPtr, nullptr);
91 return nullptr;
92 }
93
94 png_set_write_fn(pngPtr, (void*)stream, sk_write_fn, nullptr);
95 return std::unique_ptr<SkPngEncoderMgr>(new SkPngEncoderMgr(pngPtr, infoPtr));
96 }
97
setHeader(const SkImageInfo & srcInfo,const SkPngEncoder::Options & options)98 bool SkPngEncoderMgr::setHeader(const SkImageInfo& srcInfo, const SkPngEncoder::Options& options) {
99 if (setjmp(png_jmpbuf(fPngPtr))) {
100 return false;
101 }
102
103 int pngColorType;
104 png_color_8 sigBit;
105 int bitDepth = 8;
106 switch (srcInfo.colorType()) {
107 case kRGBA_F16_SkColorType:
108 SkASSERT(srcInfo.colorSpace() && srcInfo.colorSpace()->gammaIsLinear());
109 sigBit.red = 16;
110 sigBit.green = 16;
111 sigBit.blue = 16;
112 sigBit.alpha = 16;
113 bitDepth = 16;
114 pngColorType = srcInfo.isOpaque() ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA;
115 fPngBytesPerPixel = 8;
116 break;
117 case kGray_8_SkColorType:
118 sigBit.gray = 8;
119 pngColorType = PNG_COLOR_TYPE_GRAY;
120 fPngBytesPerPixel = 1;
121 SkASSERT(srcInfo.isOpaque());
122 break;
123 case kRGBA_8888_SkColorType:
124 case kBGRA_8888_SkColorType:
125 sigBit.red = 8;
126 sigBit.green = 8;
127 sigBit.blue = 8;
128 sigBit.alpha = 8;
129 pngColorType = srcInfo.isOpaque() ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA;
130 fPngBytesPerPixel = srcInfo.isOpaque() ? 3 : 4;
131 break;
132 case kARGB_4444_SkColorType:
133 if (kUnpremul_SkAlphaType == srcInfo.alphaType()) {
134 return false;
135 }
136
137 sigBit.red = 4;
138 sigBit.green = 4;
139 sigBit.blue = 4;
140 sigBit.alpha = 4;
141 pngColorType = srcInfo.isOpaque() ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA;
142 fPngBytesPerPixel = srcInfo.isOpaque() ? 3 : 4;
143 break;
144 case kRGB_565_SkColorType:
145 sigBit.red = 5;
146 sigBit.green = 6;
147 sigBit.blue = 5;
148 pngColorType = PNG_COLOR_TYPE_RGB;
149 fPngBytesPerPixel = 3;
150 SkASSERT(srcInfo.isOpaque());
151 break;
152 default:
153 return false;
154 }
155
156 png_set_IHDR(fPngPtr, fInfoPtr, srcInfo.width(), srcInfo.height(),
157 bitDepth, pngColorType,
158 PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE,
159 PNG_FILTER_TYPE_BASE);
160 png_set_sBIT(fPngPtr, fInfoPtr, &sigBit);
161
162 int filters = (int)options.fFilterFlags & (int)SkPngEncoder::FilterFlag::kAll;
163 SkASSERT(filters == (int)options.fFilterFlags);
164 png_set_filter(fPngPtr, PNG_FILTER_TYPE_BASE, filters);
165
166 int zlibLevel = SkTMin(SkTMax(0, options.fZLibLevel), 9);
167 SkASSERT(zlibLevel == options.fZLibLevel);
168 png_set_compression_level(fPngPtr, zlibLevel);
169
170 // Set comments in tEXt chunk
171 const sk_sp<SkDataTable>& comments = options.fComments;
172 if (comments != nullptr) {
173 std::vector<png_text> png_texts(comments->count());
174 std::vector<SkString> clippedKeys;
175 for (int i = 0; i < comments->count() / 2; ++i) {
176 const char* keyword;
177 const char* originalKeyword = comments->atStr(2 * i);
178 const char* text = comments->atStr(2 * i + 1);
179 if (strlen(originalKeyword) <= PNG_KEYWORD_MAX_LENGTH) {
180 keyword = originalKeyword;
181 } else {
182 SkDEBUGFAILF("PNG tEXt keyword should be no longer than %d.",
183 PNG_KEYWORD_MAX_LENGTH);
184 clippedKeys.emplace_back(originalKeyword, PNG_KEYWORD_MAX_LENGTH);
185 keyword = clippedKeys.back().c_str();
186 }
187 // It seems safe to convert png_const_charp to png_charp for key/text,
188 // and we don't have to provide text_length and other fields as we're providing
189 // 0-terminated c_str with PNG_TEXT_COMPRESSION_NONE (no compression, no itxt).
190 png_texts[i].compression = PNG_TEXT_COMPRESSION_NONE;
191 png_texts[i].key = (png_charp)keyword;
192 png_texts[i].text = (png_charp)text;
193 }
194 png_set_text(fPngPtr, fInfoPtr, png_texts.data(), png_texts.size());
195 }
196
197 return true;
198 }
199
choose_proc(const SkImageInfo & info,SkTransferFunctionBehavior unpremulBehavior)200 static transform_scanline_proc choose_proc(const SkImageInfo& info,
201 SkTransferFunctionBehavior unpremulBehavior) {
202 const bool isSRGBTransferFn =
203 (SkTransferFunctionBehavior::kRespect == unpremulBehavior) && info.gammaCloseToSRGB();
204 switch (info.colorType()) {
205 case kRGBA_8888_SkColorType:
206 switch (info.alphaType()) {
207 case kOpaque_SkAlphaType:
208 return transform_scanline_RGBX;
209 case kUnpremul_SkAlphaType:
210 return transform_scanline_memcpy;
211 case kPremul_SkAlphaType:
212 return isSRGBTransferFn ? transform_scanline_srgbA :
213 transform_scanline_rgbA;
214 default:
215 SkASSERT(false);
216 return nullptr;
217 }
218 case kBGRA_8888_SkColorType:
219 switch (info.alphaType()) {
220 case kOpaque_SkAlphaType:
221 return transform_scanline_BGRX;
222 case kUnpremul_SkAlphaType:
223 return transform_scanline_BGRA;
224 case kPremul_SkAlphaType:
225 return isSRGBTransferFn ? transform_scanline_sbgrA :
226 transform_scanline_bgrA;
227 default:
228 SkASSERT(false);
229 return nullptr;
230 }
231 case kRGB_565_SkColorType:
232 return transform_scanline_565;
233 case kARGB_4444_SkColorType:
234 switch (info.alphaType()) {
235 case kOpaque_SkAlphaType:
236 return transform_scanline_444;
237 case kPremul_SkAlphaType:
238 // 4444 is assumed to be legacy premul.
239 return transform_scanline_4444;
240 default:
241 SkASSERT(false);
242 return nullptr;
243 }
244 case kGray_8_SkColorType:
245 return transform_scanline_memcpy;
246 case kRGBA_F16_SkColorType:
247 switch (info.alphaType()) {
248 case kOpaque_SkAlphaType:
249 case kUnpremul_SkAlphaType:
250 return transform_scanline_F16;
251 case kPremul_SkAlphaType:
252 return transform_scanline_F16_premul;
253 default:
254 SkASSERT(false);
255 return nullptr;
256 }
257 default:
258 SkASSERT(false);
259 return nullptr;
260 }
261 }
262
set_icc(png_structp png_ptr,png_infop info_ptr,const SkImageInfo & info)263 static void set_icc(png_structp png_ptr, png_infop info_ptr, const SkImageInfo& info) {
264 sk_sp<SkData> icc = icc_from_color_space(info);
265 if (!icc) {
266 return;
267 }
268
269 #if PNG_LIBPNG_VER_MAJOR > 1 || (PNG_LIBPNG_VER_MAJOR == 1 && PNG_LIBPNG_VER_MINOR >= 5)
270 const char* name = "Skia";
271 png_const_bytep iccPtr = icc->bytes();
272 #else
273 SkString str("Skia");
274 char* name = str.writable_str();
275 png_charp iccPtr = (png_charp) icc->writable_data();
276 #endif
277 png_set_iCCP(png_ptr, info_ptr, name, 0, iccPtr, icc->size());
278 }
279
setColorSpace(const SkImageInfo & info)280 bool SkPngEncoderMgr::setColorSpace(const SkImageInfo& info) {
281 if (setjmp(png_jmpbuf(fPngPtr))) {
282 return false;
283 }
284
285 if (info.colorSpace() && info.colorSpace()->isSRGB()) {
286 png_set_sRGB(fPngPtr, fInfoPtr, PNG_sRGB_INTENT_PERCEPTUAL);
287 } else {
288 set_icc(fPngPtr, fInfoPtr, info);
289 }
290
291 return true;
292 }
293
writeInfo(const SkImageInfo & srcInfo)294 bool SkPngEncoderMgr::writeInfo(const SkImageInfo& srcInfo) {
295 if (setjmp(png_jmpbuf(fPngPtr))) {
296 return false;
297 }
298
299 png_write_info(fPngPtr, fInfoPtr);
300 if (kRGBA_F16_SkColorType == srcInfo.colorType() &&
301 kOpaque_SkAlphaType == srcInfo.alphaType())
302 {
303 // For kOpaque, kRGBA_F16, we will keep the row as RGBA and tell libpng
304 // to skip the alpha channel.
305 png_set_filler(fPngPtr, 0, PNG_FILLER_AFTER);
306 }
307
308 return true;
309 }
310
chooseProc(const SkImageInfo & srcInfo,SkTransferFunctionBehavior unpremulBehavior)311 void SkPngEncoderMgr::chooseProc(const SkImageInfo& srcInfo,
312 SkTransferFunctionBehavior unpremulBehavior) {
313 fProc = choose_proc(srcInfo, unpremulBehavior);
314 }
315
Make(SkWStream * dst,const SkPixmap & src,const Options & options)316 std::unique_ptr<SkEncoder> SkPngEncoder::Make(SkWStream* dst, const SkPixmap& src,
317 const Options& options) {
318 if (!SkPixmapIsValid(src, options.fUnpremulBehavior)) {
319 return nullptr;
320 }
321
322 std::unique_ptr<SkPngEncoderMgr> encoderMgr = SkPngEncoderMgr::Make(dst);
323 if (!encoderMgr) {
324 return nullptr;
325 }
326
327 if (!encoderMgr->setHeader(src.info(), options)) {
328 return nullptr;
329 }
330
331 if (!encoderMgr->setColorSpace(src.info())) {
332 return nullptr;
333 }
334
335 if (!encoderMgr->writeInfo(src.info())) {
336 return nullptr;
337 }
338
339 encoderMgr->chooseProc(src.info(), options.fUnpremulBehavior);
340
341 return std::unique_ptr<SkPngEncoder>(new SkPngEncoder(std::move(encoderMgr), src));
342 }
343
SkPngEncoder(std::unique_ptr<SkPngEncoderMgr> encoderMgr,const SkPixmap & src)344 SkPngEncoder::SkPngEncoder(std::unique_ptr<SkPngEncoderMgr> encoderMgr, const SkPixmap& src)
345 : INHERITED(src, encoderMgr->pngBytesPerPixel() * src.width())
346 , fEncoderMgr(std::move(encoderMgr))
347 {}
348
~SkPngEncoder()349 SkPngEncoder::~SkPngEncoder() {}
350
onEncodeRows(int numRows)351 bool SkPngEncoder::onEncodeRows(int numRows) {
352 if (setjmp(png_jmpbuf(fEncoderMgr->pngPtr()))) {
353 return false;
354 }
355
356 const void* srcRow = fSrc.addr(0, fCurrRow);
357 for (int y = 0; y < numRows; y++) {
358 fEncoderMgr->proc()((char*) fStorage.get(), (const char*) srcRow, fSrc.width(),
359 SkColorTypeBytesPerPixel(fSrc.colorType()), nullptr);
360
361 png_bytep rowPtr = (png_bytep) fStorage.get();
362 png_write_rows(fEncoderMgr->pngPtr(), &rowPtr, 1);
363 srcRow = SkTAddOffset<const void>(srcRow, fSrc.rowBytes());
364 }
365
366 fCurrRow += numRows;
367 if (fCurrRow == fSrc.height()) {
368 png_write_end(fEncoderMgr->pngPtr(), fEncoderMgr->infoPtr());
369 }
370
371 return true;
372 }
373
Encode(SkWStream * dst,const SkPixmap & src,const Options & options)374 bool SkPngEncoder::Encode(SkWStream* dst, const SkPixmap& src, const Options& options) {
375 auto encoder = SkPngEncoder::Make(dst, src, options);
376 return encoder.get() && encoder->encodeRows(src.height());
377 }
378
379 #endif
380