xref: /third_party/skia/tests/EncodeTest.cpp (revision cb93a386)
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 "tests/Test.h"
9#include "tools/Resources.h"
10
11#include "include/core/SkBitmap.h"
12#include "include/core/SkCanvas.h"
13#include "include/core/SkColorPriv.h"
14#include "include/core/SkEncodedImageFormat.h"
15#include "include/core/SkImage.h"
16#include "include/core/SkStream.h"
17#include "include/core/SkSurface.h"
18#include "include/encode/SkJpegEncoder.h"
19#include "include/encode/SkPngEncoder.h"
20#include "include/encode/SkWebpEncoder.h"
21#include "include/private/SkImageInfoPriv.h"
22
23#include "png.h"
24
25#include <algorithm>
26#include <string>
27#include <vector>
28
29// FIXME: Update the Google3 build's dependencies so it can run this test.
30#ifndef SK_BUILD_FOR_GOOGLE3
31#include "webp/decode.h"
32#endif
33
34static bool encode(SkEncodedImageFormat format, SkWStream* dst, const SkPixmap& src) {
35    switch (format) {
36        case SkEncodedImageFormat::kJPEG:
37            return SkJpegEncoder::Encode(dst, src, SkJpegEncoder::Options());
38        case SkEncodedImageFormat::kPNG:
39            return SkPngEncoder::Encode(dst, src, SkPngEncoder::Options());
40        default:
41            return false;
42    }
43}
44
45static std::unique_ptr<SkEncoder> make(SkEncodedImageFormat format, SkWStream* dst,
46                                       const SkPixmap& src) {
47    switch (format) {
48        case SkEncodedImageFormat::kJPEG:
49            return SkJpegEncoder::Make(dst, src, SkJpegEncoder::Options());
50        case SkEncodedImageFormat::kPNG:
51            return SkPngEncoder::Make(dst, src, SkPngEncoder::Options());
52        default:
53            return nullptr;
54    }
55}
56
57static void test_encode(skiatest::Reporter* r, SkEncodedImageFormat format) {
58    SkBitmap bitmap;
59    bool success = GetResourceAsBitmap("images/mandrill_128.png", &bitmap);
60    if (!success) {
61        return;
62    }
63
64    SkPixmap src;
65    success = bitmap.peekPixels(&src);
66    REPORTER_ASSERT(r, success);
67    if (!success) {
68        return;
69    }
70
71    SkDynamicMemoryWStream dst0, dst1, dst2, dst3;
72    success = encode(format, &dst0, src);
73    REPORTER_ASSERT(r, success);
74
75    auto encoder1 = make(format, &dst1, src);
76    for (int i = 0; i < src.height(); i++) {
77        success = encoder1->encodeRows(1);
78        REPORTER_ASSERT(r, success);
79    }
80
81    auto encoder2 = make(format, &dst2, src);
82    for (int i = 0; i < src.height(); i+=3) {
83        success = encoder2->encodeRows(3);
84        REPORTER_ASSERT(r, success);
85    }
86
87    auto encoder3 = make(format, &dst3, src);
88    success = encoder3->encodeRows(200);
89    REPORTER_ASSERT(r, success);
90
91    sk_sp<SkData> data0 = dst0.detachAsData();
92    sk_sp<SkData> data1 = dst1.detachAsData();
93    sk_sp<SkData> data2 = dst2.detachAsData();
94    sk_sp<SkData> data3 = dst3.detachAsData();
95    REPORTER_ASSERT(r, data0->equals(data1.get()));
96    REPORTER_ASSERT(r, data0->equals(data2.get()));
97    REPORTER_ASSERT(r, data0->equals(data3.get()));
98}
99
100DEF_TEST(Encode, r) {
101    test_encode(r, SkEncodedImageFormat::kJPEG);
102    test_encode(r, SkEncodedImageFormat::kPNG);
103}
104
105static inline bool almost_equals(SkPMColor a, SkPMColor b, int tolerance) {
106    if (SkTAbs((int)SkGetPackedR32(a) - (int)SkGetPackedR32(b)) > tolerance) {
107        return false;
108    }
109
110    if (SkTAbs((int)SkGetPackedG32(a) - (int)SkGetPackedG32(b)) > tolerance) {
111        return false;
112    }
113
114    if (SkTAbs((int)SkGetPackedB32(a) - (int)SkGetPackedB32(b)) > tolerance) {
115        return false;
116    }
117
118    if (SkTAbs((int)SkGetPackedA32(a) - (int)SkGetPackedA32(b)) > tolerance) {
119        return false;
120    }
121
122    return true;
123}
124
125static inline bool almost_equals(const SkBitmap& a, const SkBitmap& b, int tolerance) {
126    if (a.info() != b.info()) {
127        return false;
128    }
129
130    SkASSERT(kN32_SkColorType == a.colorType());
131    for (int y = 0; y < a.height(); y++) {
132        for (int x = 0; x < a.width(); x++) {
133            if (!almost_equals(*a.getAddr32(x, y), *b.getAddr32(x, y), tolerance)) {
134                return false;
135            }
136        }
137    }
138
139    return true;
140}
141
142DEF_TEST(Encode_JPG, r) {
143    auto image = GetResourceAsImage("images/mandrill_128.png");
144    if (!image) {
145        return;
146    }
147
148    for (auto ct : { kRGBA_8888_SkColorType,
149                     kBGRA_8888_SkColorType,
150                     kRGB_565_SkColorType,
151                     kARGB_4444_SkColorType,
152                     kGray_8_SkColorType,
153                     kRGBA_F16_SkColorType }) {
154        for (auto at : { kPremul_SkAlphaType, kUnpremul_SkAlphaType, kOpaque_SkAlphaType }) {
155            auto info = SkImageInfo::Make(image->width(), image->height(), ct, at);
156            auto surface = SkSurface::MakeRaster(info);
157            auto canvas = surface->getCanvas();
158            canvas->drawImage(image, 0, 0);
159
160            SkBitmap bm;
161            bm.allocPixels(info);
162            if (!surface->makeImageSnapshot()->readPixels(nullptr, bm.pixmap(), 0, 0)) {
163                ERRORF(r, "failed to readPixels! ct: %i\tat: %i\n", ct, at);
164                continue;
165            }
166            for (auto alphaOption : { SkJpegEncoder::AlphaOption::kIgnore,
167                                      SkJpegEncoder::AlphaOption::kBlendOnBlack }) {
168                SkJpegEncoder::Options opts;
169                opts.fAlphaOption = alphaOption;
170                SkNullWStream ignored;
171                if (!SkJpegEncoder::Encode(&ignored, bm.pixmap(), opts)) {
172                    REPORTER_ASSERT(r, ct == kARGB_4444_SkColorType
173                                    && alphaOption == SkJpegEncoder::AlphaOption::kBlendOnBlack);
174                }
175            }
176        }
177    }
178}
179
180DEF_TEST(Encode_JpegDownsample, r) {
181    SkBitmap bitmap;
182    bool success = GetResourceAsBitmap("images/mandrill_128.png", &bitmap);
183    if (!success) {
184        return;
185    }
186
187    SkPixmap src;
188    success = bitmap.peekPixels(&src);
189    REPORTER_ASSERT(r, success);
190    if (!success) {
191        return;
192    }
193
194    SkDynamicMemoryWStream dst0, dst1, dst2;
195    SkJpegEncoder::Options options;
196    success = SkJpegEncoder::Encode(&dst0, src, options);
197    REPORTER_ASSERT(r, success);
198
199    options.fDownsample = SkJpegEncoder::Downsample::k422;
200    success = SkJpegEncoder::Encode(&dst1, src, options);
201    REPORTER_ASSERT(r, success);
202
203    options.fDownsample = SkJpegEncoder::Downsample::k444;
204    success = SkJpegEncoder::Encode(&dst2, src, options);
205    REPORTER_ASSERT(r, success);
206
207    sk_sp<SkData> data0 = dst0.detachAsData();
208    sk_sp<SkData> data1 = dst1.detachAsData();
209    sk_sp<SkData> data2 = dst2.detachAsData();
210    REPORTER_ASSERT(r, data0->size() < data1->size());
211    REPORTER_ASSERT(r, data1->size() < data2->size());
212
213    SkBitmap bm0, bm1, bm2;
214    SkImage::MakeFromEncoded(data0)->asLegacyBitmap(&bm0);
215    SkImage::MakeFromEncoded(data1)->asLegacyBitmap(&bm1);
216    SkImage::MakeFromEncoded(data2)->asLegacyBitmap(&bm2);
217    REPORTER_ASSERT(r, almost_equals(bm0, bm1, 60));
218    REPORTER_ASSERT(r, almost_equals(bm1, bm2, 60));
219}
220
221static inline void pushComment(
222        std::vector<std::string>& comments, const char* keyword, const char* text) {
223    comments.push_back(keyword);
224    comments.push_back(text);
225}
226
227static void testPngComments(const SkPixmap& src, SkPngEncoder::Options& options,
228        skiatest::Reporter* r) {
229    std::vector<std::string> commentStrings;
230    pushComment(commentStrings, "key", "text");
231    pushComment(commentStrings, "test", "something");
232    pushComment(commentStrings, "have some", "spaces in both");
233
234    std::string longKey(PNG_KEYWORD_MAX_LENGTH, 'x');
235#ifdef SK_DEBUG
236    commentStrings.push_back(longKey);
237#else
238    // We call SkDEBUGFAILF it the key is too long so we'll only test this in release mode.
239    commentStrings.push_back(longKey + "x");
240#endif
241    commentStrings.push_back("");
242
243    std::vector<const char*> commentPointers;
244    std::vector<size_t> commentSizes;
245    for(auto& str : commentStrings) {
246        commentPointers.push_back(str.c_str());
247        commentSizes.push_back(str.length() + 1);
248    }
249
250    options.fComments = SkDataTable::MakeCopyArrays((void const *const *)commentPointers.data(),
251            commentSizes.data(), commentStrings.size());
252
253
254    SkDynamicMemoryWStream dst;
255    bool success = SkPngEncoder::Encode(&dst, src, options);
256    REPORTER_ASSERT(r, success);
257
258    std::vector<char> output(dst.bytesWritten());
259    dst.copyTo(output.data());
260
261    // Each chunk is of the form length (4 bytes), chunk type (tEXt), data,
262    // checksum (4 bytes).  Make sure we find all of them in the encoded
263    // results.
264    const char kExpected1[] =
265        "\x00\x00\x00\x08tEXtkey\x00text\x9e\xe7\x66\x51";
266    const char kExpected2[] =
267        "\x00\x00\x00\x0etEXttest\x00something\x29\xba\xef\xac";
268    const char kExpected3[] =
269        "\x00\x00\x00\x18tEXthave some\x00spaces in both\x8d\x69\x34\x2d";
270    std::string longKeyRecord = "tEXt" + longKey; // A snippet of our long key comment
271    std::string tooLongRecord = "tExt" + longKey + "x"; // A snippet whose key is too long
272
273    auto search1 = std::search(output.begin(), output.end(),
274            kExpected1, kExpected1 + sizeof(kExpected1));
275    auto search2 = std::search(output.begin(), output.end(),
276            kExpected2, kExpected2 + sizeof(kExpected2));
277    auto search3 = std::search(output.begin(), output.end(),
278            kExpected3, kExpected3 + sizeof(kExpected3));
279    auto search4 = std::search(output.begin(), output.end(),
280            longKeyRecord.begin(), longKeyRecord.end());
281    auto search5 = std::search(output.begin(), output.end(),
282            tooLongRecord.begin(), tooLongRecord.end());
283
284    REPORTER_ASSERT(r, search1 != output.end());
285    REPORTER_ASSERT(r, search2 != output.end());
286    REPORTER_ASSERT(r, search3 != output.end());
287    REPORTER_ASSERT(r, search4 != output.end());
288    REPORTER_ASSERT(r, search5 == output.end());
289    // Comments test ends
290}
291
292DEF_TEST(Encode_PngOptions, r) {
293    SkBitmap bitmap;
294    bool success = GetResourceAsBitmap("images/mandrill_128.png", &bitmap);
295    if (!success) {
296        return;
297    }
298
299    SkPixmap src;
300    success = bitmap.peekPixels(&src);
301    REPORTER_ASSERT(r, success);
302    if (!success) {
303        return;
304    }
305
306    SkDynamicMemoryWStream dst0, dst1, dst2;
307    SkPngEncoder::Options options;
308    success = SkPngEncoder::Encode(&dst0, src, options);
309    REPORTER_ASSERT(r, success);
310
311    options.fFilterFlags = SkPngEncoder::FilterFlag::kUp;
312    success = SkPngEncoder::Encode(&dst1, src, options);
313    REPORTER_ASSERT(r, success);
314
315    options.fZLibLevel = 3;
316    success = SkPngEncoder::Encode(&dst2, src, options);
317    REPORTER_ASSERT(r, success);
318
319    testPngComments(src, options, r);
320
321    sk_sp<SkData> data0 = dst0.detachAsData();
322    sk_sp<SkData> data1 = dst1.detachAsData();
323    sk_sp<SkData> data2 = dst2.detachAsData();
324    REPORTER_ASSERT(r, data0->size() < data1->size());
325    REPORTER_ASSERT(r, data1->size() < data2->size());
326
327    SkBitmap bm0, bm1, bm2;
328    SkImage::MakeFromEncoded(data0)->asLegacyBitmap(&bm0);
329    SkImage::MakeFromEncoded(data1)->asLegacyBitmap(&bm1);
330    SkImage::MakeFromEncoded(data2)->asLegacyBitmap(&bm2);
331    REPORTER_ASSERT(r, almost_equals(bm0, bm1, 0));
332    REPORTER_ASSERT(r, almost_equals(bm0, bm2, 0));
333}
334
335#ifndef SK_BUILD_FOR_GOOGLE3
336DEF_TEST(Encode_WebpQuality, r) {
337    SkBitmap bm;
338    bm.allocN32Pixels(100, 100);
339    bm.eraseColor(SK_ColorBLUE);
340
341    auto dataLossy    = SkEncodeBitmap(bm, SkEncodedImageFormat::kWEBP, 99);
342    auto dataLossLess = SkEncodeBitmap(bm, SkEncodedImageFormat::kWEBP, 100);
343
344    enum Format {
345        kMixed    = 0,
346        kLossy    = 1,
347        kLossless = 2,
348    };
349
350    auto test = [&r](const sk_sp<SkData>& data, Format expected) {
351        auto printFormat = [](int f) {
352            switch (f) {
353                case kMixed:    return "mixed";
354                case kLossy:    return "lossy";
355                case kLossless: return "lossless";
356                default:        return "unknown";
357            }
358        };
359
360        if (!data) {
361            ERRORF(r, "Failed to encode. Expected %s", printFormat(expected));
362            return;
363        }
364
365        WebPBitstreamFeatures features;
366        auto status = WebPGetFeatures(data->bytes(), data->size(), &features);
367        if (status != VP8_STATUS_OK) {
368            ERRORF(r, "Encode had an error %i. Expected %s", status, printFormat(expected));
369            return;
370        }
371
372        if (expected != features.format) {
373            ERRORF(r, "Expected %s encode, but got format %s", printFormat(expected),
374                                                               printFormat(features.format));
375        }
376    };
377
378    test(dataLossy,    kLossy);
379    test(dataLossLess, kLossless);
380}
381#endif
382
383DEF_TEST(Encode_WebpOptions, r) {
384    SkBitmap bitmap;
385    bool success = GetResourceAsBitmap("images/google_chrome.ico", &bitmap);
386    if (!success) {
387        return;
388    }
389
390    SkPixmap src;
391    success = bitmap.peekPixels(&src);
392    REPORTER_ASSERT(r, success);
393    if (!success) {
394        return;
395    }
396
397    SkDynamicMemoryWStream dst0, dst1, dst2, dst3;
398    SkWebpEncoder::Options options;
399    options.fCompression = SkWebpEncoder::Compression::kLossless;
400    options.fQuality = 0.0f;
401    success = SkWebpEncoder::Encode(&dst0, src, options);
402    REPORTER_ASSERT(r, success);
403
404    options.fQuality = 100.0f;
405    success = SkWebpEncoder::Encode(&dst1, src, options);
406    REPORTER_ASSERT(r, success);
407
408    options.fCompression = SkWebpEncoder::Compression::kLossy;
409    options.fQuality = 100.0f;
410    success = SkWebpEncoder::Encode(&dst2, src, options);
411    REPORTER_ASSERT(r, success);
412
413    options.fCompression = SkWebpEncoder::Compression::kLossy;
414    options.fQuality = 50.0f;
415    success = SkWebpEncoder::Encode(&dst3, src, options);
416    REPORTER_ASSERT(r, success);
417
418    sk_sp<SkData> data0 = dst0.detachAsData();
419    sk_sp<SkData> data1 = dst1.detachAsData();
420    sk_sp<SkData> data2 = dst2.detachAsData();
421    sk_sp<SkData> data3 = dst3.detachAsData();
422    REPORTER_ASSERT(r, data0->size() > data1->size());
423    REPORTER_ASSERT(r, data1->size() > data2->size());
424    REPORTER_ASSERT(r, data2->size() > data3->size());
425
426    SkBitmap bm0, bm1, bm2, bm3;
427    SkImage::MakeFromEncoded(data0)->asLegacyBitmap(&bm0);
428    SkImage::MakeFromEncoded(data1)->asLegacyBitmap(&bm1);
429    SkImage::MakeFromEncoded(data2)->asLegacyBitmap(&bm2);
430    SkImage::MakeFromEncoded(data3)->asLegacyBitmap(&bm3);
431    REPORTER_ASSERT(r, almost_equals(bm0, bm1, 0));
432    REPORTER_ASSERT(r, almost_equals(bm0, bm2, 90));
433    REPORTER_ASSERT(r, almost_equals(bm2, bm3, 50));
434}
435
436DEF_TEST(Encode_Alpha, r) {
437    // These formats have no sensible way to encode alpha images.
438    for (auto format : { SkEncodedImageFormat::kJPEG,
439                         SkEncodedImageFormat::kPNG,
440                         SkEncodedImageFormat::kWEBP }) {
441        for (int ctAsInt = kUnknown_SkColorType + 1; ctAsInt <= kLastEnum_SkColorType; ctAsInt++) {
442            auto ct = static_cast<SkColorType>(ctAsInt);
443            // Non-alpha-only colortypes are tested elsewhere.
444            if (!SkColorTypeIsAlphaOnly(ct)) continue;
445            SkBitmap bm;
446            bm.allocPixels(SkImageInfo::Make(10, 10, ct, kPremul_SkAlphaType));
447            sk_bzero(bm.getPixels(), bm.computeByteSize());
448            auto data = SkEncodeBitmap(bm, format, 100);
449            if (format == SkEncodedImageFormat::kPNG && ct == kAlpha_8_SkColorType) {
450                // We support encoding alpha8 to png with our own private meaning.
451                REPORTER_ASSERT(r, data != nullptr);
452            } else {
453                REPORTER_ASSERT(r, data == nullptr);
454            }
455        }
456    }
457}
458