Home | History | Annotate | Download | only in tests
      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 
     25 static 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 
     36 static 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 
     48 static void test_encode(skiatest::Reporter* r, SkEncodedImageFormat format) {
     49     SkBitmap bitmap;
     50     bool success = GetResourceAsBitmap("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 
     91 DEF_TEST(Encode, r) {
     92     test_encode(r, SkEncodedImageFormat::kJPEG);
     93     test_encode(r, SkEncodedImageFormat::kPNG);
     94 }
     95 
     96 static 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 
    116 static 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 
    133 DEF_TEST(Encode_JpegDownsample, r) {
    134     SkBitmap bitmap;
    135     bool success = GetResourceAsBitmap("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 
    174 static 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 
    180 static 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 
    245 DEF_TEST(Encode_PngOptions, r) {
    246     SkBitmap bitmap;
    247     bool success = GetResourceAsBitmap("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 
    288 DEF_TEST(Encode_WebpOptions, r) {
    289     SkBitmap bitmap;
    290     bool success = GetResourceAsBitmap("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