aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--include/encode/SkPngEncoder.h8
-rw-r--r--src/images/SkPngEncoder.cpp28
-rw-r--r--tests/EncodeTest.cpp79
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();