diff options
-rw-r--r-- | include/encode/SkPngEncoder.h | 8 | ||||
-rw-r--r-- | src/images/SkPngEncoder.cpp | 28 | ||||
-rw-r--r-- | tests/EncodeTest.cpp | 79 |
3 files changed, 115 insertions, 0 deletions
diff --git a/include/encode/SkPngEncoder.h b/include/encode/SkPngEncoder.h index 6de800f367..39e407e075 100644 --- a/include/encode/SkPngEncoder.h +++ b/include/encode/SkPngEncoder.h @@ -9,6 +9,7 @@ #define SkPngEncoder_DEFINED #include "SkEncoder.h" +#include "SkDataTable.h" class SkPngEncoderMgr; class SkWStream; @@ -58,6 +59,13 @@ public: * function and unpremultiply the input as is. */ SkTransferFunctionBehavior fUnpremulBehavior = SkTransferFunctionBehavior::kRespect; + + /** + * Represents comments in the tEXt ancillary chunk of the png. + * The 2i-th entry is the keyword for the i-th comment, + * and the (2i + 1)-th entry is the text for the i-th comment. + */ + sk_sp<SkDataTable> fComments; }; /** diff --git a/src/images/SkPngEncoder.cpp b/src/images/SkPngEncoder.cpp index d28657f008..cc925207b1 100644 --- a/src/images/SkPngEncoder.cpp +++ b/src/images/SkPngEncoder.cpp @@ -175,6 +175,34 @@ bool SkPngEncoderMgr::setHeader(const SkImageInfo& srcInfo, const SkPngEncoder:: int zlibLevel = SkTMin(SkTMax(0, options.fZLibLevel), 9); SkASSERT(zlibLevel == options.fZLibLevel); png_set_compression_level(fPngPtr, zlibLevel); + + // Set comments in tEXt chunk + const sk_sp<SkDataTable>& comments = options.fComments; + if (comments != nullptr) { + std::vector<png_text> png_texts(comments->count()); + std::vector<SkString> clippedKeys; + for (int i = 0; i < comments->count() / 2; ++i) { + const char* keyword; + const char* originalKeyword = comments->atStr(2 * i); + const char* text = comments->atStr(2 * i + 1); + if (strlen(originalKeyword) <= PNG_KEYWORD_MAX_LENGTH) { + keyword = originalKeyword; + } else { + SkDEBUGFAILF("PNG tEXt keyword should be no longer than %d.", + PNG_KEYWORD_MAX_LENGTH); + clippedKeys.emplace_back(originalKeyword, PNG_KEYWORD_MAX_LENGTH); + keyword = clippedKeys.back().c_str(); + } + // It seems safe to convert png_const_charp to png_charp for key/text, + // and we don't have to provide text_length and other fields as we're providing + // 0-terminated c_str with PNG_TEXT_COMPRESSION_NONE (no compression, no itxt). + png_texts[i].compression = PNG_TEXT_COMPRESSION_NONE; + png_texts[i].key = (png_charp)keyword; + png_texts[i].text = (png_charp)text; + } + png_set_text(fPngPtr, fInfoPtr, png_texts.data(), png_texts.size()); + } + return true; } diff --git a/tests/EncodeTest.cpp b/tests/EncodeTest.cpp index 4d0ade1ac5..6638c1e72b 100644 --- a/tests/EncodeTest.cpp +++ b/tests/EncodeTest.cpp @@ -16,6 +16,12 @@ #include "SkStream.h" #include "SkWebpEncoder.h" +#include "png.h" + +#include <algorithm> +#include <string> +#include <vector> + static bool encode(SkEncodedImageFormat format, SkWStream* dst, const SkPixmap& src) { switch (format) { case SkEncodedImageFormat::kJPEG: @@ -165,6 +171,77 @@ DEF_TEST(Encode_JpegDownsample, r) { REPORTER_ASSERT(r, almost_equals(bm1, bm2, 60)); } +static inline void pushComment( + std::vector<std::string>& comments, const char* keyword, const char* text) { + comments.push_back(keyword); + comments.push_back(text); +} + +static void testPngComments(const SkPixmap& src, SkPngEncoder::Options& options, + skiatest::Reporter* r) { + std::vector<std::string> commentStrings; + pushComment(commentStrings, "key", "text"); + pushComment(commentStrings, "test", "something"); + pushComment(commentStrings, "have some", "spaces in both"); + + std::string longKey(PNG_KEYWORD_MAX_LENGTH, 'x'); +#ifdef SK_DEBUG + commentStrings.push_back(longKey); +#else + // We call SkDEBUGFAILF it the key is too long so we'll only test this in release mode. + commentStrings.push_back(longKey + "x"); +#endif + commentStrings.push_back(""); + + std::vector<const char*> commentPointers; + std::vector<size_t> commentSizes; + for(auto& str : commentStrings) { + commentPointers.push_back(str.c_str()); + commentSizes.push_back(str.length() + 1); + } + + options.fComments = SkDataTable::MakeCopyArrays((void const *const *)commentPointers.data(), + commentSizes.data(), commentStrings.size()); + + + SkDynamicMemoryWStream dst; + bool success = SkPngEncoder::Encode(&dst, src, options); + REPORTER_ASSERT(r, success); + + std::vector<char> output(dst.bytesWritten()); + dst.copyTo(output.data()); + + // Each chunk is of the form length (4 bytes), chunk type (tEXt), data, + // checksum (4 bytes). Make sure we find all of them in the encoded + // results. + const char kExpected1[] = + "\x00\x00\x00\x08tEXtkey\x00text\x9e\xe7\x66\x51"; + const char kExpected2[] = + "\x00\x00\x00\x0etEXttest\x00something\x29\xba\xef\xac"; + const char kExpected3[] = + "\x00\x00\x00\x18tEXthave some\x00spaces in both\x8d\x69\x34\x2d"; + std::string longKeyRecord = "tEXt" + longKey; // A snippet of our long key comment + std::string tooLongRecord = "tExt" + longKey + "x"; // A snippet whose key is too long + + auto search1 = std::search(output.begin(), output.end(), + kExpected1, kExpected1 + sizeof(kExpected1)); + auto search2 = std::search(output.begin(), output.end(), + kExpected2, kExpected2 + sizeof(kExpected2)); + auto search3 = std::search(output.begin(), output.end(), + kExpected3, kExpected3 + sizeof(kExpected3)); + auto search4 = std::search(output.begin(), output.end(), + longKeyRecord.begin(), longKeyRecord.end()); + auto search5 = std::search(output.begin(), output.end(), + tooLongRecord.begin(), tooLongRecord.end()); + + REPORTER_ASSERT(r, search1 != output.end()); + REPORTER_ASSERT(r, search2 != output.end()); + REPORTER_ASSERT(r, search3 != output.end()); + REPORTER_ASSERT(r, search4 != output.end()); + REPORTER_ASSERT(r, search5 == output.end()); + // Comments test ends +} + DEF_TEST(Encode_PngOptions, r) { SkBitmap bitmap; bool success = GetResourceAsBitmap("mandrill_128.png", &bitmap); @@ -192,6 +269,8 @@ DEF_TEST(Encode_PngOptions, r) { success = SkPngEncoder::Encode(&dst2, src, options); REPORTER_ASSERT(r, success); + testPngComments(src, options, r); + sk_sp<SkData> data0 = dst0.detachAsData(); sk_sp<SkData> data1 = dst1.detachAsData(); sk_sp<SkData> data2 = dst2.detachAsData(); |