1/* 2 * Copyright 2017 Google Inc. 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 "Resources.h" 9#include "Test.h" 10 11#include "SkBitmap.h" 12#include "SkEncodedImageFormat.h" 13#include "SkImage.h" 14#include "SkJpegEncoder.h" 15#include "SkPngEncoder.h" 16#include "SkStream.h" 17#include "SkWebpEncoder.h" 18 19#include "png.h" 20 21#include <algorithm> 22#include <string> 23#include <vector> 24 25static bool encode(SkEncodedImageFormat format, SkWStream* dst, const SkPixmap& src) { 26 switch (format) { 27 case SkEncodedImageFormat::kJPEG: 28 return SkJpegEncoder::Encode(dst, src, SkJpegEncoder::Options()); 29 case SkEncodedImageFormat::kPNG: 30 return SkPngEncoder::Encode(dst, src, SkPngEncoder::Options()); 31 default: 32 return false; 33 } 34} 35 36static std::unique_ptr<SkEncoder> make(SkEncodedImageFormat format, SkWStream* dst, 37 const SkPixmap& src) { 38 switch (format) { 39 case SkEncodedImageFormat::kJPEG: 40 return SkJpegEncoder::Make(dst, src, SkJpegEncoder::Options()); 41 case SkEncodedImageFormat::kPNG: 42 return SkPngEncoder::Make(dst, src, SkPngEncoder::Options()); 43 default: 44 return nullptr; 45 } 46} 47 48static void test_encode(skiatest::Reporter* r, SkEncodedImageFormat format) { 49 SkBitmap bitmap; 50 bool success = GetResourceAsBitmap("images/mandrill_128.png", &bitmap); 51 if (!success) { 52 return; 53 } 54 55 SkPixmap src; 56 success = bitmap.peekPixels(&src); 57 REPORTER_ASSERT(r, success); 58 if (!success) { 59 return; 60 } 61 62 SkDynamicMemoryWStream dst0, dst1, dst2, dst3; 63 success = encode(format, &dst0, src); 64 REPORTER_ASSERT(r, success); 65 66 auto encoder1 = make(format, &dst1, src); 67 for (int i = 0; i < src.height(); i++) { 68 success = encoder1->encodeRows(1); 69 REPORTER_ASSERT(r, success); 70 } 71 72 auto encoder2 = make(format, &dst2, src); 73 for (int i = 0; i < src.height(); i+=3) { 74 success = encoder2->encodeRows(3); 75 REPORTER_ASSERT(r, success); 76 } 77 78 auto encoder3 = make(format, &dst3, src); 79 success = encoder3->encodeRows(200); 80 REPORTER_ASSERT(r, success); 81 82 sk_sp<SkData> data0 = dst0.detachAsData(); 83 sk_sp<SkData> data1 = dst1.detachAsData(); 84 sk_sp<SkData> data2 = dst2.detachAsData(); 85 sk_sp<SkData> data3 = dst3.detachAsData(); 86 REPORTER_ASSERT(r, data0->equals(data1.get())); 87 REPORTER_ASSERT(r, data0->equals(data2.get())); 88 REPORTER_ASSERT(r, data0->equals(data3.get())); 89} 90 91DEF_TEST(Encode, r) { 92 test_encode(r, SkEncodedImageFormat::kJPEG); 93 test_encode(r, SkEncodedImageFormat::kPNG); 94} 95 96static inline bool almost_equals(SkPMColor a, SkPMColor b, int tolerance) { 97 if (SkTAbs((int)SkGetPackedR32(a) - (int)SkGetPackedR32(b)) > tolerance) { 98 return false; 99 } 100 101 if (SkTAbs((int)SkGetPackedG32(a) - (int)SkGetPackedG32(b)) > tolerance) { 102 return false; 103 } 104 105 if (SkTAbs((int)SkGetPackedB32(a) - (int)SkGetPackedB32(b)) > tolerance) { 106 return false; 107 } 108 109 if (SkTAbs((int)SkGetPackedA32(a) - (int)SkGetPackedA32(b)) > tolerance) { 110 return false; 111 } 112 113 return true; 114} 115 116static inline bool almost_equals(const SkBitmap& a, const SkBitmap& b, int tolerance) { 117 if (a.info() != b.info()) { 118 return false; 119 } 120 121 SkASSERT(kN32_SkColorType == a.colorType()); 122 for (int y = 0; y < a.height(); y++) { 123 for (int x = 0; x < a.width(); x++) { 124 if (!almost_equals(*a.getAddr32(x, y), *b.getAddr32(x, y), tolerance)) { 125 return false; 126 } 127 } 128 } 129 130 return true; 131} 132 133DEF_TEST(Encode_JpegDownsample, r) { 134 SkBitmap bitmap; 135 bool success = GetResourceAsBitmap("images/mandrill_128.png", &bitmap); 136 if (!success) { 137 return; 138 } 139 140 SkPixmap src; 141 success = bitmap.peekPixels(&src); 142 REPORTER_ASSERT(r, success); 143 if (!success) { 144 return; 145 } 146 147 SkDynamicMemoryWStream dst0, dst1, dst2; 148 SkJpegEncoder::Options options; 149 success = SkJpegEncoder::Encode(&dst0, src, options); 150 REPORTER_ASSERT(r, success); 151 152 options.fDownsample = SkJpegEncoder::Downsample::k422; 153 success = SkJpegEncoder::Encode(&dst1, src, options); 154 REPORTER_ASSERT(r, success); 155 156 options.fDownsample = SkJpegEncoder::Downsample::k444; 157 success = SkJpegEncoder::Encode(&dst2, src, options); 158 REPORTER_ASSERT(r, success); 159 160 sk_sp<SkData> data0 = dst0.detachAsData(); 161 sk_sp<SkData> data1 = dst1.detachAsData(); 162 sk_sp<SkData> data2 = dst2.detachAsData(); 163 REPORTER_ASSERT(r, data0->size() < data1->size()); 164 REPORTER_ASSERT(r, data1->size() < data2->size()); 165 166 SkBitmap bm0, bm1, bm2; 167 SkImage::MakeFromEncoded(data0)->asLegacyBitmap(&bm0, SkImage::kRO_LegacyBitmapMode); 168 SkImage::MakeFromEncoded(data1)->asLegacyBitmap(&bm1, SkImage::kRO_LegacyBitmapMode); 169 SkImage::MakeFromEncoded(data2)->asLegacyBitmap(&bm2, SkImage::kRO_LegacyBitmapMode); 170 REPORTER_ASSERT(r, almost_equals(bm0, bm1, 60)); 171 REPORTER_ASSERT(r, almost_equals(bm1, bm2, 60)); 172} 173 174static inline void pushComment( 175 std::vector<std::string>& comments, const char* keyword, const char* text) { 176 comments.push_back(keyword); 177 comments.push_back(text); 178} 179 180static void testPngComments(const SkPixmap& src, SkPngEncoder::Options& options, 181 skiatest::Reporter* r) { 182 std::vector<std::string> commentStrings; 183 pushComment(commentStrings, "key", "text"); 184 pushComment(commentStrings, "test", "something"); 185 pushComment(commentStrings, "have some", "spaces in both"); 186 187 std::string longKey(PNG_KEYWORD_MAX_LENGTH, 'x'); 188#ifdef SK_DEBUG 189 commentStrings.push_back(longKey); 190#else 191 // We call SkDEBUGFAILF it the key is too long so we'll only test this in release mode. 192 commentStrings.push_back(longKey + "x"); 193#endif 194 commentStrings.push_back(""); 195 196 std::vector<const char*> commentPointers; 197 std::vector<size_t> commentSizes; 198 for(auto& str : commentStrings) { 199 commentPointers.push_back(str.c_str()); 200 commentSizes.push_back(str.length() + 1); 201 } 202 203 options.fComments = SkDataTable::MakeCopyArrays((void const *const *)commentPointers.data(), 204 commentSizes.data(), commentStrings.size()); 205 206 207 SkDynamicMemoryWStream dst; 208 bool success = SkPngEncoder::Encode(&dst, src, options); 209 REPORTER_ASSERT(r, success); 210 211 std::vector<char> output(dst.bytesWritten()); 212 dst.copyTo(output.data()); 213 214 // Each chunk is of the form length (4 bytes), chunk type (tEXt), data, 215 // checksum (4 bytes). Make sure we find all of them in the encoded 216 // results. 217 const char kExpected1[] = 218 "\x00\x00\x00\x08tEXtkey\x00text\x9e\xe7\x66\x51"; 219 const char kExpected2[] = 220 "\x00\x00\x00\x0etEXttest\x00something\x29\xba\xef\xac"; 221 const char kExpected3[] = 222 "\x00\x00\x00\x18tEXthave some\x00spaces in both\x8d\x69\x34\x2d"; 223 std::string longKeyRecord = "tEXt" + longKey; // A snippet of our long key comment 224 std::string tooLongRecord = "tExt" + longKey + "x"; // A snippet whose key is too long 225 226 auto search1 = std::search(output.begin(), output.end(), 227 kExpected1, kExpected1 + sizeof(kExpected1)); 228 auto search2 = std::search(output.begin(), output.end(), 229 kExpected2, kExpected2 + sizeof(kExpected2)); 230 auto search3 = std::search(output.begin(), output.end(), 231 kExpected3, kExpected3 + sizeof(kExpected3)); 232 auto search4 = std::search(output.begin(), output.end(), 233 longKeyRecord.begin(), longKeyRecord.end()); 234 auto search5 = std::search(output.begin(), output.end(), 235 tooLongRecord.begin(), tooLongRecord.end()); 236 237 REPORTER_ASSERT(r, search1 != output.end()); 238 REPORTER_ASSERT(r, search2 != output.end()); 239 REPORTER_ASSERT(r, search3 != output.end()); 240 REPORTER_ASSERT(r, search4 != output.end()); 241 REPORTER_ASSERT(r, search5 == output.end()); 242 // Comments test ends 243} 244 245DEF_TEST(Encode_PngOptions, r) { 246 SkBitmap bitmap; 247 bool success = GetResourceAsBitmap("images/mandrill_128.png", &bitmap); 248 if (!success) { 249 return; 250 } 251 252 SkPixmap src; 253 success = bitmap.peekPixels(&src); 254 REPORTER_ASSERT(r, success); 255 if (!success) { 256 return; 257 } 258 259 SkDynamicMemoryWStream dst0, dst1, dst2; 260 SkPngEncoder::Options options; 261 success = SkPngEncoder::Encode(&dst0, src, options); 262 REPORTER_ASSERT(r, success); 263 264 options.fFilterFlags = SkPngEncoder::FilterFlag::kUp; 265 success = SkPngEncoder::Encode(&dst1, src, options); 266 REPORTER_ASSERT(r, success); 267 268 options.fZLibLevel = 3; 269 success = SkPngEncoder::Encode(&dst2, src, options); 270 REPORTER_ASSERT(r, success); 271 272 testPngComments(src, options, r); 273 274 sk_sp<SkData> data0 = dst0.detachAsData(); 275 sk_sp<SkData> data1 = dst1.detachAsData(); 276 sk_sp<SkData> data2 = dst2.detachAsData(); 277 REPORTER_ASSERT(r, data0->size() < data1->size()); 278 REPORTER_ASSERT(r, data1->size() < data2->size()); 279 280 SkBitmap bm0, bm1, bm2; 281 SkImage::MakeFromEncoded(data0)->asLegacyBitmap(&bm0, SkImage::kRO_LegacyBitmapMode); 282 SkImage::MakeFromEncoded(data1)->asLegacyBitmap(&bm1, SkImage::kRO_LegacyBitmapMode); 283 SkImage::MakeFromEncoded(data2)->asLegacyBitmap(&bm2, SkImage::kRO_LegacyBitmapMode); 284 REPORTER_ASSERT(r, almost_equals(bm0, bm1, 0)); 285 REPORTER_ASSERT(r, almost_equals(bm0, bm2, 0)); 286} 287 288DEF_TEST(Encode_WebpOptions, r) { 289 SkBitmap bitmap; 290 bool success = GetResourceAsBitmap("images/google_chrome.ico", &bitmap); 291 if (!success) { 292 return; 293 } 294 295 SkPixmap src; 296 success = bitmap.peekPixels(&src); 297 REPORTER_ASSERT(r, success); 298 if (!success) { 299 return; 300 } 301 302 SkDynamicMemoryWStream dst0, dst1, dst2, dst3; 303 SkWebpEncoder::Options options; 304 options.fCompression = SkWebpEncoder::Compression::kLossless; 305 options.fQuality = 0.0f; 306 success = SkWebpEncoder::Encode(&dst0, src, options); 307 REPORTER_ASSERT(r, success); 308 309 options.fQuality = 100.0f; 310 success = SkWebpEncoder::Encode(&dst1, src, options); 311 REPORTER_ASSERT(r, success); 312 313 options.fCompression = SkWebpEncoder::Compression::kLossy; 314 options.fQuality = 100.0f; 315 success = SkWebpEncoder::Encode(&dst2, src, options); 316 REPORTER_ASSERT(r, success); 317 318 options.fCompression = SkWebpEncoder::Compression::kLossy; 319 options.fQuality = 50.0f; 320 success = SkWebpEncoder::Encode(&dst3, src, options); 321 REPORTER_ASSERT(r, success); 322 323 sk_sp<SkData> data0 = dst0.detachAsData(); 324 sk_sp<SkData> data1 = dst1.detachAsData(); 325 sk_sp<SkData> data2 = dst2.detachAsData(); 326 sk_sp<SkData> data3 = dst3.detachAsData(); 327 REPORTER_ASSERT(r, data0->size() > data1->size()); 328 REPORTER_ASSERT(r, data1->size() > data2->size()); 329 REPORTER_ASSERT(r, data2->size() > data3->size()); 330 331 SkBitmap bm0, bm1, bm2, bm3; 332 SkImage::MakeFromEncoded(data0)->asLegacyBitmap(&bm0, SkImage::kRO_LegacyBitmapMode); 333 SkImage::MakeFromEncoded(data1)->asLegacyBitmap(&bm1, SkImage::kRO_LegacyBitmapMode); 334 SkImage::MakeFromEncoded(data2)->asLegacyBitmap(&bm2, SkImage::kRO_LegacyBitmapMode); 335 SkImage::MakeFromEncoded(data3)->asLegacyBitmap(&bm3, SkImage::kRO_LegacyBitmapMode); 336 REPORTER_ASSERT(r, almost_equals(bm0, bm1, 0)); 337 REPORTER_ASSERT(r, almost_equals(bm0, bm2, 90)); 338 REPORTER_ASSERT(r, almost_equals(bm2, bm3, 45)); 339} 340