• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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         if (comments->count() % 2 != 0) {
238             return false;
239         }
240 
241         std::vector<png_text> png_texts(comments->count());
242         std::vector<SkString> clippedKeys;
243         for (int i = 0; i < comments->count() / 2; ++i) {
244             const char* keyword;
245             const char* originalKeyword = comments->atStr(2 * i);
246             const char* text = comments->atStr(2 * i + 1);
247             if (strlen(originalKeyword) <= PNG_KEYWORD_MAX_LENGTH) {
248                 keyword = originalKeyword;
249             } else {
250                 SkDEBUGFAILF("PNG tEXt keyword should be no longer than %d.",
251                              PNG_KEYWORD_MAX_LENGTH);
252                 clippedKeys.emplace_back(originalKeyword, PNG_KEYWORD_MAX_LENGTH);
253                 keyword = clippedKeys.back().c_str();
254             }
255             // It seems safe to convert png_const_charp to png_charp for key/text,
256             // and we don't have to provide text_length and other fields as we're providing
257             // 0-terminated c_str with PNG_TEXT_COMPRESSION_NONE (no compression, no itxt).
258             png_texts[i].compression = PNG_TEXT_COMPRESSION_NONE;
259             png_texts[i].key = const_cast<png_charp>(keyword);
260             png_texts[i].text = const_cast<png_charp>(text);
261         }
262         png_set_text(fPngPtr, fInfoPtr, png_texts.data(), png_texts.size());
263     }
264 
265     return true;
266 }
267 
set_icc(png_structp png_ptr,png_infop info_ptr,const SkImageInfo & info,const skcms_ICCProfile * profile,const char * profile_description)268 static void set_icc(png_structp png_ptr,
269                     png_infop info_ptr,
270                     const SkImageInfo& info,
271                     const skcms_ICCProfile* profile,
272                     const char* profile_description) {
273     sk_sp<SkData> icc = icc_from_color_space(info, profile, profile_description);
274     if (!icc) {
275         return;
276     }
277 
278 #if PNG_LIBPNG_VER_MAJOR > 1 || (PNG_LIBPNG_VER_MAJOR == 1 && PNG_LIBPNG_VER_MINOR >= 5)
279     const char* name = "Skia";
280     png_const_bytep iccPtr = icc->bytes();
281 #else
282     SkString str("Skia");
283     char* name = str.data();
284     png_charp iccPtr = (png_charp)icc->writable_data();
285 #endif
286     png_set_iCCP(png_ptr, info_ptr, name, 0, iccPtr, icc->size());
287 }
288 
setColorSpace(const SkImageInfo & info,const SkPngEncoder::Options & options)289 bool SkPngEncoderMgr::setColorSpace(const SkImageInfo& info, const SkPngEncoder::Options& options) {
290     if (setjmp(png_jmpbuf(fPngPtr))) {
291         return false;
292     }
293 
294     if (info.colorSpace() && info.colorSpace()->isSRGB()) {
295         png_set_sRGB(fPngPtr, fInfoPtr, PNG_sRGB_INTENT_PERCEPTUAL);
296     } else {
297         set_icc(fPngPtr, fInfoPtr, info, options.fICCProfile, options.fICCProfileDescription);
298     }
299 
300     return true;
301 }
302 
setV0Gainmap(const SkPngEncoder::Options & options)303 bool SkPngEncoderMgr::setV0Gainmap(const SkPngEncoder::Options& options) {
304 #ifdef PNG_STORE_UNKNOWN_CHUNKS_SUPPORTED
305     if (setjmp(png_jmpbuf(fPngPtr))) {
306         return false;
307     }
308 
309     // We require some gainmap information.
310     if (!options.fGainmapInfo) {
311         return false;
312     }
313 
314     if (options.fGainmap) {
315         sk_sp<SkData> gainmapVersion = SkGainmapInfo::SerializeVersion();
316         SkDynamicMemoryWStream gainmapStream;
317 
318         // When we encode the gainmap, we need to remove the gainmap from its
319         // own encoding options, so that we don't recurse.
320         auto modifiedOptions = options;
321         modifiedOptions.fGainmap = nullptr;
322 
323         bool result = SkPngEncoder::Encode(&gainmapStream, *(options.fGainmap), modifiedOptions);
324         if (!result) {
325             return false;
326         }
327 
328         sk_sp<SkData> gainmapData = gainmapStream.detachAsData();
329 
330         // The base image contains chunks for both the gainmap versioning (for possible
331         // forward-compat, and as a cheap way to check a gainmap might exist) as
332         // well as the gainmap data.
333         std::array<png_unknown_chunk, 2> chunks;
334         auto& gmapChunk = chunks.at(0);
335         std::strcpy(reinterpret_cast<char*>(gmapChunk.name), "gmAP\0");
336         gmapChunk.data = reinterpret_cast<png_byte*>(gainmapVersion->writable_data());
337         gmapChunk.size = gainmapVersion->size();
338         gmapChunk.location = PNG_HAVE_IHDR;
339 
340         auto& gdatChunk = chunks.at(1);
341         std::strcpy(reinterpret_cast<char*>(gdatChunk.name), "gdAT\0");
342         gdatChunk.data = reinterpret_cast<png_byte*>(gainmapData->writable_data());
343         gdatChunk.size = gainmapData->size();
344         gdatChunk.location = PNG_HAVE_IHDR;
345 
346         png_set_keep_unknown_chunks(fPngPtr, PNG_HANDLE_CHUNK_ALWAYS,
347                                     (png_const_bytep)"gmAP\0gdAT\0", chunks.size());
348         png_set_unknown_chunks(fPngPtr, fInfoPtr, chunks.data(), chunks.size());
349     } else {
350         // If there is no gainmap provided for encoding, but we have info, then
351         // we're currently encoding the gainmap pixels, so we need to encode the
352         // gainmap metadata to interpret those pixels.
353         sk_sp<SkData> data = options.fGainmapInfo->serialize();
354         png_unknown_chunk chunk;
355         std::strcpy(reinterpret_cast<char*>(chunk.name), "gmAP\0");
356         chunk.data = reinterpret_cast<png_byte*>(data->writable_data());
357         chunk.size = data->size();
358         chunk.location = PNG_HAVE_IHDR;
359         png_set_keep_unknown_chunks(fPngPtr, PNG_HANDLE_CHUNK_ALWAYS,
360                                     (png_const_bytep)"gmAP\0", 1);
361         png_set_unknown_chunks(fPngPtr, fInfoPtr, &chunk, 1);
362     }
363 #endif
364     return true;
365 }
366 
writeInfo(const SkImageInfo & srcInfo)367 bool SkPngEncoderMgr::writeInfo(const SkImageInfo& srcInfo) {
368     if (setjmp(png_jmpbuf(fPngPtr))) {
369         return false;
370     }
371 
372     png_write_info(fPngPtr, fInfoPtr);
373     return true;
374 }
375 
SkPngEncoderImpl(TargetInfo targetInfo,std::unique_ptr<SkPngEncoderMgr> encoderMgr,const SkPixmap & src)376 SkPngEncoderImpl::SkPngEncoderImpl(TargetInfo targetInfo,
377                                    std::unique_ptr<SkPngEncoderMgr> encoderMgr,
378                                    const SkPixmap& src)
379         : SkPngEncoderBase(std::move(targetInfo), src), fEncoderMgr(std::move(encoderMgr)) {}
380 
~SkPngEncoderImpl()381 SkPngEncoderImpl::~SkPngEncoderImpl() {}
382 
onEncodeRow(SkSpan<const uint8_t> row)383 bool SkPngEncoderImpl::onEncodeRow(SkSpan<const uint8_t> row) {
384     if (setjmp(png_jmpbuf(fEncoderMgr->pngPtr()))) {
385         return false;
386     }
387 
388     // `png_bytep` is `uint8_t*` rather than `const uint8_t*`.
389     png_bytep rowPtr = const_cast<png_bytep>(row.data());
390 
391     png_write_rows(fEncoderMgr->pngPtr(), &rowPtr, 1);
392     return true;
393 }
394 
onFinishEncoding()395 bool SkPngEncoderImpl::onFinishEncoding() {
396     if (setjmp(png_jmpbuf(fEncoderMgr->pngPtr()))) {
397         return false;
398     }
399 
400     png_write_end(fEncoderMgr->pngPtr(), fEncoderMgr->infoPtr());
401     return true;
402 }
403 
404 namespace SkPngEncoder {
Make(SkWStream * dst,const SkPixmap & src,const Options & options)405 std::unique_ptr<SkEncoder> Make(SkWStream* dst, const SkPixmap& src, const Options& options) {
406     if (!SkPixmapIsValid(src)) {
407         return nullptr;
408     }
409 
410     std::unique_ptr<SkPngEncoderMgr> encoderMgr = SkPngEncoderMgr::Make(dst);
411     if (!encoderMgr) {
412         return nullptr;
413     }
414 
415     std::optional<SkPngEncoderBase::TargetInfo> targetInfo =
416             SkPngEncoderBase::getTargetInfo(src.info());
417     if (!targetInfo.has_value()) {
418         return nullptr;
419     }
420 
421     if (!encoderMgr->setHeader(targetInfo->fDstInfo, src.info(), options)) {
422         return nullptr;
423     }
424 
425     if (!encoderMgr->setColorSpace(src.info(), options)) {
426         return nullptr;
427     }
428 
429     if (options.fGainmapInfo && !encoderMgr->setV0Gainmap(options)) {
430         return nullptr;
431     }
432 
433     if (!encoderMgr->writeInfo(src.info())) {
434         return nullptr;
435     }
436 
437     return std::make_unique<SkPngEncoderImpl>(std::move(*targetInfo), std::move(encoderMgr), src);
438 }
439 
Encode(SkWStream * dst,const SkPixmap & src,const Options & options)440 bool Encode(SkWStream* dst, const SkPixmap& src, const Options& options) {
441     auto encoder = Make(dst, src, options);
442     return encoder.get() && encoder->encodeRows(src.height());
443 }
444 
Encode(GrDirectContext * ctx,const SkImage * img,const Options & options)445 sk_sp<SkData> Encode(GrDirectContext* ctx, const SkImage* img, const Options& options) {
446     if (!img) {
447         return nullptr;
448     }
449     SkBitmap bm;
450     if (!as_IB(img)->getROPixels(ctx, &bm)) {
451         return nullptr;
452     }
453     SkDynamicMemoryWStream stream;
454     if (Encode(&stream, bm.pixmap(), options)) {
455         return stream.detachAsData();
456     }
457     return nullptr;
458 }
459 
460 }  // namespace SkPngEncoder
461