xref: /aosp_15_r20/external/skia/src/encode/SkPngEncoderImpl.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
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