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 "src/encode/SkPngEncoderImpl.h"
9
10 #include <optional>
11
12 #include "include/core/SkBitmap.h"
13 #include "include/core/SkColorSpace.h"
14 #include "include/core/SkColorType.h"
15 #include "include/core/SkData.h"
16 #include "include/core/SkDataTable.h"
17 #include "include/core/SkImageInfo.h"
18 #include "include/core/SkPixmap.h"
19 #include "include/core/SkRefCnt.h"
20 #include "include/core/SkSpan.h"
21 #include "include/core/SkStream.h"
22 #include "include/core/SkString.h"
23 #include "include/encode/SkEncoder.h"
24 #include "include/encode/SkPngEncoder.h"
25 #include "include/private/SkEncodedInfo.h"
26 #include "include/private/SkGainmapInfo.h"
27 #include "include/private/base/SkAssert.h"
28 #include "include/private/base/SkDebug.h"
29 #include "include/private/base/SkNoncopyable.h"
30 #include "modules/skcms/skcms.h"
31 #include "src/codec/SkPngPriv.h"
32 #include "src/encode/SkImageEncoderFns.h"
33 #include "src/encode/SkImageEncoderPriv.h"
34 #include "src/encode/SkPngEncoderBase.h"
35 #include "src/image/SkImage_Base.h"
36
37 #include <algorithm>
38 #include <array>
39 #include <csetjmp>
40 #include <cstdint>
41 #include <cstring>
42 #include <memory>
43 #include <utility>
44 #include <vector>
45
46 #include <png.h>
47 #include <pngconf.h>
48
49 class GrDirectContext;
50 class SkImage;
51
52 static_assert(PNG_FILTER_NONE == (int)SkPngEncoder::FilterFlag::kNone, "Skia libpng filter err.");
53 static_assert(PNG_FILTER_SUB == (int)SkPngEncoder::FilterFlag::kSub, "Skia libpng filter err.");
54 static_assert(PNG_FILTER_UP == (int)SkPngEncoder::FilterFlag::kUp, "Skia libpng filter err.");
55 static_assert(PNG_FILTER_AVG == (int)SkPngEncoder::FilterFlag::kAvg, "Skia libpng filter err.");
56 static_assert(PNG_FILTER_PAETH == (int)SkPngEncoder::FilterFlag::kPaeth, "Skia libpng filter err.");
57 static_assert(PNG_ALL_FILTERS == (int)SkPngEncoder::FilterFlag::kAll, "Skia libpng filter err.");
58
59 static constexpr bool kSuppressPngEncodeWarnings = true;
60
sk_error_fn(png_structp png_ptr,png_const_charp msg)61 static void sk_error_fn(png_structp png_ptr, png_const_charp msg) {
62 if (!kSuppressPngEncodeWarnings) {
63 SkDebugf("libpng encode error: %s\n", msg);
64 }
65
66 longjmp(png_jmpbuf(png_ptr), 1);
67 }
68
sk_write_fn(png_structp png_ptr,png_bytep data,png_size_t len)69 static void sk_write_fn(png_structp png_ptr, png_bytep data, png_size_t len) {
70 SkWStream* stream = (SkWStream*)png_get_io_ptr(png_ptr);
71 if (!stream->write(data, len)) {
72 png_error(png_ptr, "sk_write_fn cannot write to stream");
73 }
74 }
75
76 class SkPngEncoderMgr final : SkNoncopyable {
77 public:
78 /*
79 * Create the decode manager
80 * Does not take ownership of stream
81 */
82 static std::unique_ptr<SkPngEncoderMgr> Make(SkWStream* stream);
83
84 bool setHeader(const SkEncodedInfo& dstInfo,
85 const SkImageInfo& srcInfo,
86 const SkPngEncoder::Options& options);
87 bool setColorSpace(const SkImageInfo& info, const SkPngEncoder::Options& options);
88 bool setV0Gainmap(const SkPngEncoder::Options& options);
89 bool writeInfo(const SkImageInfo& srcInfo);
90
pngPtr()91 png_structp pngPtr() { return fPngPtr; }
infoPtr()92 png_infop infoPtr() { return fInfoPtr; }
proc() const93 transform_scanline_proc proc() const { return fProc; }
94
~SkPngEncoderMgr()95 ~SkPngEncoderMgr() { png_destroy_write_struct(&fPngPtr, &fInfoPtr); }
96
97 private:
SkPngEncoderMgr(png_structp pngPtr,png_infop infoPtr)98 SkPngEncoderMgr(png_structp pngPtr, png_infop infoPtr) : fPngPtr(pngPtr), fInfoPtr(infoPtr) {}
99
100 png_structp fPngPtr;
101 png_infop fInfoPtr;
102 transform_scanline_proc fProc = nullptr;
103 };
104
Make(SkWStream * stream)105 std::unique_ptr<SkPngEncoderMgr> SkPngEncoderMgr::Make(SkWStream* stream) {
106 png_structp pngPtr =
107 png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, sk_error_fn, nullptr);
108 if (!pngPtr) {
109 return nullptr;
110 }
111
112 png_infop infoPtr = png_create_info_struct(pngPtr);
113 if (!infoPtr) {
114 png_destroy_write_struct(&pngPtr, nullptr);
115 return nullptr;
116 }
117
118 png_set_write_fn(pngPtr, (void*)stream, sk_write_fn, nullptr);
119 return std::unique_ptr<SkPngEncoderMgr>(new SkPngEncoderMgr(pngPtr, infoPtr));
120 }
121
setHeader(const SkEncodedInfo & dstInfo,const SkImageInfo & srcInfo,const SkPngEncoder::Options & options)122 bool SkPngEncoderMgr::setHeader(const SkEncodedInfo& dstInfo,
123 const SkImageInfo& srcInfo,
124 const SkPngEncoder::Options& options) {
125 if (setjmp(png_jmpbuf(fPngPtr))) {
126 return false;
127 }
128
129 int pngColorType;
130 switch (dstInfo.color()) {
131 case SkEncodedInfo::kRGB_Color:
132 pngColorType = PNG_COLOR_TYPE_RGB;
133 break;
134 case SkEncodedInfo::kRGBA_Color:
135 pngColorType = PNG_COLOR_TYPE_RGB_ALPHA;
136 break;
137 case SkEncodedInfo::kGray_Color:
138 pngColorType = PNG_COLOR_TYPE_GRAY;
139 break;
140 case SkEncodedInfo::kGrayAlpha_Color:
141 pngColorType = PNG_COLOR_TYPE_GRAY_ALPHA;
142 break;
143 default:
144 SkDEBUGFAIL("`getTargetInfo` returned unexpected `SkEncodedInfo::Color`");
145 return false;
146 }
147
148 png_color_8 sigBit;
149 switch (srcInfo.colorType()) {
150 case kRGBA_F16Norm_SkColorType:
151 case kRGBA_F16_SkColorType:
152 case kRGBA_F32_SkColorType:
153 sigBit.red = 16;
154 sigBit.green = 16;
155 sigBit.blue = 16;
156 sigBit.alpha = 16;
157 break;
158 case kRGB_F16F16F16x_SkColorType:
159 sigBit.red = 16;
160 sigBit.green = 16;
161 sigBit.blue = 16;
162 break;
163 case kGray_8_SkColorType:
164 sigBit.gray = 8;
165 break;
166 case kRGBA_8888_SkColorType:
167 case kBGRA_8888_SkColorType:
168 sigBit.red = 8;
169 sigBit.green = 8;
170 sigBit.blue = 8;
171 sigBit.alpha = 8;
172 break;
173 case kRGB_888x_SkColorType:
174 sigBit.red = 8;
175 sigBit.green = 8;
176 sigBit.blue = 8;
177 break;
178 case kARGB_4444_SkColorType:
179 sigBit.red = 4;
180 sigBit.green = 4;
181 sigBit.blue = 4;
182 sigBit.alpha = 4;
183 break;
184 case kRGB_565_SkColorType:
185 sigBit.red = 5;
186 sigBit.green = 6;
187 sigBit.blue = 5;
188 break;
189 case kAlpha_8_SkColorType: // store as gray+alpha, but ignore gray
190 sigBit.gray = kGraySigBit_GrayAlphaIsJustAlpha;
191 sigBit.alpha = 8;
192 break;
193 case kRGBA_1010102_SkColorType:
194 sigBit.red = 10;
195 sigBit.green = 10;
196 sigBit.blue = 10;
197 sigBit.alpha = 2;
198 break;
199 case kBGR_101010x_XR_SkColorType:
200 case kRGB_101010x_SkColorType:
201 sigBit.red = 10;
202 sigBit.green = 10;
203 sigBit.blue = 10;
204 break;
205 case kBGRA_10101010_XR_SkColorType:
206 sigBit.red = 10;
207 sigBit.green = 10;
208 sigBit.blue = 10;
209 sigBit.alpha = 10;
210 break;
211 default:
212 return false;
213 }
214
215 png_set_IHDR(fPngPtr,
216 fInfoPtr,
217 srcInfo.width(),
218 srcInfo.height(),
219 dstInfo.bitsPerComponent(),
220 pngColorType,
221 PNG_INTERLACE_NONE,
222 PNG_COMPRESSION_TYPE_BASE,
223 PNG_FILTER_TYPE_BASE);
224 png_set_sBIT(fPngPtr, fInfoPtr, &sigBit);
225
226 int filters = (int)options.fFilterFlags & (int)SkPngEncoder::FilterFlag::kAll;
227 SkASSERT(filters == (int)options.fFilterFlags);
228 png_set_filter(fPngPtr, PNG_FILTER_TYPE_BASE, filters);
229
230 int zlibLevel = std::min(std::max(0, options.fZLibLevel), 9);
231 SkASSERT(zlibLevel == options.fZLibLevel);
232 png_set_compression_level(fPngPtr, zlibLevel);
233
234 // Set comments in tEXt chunk
235 const sk_sp<SkDataTable>& comments = options.fComments;
236 if (comments != nullptr) {
237 std::vector<png_text> png_texts(comments->count());
238 std::vector<SkString> clippedKeys;
239 for (int i = 0; i < comments->count() / 2; ++i) {
240 const char* keyword;
241 const char* originalKeyword = comments->atStr(2 * i);
242 const char* text = comments->atStr(2 * i + 1);
243 if (strlen(originalKeyword) <= PNG_KEYWORD_MAX_LENGTH) {
244 keyword = originalKeyword;
245 } else {
246 SkDEBUGFAILF("PNG tEXt keyword should be no longer than %d.",
247 PNG_KEYWORD_MAX_LENGTH);
248 clippedKeys.emplace_back(originalKeyword, PNG_KEYWORD_MAX_LENGTH);
249 keyword = clippedKeys.back().c_str();
250 }
251 // It seems safe to convert png_const_charp to png_charp for key/text,
252 // and we don't have to provide text_length and other fields as we're providing
253 // 0-terminated c_str with PNG_TEXT_COMPRESSION_NONE (no compression, no itxt).
254 png_texts[i].compression = PNG_TEXT_COMPRESSION_NONE;
255 png_texts[i].key = const_cast<png_charp>(keyword);
256 png_texts[i].text = const_cast<png_charp>(text);
257 }
258 png_set_text(fPngPtr, fInfoPtr, png_texts.data(), png_texts.size());
259 }
260
261 return true;
262 }
263
set_icc(png_structp png_ptr,png_infop info_ptr,const SkImageInfo & info,const skcms_ICCProfile * profile,const char * profile_description)264 static void set_icc(png_structp png_ptr,
265 png_infop info_ptr,
266 const SkImageInfo& info,
267 const skcms_ICCProfile* profile,
268 const char* profile_description) {
269 sk_sp<SkData> icc = icc_from_color_space(info, profile, profile_description);
270 if (!icc) {
271 return;
272 }
273
274 #if PNG_LIBPNG_VER_MAJOR > 1 || (PNG_LIBPNG_VER_MAJOR == 1 && PNG_LIBPNG_VER_MINOR >= 5)
275 const char* name = "Skia";
276 png_const_bytep iccPtr = icc->bytes();
277 #else
278 SkString str("Skia");
279 char* name = str.data();
280 png_charp iccPtr = (png_charp)icc->writable_data();
281 #endif
282 png_set_iCCP(png_ptr, info_ptr, name, 0, iccPtr, icc->size());
283 }
284
setColorSpace(const SkImageInfo & info,const SkPngEncoder::Options & options)285 bool SkPngEncoderMgr::setColorSpace(const SkImageInfo& info, const SkPngEncoder::Options& options) {
286 if (setjmp(png_jmpbuf(fPngPtr))) {
287 return false;
288 }
289
290 if (info.colorSpace() && info.colorSpace()->isSRGB()) {
291 png_set_sRGB(fPngPtr, fInfoPtr, PNG_sRGB_INTENT_PERCEPTUAL);
292 } else {
293 set_icc(fPngPtr, fInfoPtr, info, options.fICCProfile, options.fICCProfileDescription);
294 }
295
296 return true;
297 }
298
setV0Gainmap(const SkPngEncoder::Options & options)299 bool SkPngEncoderMgr::setV0Gainmap(const SkPngEncoder::Options& options) {
300 #ifdef PNG_STORE_UNKNOWN_CHUNKS_SUPPORTED
301 if (setjmp(png_jmpbuf(fPngPtr))) {
302 return false;
303 }
304
305 // We require some gainmap information.
306 if (!options.fGainmapInfo) {
307 return false;
308 }
309
310 if (options.fGainmap) {
311 sk_sp<SkData> gainmapVersion = SkGainmapInfo::SerializeVersion();
312 SkDynamicMemoryWStream gainmapStream;
313
314 // When we encode the gainmap, we need to remove the gainmap from its
315 // own encoding options, so that we don't recurse.
316 auto modifiedOptions = options;
317 modifiedOptions.fGainmap = nullptr;
318
319 bool result = SkPngEncoder::Encode(&gainmapStream, *(options.fGainmap), modifiedOptions);
320 if (!result) {
321 return false;
322 }
323
324 sk_sp<SkData> gainmapData = gainmapStream.detachAsData();
325
326 // The base image contains chunks for both the gainmap versioning (for possible
327 // forward-compat, and as a cheap way to check a gainmap might exist) as
328 // well as the gainmap data.
329 std::array<png_unknown_chunk, 2> chunks;
330 auto& gmapChunk = chunks.at(0);
331 std::strcpy(reinterpret_cast<char*>(gmapChunk.name), "gmAP\0");
332 gmapChunk.data = reinterpret_cast<png_byte*>(gainmapVersion->writable_data());
333 gmapChunk.size = gainmapVersion->size();
334 gmapChunk.location = PNG_HAVE_IHDR;
335
336 auto& gdatChunk = chunks.at(1);
337 std::strcpy(reinterpret_cast<char*>(gdatChunk.name), "gdAT\0");
338 gdatChunk.data = reinterpret_cast<png_byte*>(gainmapData->writable_data());
339 gdatChunk.size = gainmapData->size();
340 gdatChunk.location = PNG_HAVE_IHDR;
341
342 png_set_keep_unknown_chunks(fPngPtr, PNG_HANDLE_CHUNK_ALWAYS,
343 (png_const_bytep)"gmAP\0gdAT\0", chunks.size());
344 png_set_unknown_chunks(fPngPtr, fInfoPtr, chunks.data(), chunks.size());
345 } else {
346 // If there is no gainmap provided for encoding, but we have info, then
347 // we're currently encoding the gainmap pixels, so we need to encode the
348 // gainmap metadata to interpret those pixels.
349 sk_sp<SkData> data = options.fGainmapInfo->serialize();
350 png_unknown_chunk chunk;
351 std::strcpy(reinterpret_cast<char*>(chunk.name), "gmAP\0");
352 chunk.data = reinterpret_cast<png_byte*>(data->writable_data());
353 chunk.size = data->size();
354 chunk.location = PNG_HAVE_IHDR;
355 png_set_keep_unknown_chunks(fPngPtr, PNG_HANDLE_CHUNK_ALWAYS,
356 (png_const_bytep)"gmAP\0", 1);
357 png_set_unknown_chunks(fPngPtr, fInfoPtr, &chunk, 1);
358 }
359 #endif
360 return true;
361 }
362
writeInfo(const SkImageInfo & srcInfo)363 bool SkPngEncoderMgr::writeInfo(const SkImageInfo& srcInfo) {
364 if (setjmp(png_jmpbuf(fPngPtr))) {
365 return false;
366 }
367
368 png_write_info(fPngPtr, fInfoPtr);
369 return true;
370 }
371
SkPngEncoderImpl(TargetInfo targetInfo,std::unique_ptr<SkPngEncoderMgr> encoderMgr,const SkPixmap & src)372 SkPngEncoderImpl::SkPngEncoderImpl(TargetInfo targetInfo,
373 std::unique_ptr<SkPngEncoderMgr> encoderMgr,
374 const SkPixmap& src)
375 : SkPngEncoderBase(std::move(targetInfo), src), fEncoderMgr(std::move(encoderMgr)) {}
376
~SkPngEncoderImpl()377 SkPngEncoderImpl::~SkPngEncoderImpl() {}
378
onEncodeRow(SkSpan<const uint8_t> row)379 bool SkPngEncoderImpl::onEncodeRow(SkSpan<const uint8_t> row) {
380 if (setjmp(png_jmpbuf(fEncoderMgr->pngPtr()))) {
381 return false;
382 }
383
384 // `png_bytep` is `uint8_t*` rather than `const uint8_t*`.
385 png_bytep rowPtr = const_cast<png_bytep>(row.data());
386
387 png_write_rows(fEncoderMgr->pngPtr(), &rowPtr, 1);
388 return true;
389 }
390
onFinishEncoding()391 bool SkPngEncoderImpl::onFinishEncoding() {
392 if (setjmp(png_jmpbuf(fEncoderMgr->pngPtr()))) {
393 return false;
394 }
395
396 png_write_end(fEncoderMgr->pngPtr(), fEncoderMgr->infoPtr());
397 return true;
398 }
399
400 namespace SkPngEncoder {
Make(SkWStream * dst,const SkPixmap & src,const Options & options)401 std::unique_ptr<SkEncoder> Make(SkWStream* dst, const SkPixmap& src, const Options& options) {
402 if (!SkPixmapIsValid(src)) {
403 return nullptr;
404 }
405
406 std::unique_ptr<SkPngEncoderMgr> encoderMgr = SkPngEncoderMgr::Make(dst);
407 if (!encoderMgr) {
408 return nullptr;
409 }
410
411 std::optional<SkPngEncoderBase::TargetInfo> targetInfo =
412 SkPngEncoderBase::getTargetInfo(src.info());
413 if (!targetInfo.has_value()) {
414 return nullptr;
415 }
416
417 if (!encoderMgr->setHeader(targetInfo->fDstInfo, src.info(), options)) {
418 return nullptr;
419 }
420
421 if (!encoderMgr->setColorSpace(src.info(), options)) {
422 return nullptr;
423 }
424
425 if (options.fGainmapInfo && !encoderMgr->setV0Gainmap(options)) {
426 return nullptr;
427 }
428
429 if (!encoderMgr->writeInfo(src.info())) {
430 return nullptr;
431 }
432
433 return std::make_unique<SkPngEncoderImpl>(std::move(*targetInfo), std::move(encoderMgr), src);
434 }
435
Encode(SkWStream * dst,const SkPixmap & src,const Options & options)436 bool Encode(SkWStream* dst, const SkPixmap& src, const Options& options) {
437 auto encoder = Make(dst, src, options);
438 return encoder.get() && encoder->encodeRows(src.height());
439 }
440
Encode(GrDirectContext * ctx,const SkImage * img,const Options & options)441 sk_sp<SkData> Encode(GrDirectContext* ctx, const SkImage* img, const Options& options) {
442 if (!img) {
443 return nullptr;
444 }
445 SkBitmap bm;
446 if (!as_IB(img)->getROPixels(ctx, &bm)) {
447 return nullptr;
448 }
449 SkDynamicMemoryWStream stream;
450 if (Encode(&stream, bm.pixmap(), options)) {
451 return stream.detachAsData();
452 }
453 return nullptr;
454 }
455
456 } // namespace SkPngEncoder
457