xref: /aosp_15_r20/external/skia/tests/EncodeTest.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
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 "include/codec/SkCodec.h"
9 #include "include/codec/SkEncodedImageFormat.h"
10 #include "include/core/SkAlphaType.h"
11 #include "include/core/SkBitmap.h"
12 #include "include/core/SkCanvas.h"
13 #include "include/core/SkColor.h"
14 #include "include/core/SkColorPriv.h"
15 #include "include/core/SkColorType.h"
16 #include "include/core/SkData.h"
17 #include "include/core/SkDataTable.h"
18 #include "include/core/SkImage.h"
19 #include "include/core/SkImageInfo.h"
20 #include "include/core/SkPixmap.h"
21 #include "include/core/SkRefCnt.h"
22 #include "include/core/SkStream.h"
23 #include "include/core/SkSurface.h"
24 #include "include/core/SkTypes.h"
25 #include "include/encode/SkEncoder.h"
26 #include "include/encode/SkJpegEncoder.h"
27 #include "include/encode/SkPngEncoder.h"
28 #include "include/encode/SkWebpEncoder.h"
29 #include "include/private/base/SkAssert.h"
30 #include "include/private/base/SkMalloc.h"
31 #include "include/private/base/SkTemplates.h"
32 #include "modules/skcms/src/skcms_public.h"
33 #include "src/core/SkImageInfoPriv.h"
34 #include "tests/Test.h"
35 #include "tools/DecodeUtils.h"
36 
37 #include <png.h>
38 #include <webp/decode.h>
39 
40 #include <algorithm>
41 #include <cstddef>
42 #include <initializer_list>
43 #include <memory>
44 #include <string>
45 #include <vector>
46 
encode(SkEncodedImageFormat format,SkWStream * dst,const SkPixmap & src)47 static bool encode(SkEncodedImageFormat format, SkWStream* dst, const SkPixmap& src) {
48     switch (format) {
49         case SkEncodedImageFormat::kJPEG:
50             return SkJpegEncoder::Encode(dst, src, SkJpegEncoder::Options());
51         case SkEncodedImageFormat::kPNG:
52             return SkPngEncoder::Encode(dst, src, SkPngEncoder::Options());
53         default:
54             return false;
55     }
56 }
57 
make(SkEncodedImageFormat format,SkWStream * dst,const SkPixmap & src)58 static std::unique_ptr<SkEncoder> make(SkEncodedImageFormat format, SkWStream* dst,
59                                        const SkPixmap& src) {
60     switch (format) {
61         case SkEncodedImageFormat::kJPEG:
62             return SkJpegEncoder::Make(dst, src, SkJpegEncoder::Options());
63         case SkEncodedImageFormat::kPNG:
64             return SkPngEncoder::Make(dst, src, SkPngEncoder::Options());
65         default:
66             return nullptr;
67     }
68 }
69 
test_encode(skiatest::Reporter * r,SkEncodedImageFormat format)70 static void test_encode(skiatest::Reporter* r, SkEncodedImageFormat format) {
71     SkBitmap bitmap;
72     bool success = ToolUtils::GetResourceAsBitmap("images/mandrill_128.png", &bitmap);
73     if (!success) {
74         return;
75     }
76 
77     SkPixmap src;
78     success = bitmap.peekPixels(&src);
79     REPORTER_ASSERT(r, success);
80     if (!success) {
81         return;
82     }
83 
84     SkDynamicMemoryWStream dst0, dst1, dst2, dst3;
85     success = encode(format, &dst0, src);
86     REPORTER_ASSERT(r, success);
87 
88     auto encoder1 = make(format, &dst1, src);
89     for (int i = 0; i < src.height(); i++) {
90         success = encoder1->encodeRows(1);
91         REPORTER_ASSERT(r, success);
92     }
93 
94     auto encoder2 = make(format, &dst2, src);
95     for (int i = 0; i < src.height(); i+=3) {
96         success = encoder2->encodeRows(3);
97         REPORTER_ASSERT(r, success);
98     }
99 
100     auto encoder3 = make(format, &dst3, src);
101     success = encoder3->encodeRows(200);
102     REPORTER_ASSERT(r, success);
103 
104     sk_sp<SkData> data0 = dst0.detachAsData();
105     sk_sp<SkData> data1 = dst1.detachAsData();
106     sk_sp<SkData> data2 = dst2.detachAsData();
107     sk_sp<SkData> data3 = dst3.detachAsData();
108     REPORTER_ASSERT(r, data0->equals(data1.get()));
109     REPORTER_ASSERT(r, data0->equals(data2.get()));
110     REPORTER_ASSERT(r, data0->equals(data3.get()));
111 }
112 
DEF_TEST(Encode,r)113 DEF_TEST(Encode, r) {
114     test_encode(r, SkEncodedImageFormat::kJPEG);
115     test_encode(r, SkEncodedImageFormat::kPNG);
116 }
117 
almost_equals(SkPMColor a,SkPMColor b,int tolerance)118 static inline bool almost_equals(SkPMColor a, SkPMColor b, int tolerance) {
119     if (SkTAbs((int)SkGetPackedR32(a) - (int)SkGetPackedR32(b)) > tolerance) {
120         return false;
121     }
122 
123     if (SkTAbs((int)SkGetPackedG32(a) - (int)SkGetPackedG32(b)) > tolerance) {
124         return false;
125     }
126 
127     if (SkTAbs((int)SkGetPackedB32(a) - (int)SkGetPackedB32(b)) > tolerance) {
128         return false;
129     }
130 
131     if (SkTAbs((int)SkGetPackedA32(a) - (int)SkGetPackedA32(b)) > tolerance) {
132         return false;
133     }
134 
135     return true;
136 }
137 
almost_equals(const SkBitmap & a,const SkBitmap & b,int tolerance)138 static inline bool almost_equals(const SkBitmap& a, const SkBitmap& b, int tolerance) {
139     if (a.info() != b.info()) {
140         return false;
141     }
142 
143     SkASSERT(kN32_SkColorType == a.colorType());
144     for (int y = 0; y < a.height(); y++) {
145         for (int x = 0; x < a.width(); x++) {
146             if (!almost_equals(*a.getAddr32(x, y), *b.getAddr32(x, y), tolerance)) {
147                 return false;
148             }
149         }
150     }
151 
152     return true;
153 }
154 
test_png_encoding_roundtrip_from_specific_source_format(skiatest::Reporter * r,SkColorType colorType,SkAlphaType alphaType,int tolerance)155 void test_png_encoding_roundtrip_from_specific_source_format(skiatest::Reporter* r,
156                                                              SkColorType colorType,
157                                                              SkAlphaType alphaType,
158                                                              int tolerance) {
159     ///////////////////////////////////////////////////
160     // Decode the test image into `originalBitmapRgba8`
161     // (RGBA8, as the name implies).
162     SkBitmap originalBitmapRgba8;
163     {
164         const char* resource = (kOpaque_SkAlphaType == alphaType) ? "images/color_wheel.jpg"
165                                                                   : "images/color_wheel.png";
166         sk_sp<SkData> data = GetResourceAsData(resource);
167         if (!data) {
168             return;
169         }
170         std::unique_ptr<SkCodec> codec = SkCodec::MakeFromData(data);
171         REPORTER_ASSERT(r, !!codec);
172         if (!codec) {
173             return;
174         }
175         SkImageInfo dstInfo = codec->getInfo().makeColorType(kRGBA_8888_SkColorType);
176         originalBitmapRgba8.allocPixels(dstInfo);
177         SkCodec::Result result = codec->getPixels(
178                 dstInfo, originalBitmapRgba8.getPixels(), originalBitmapRgba8.rowBytes());
179         REPORTER_ASSERT(r,
180                         result == SkCodec::kSuccess,
181                         "result=%s, color=%d, alpha=%d",
182                         SkCodec::ResultToString(result),
183                         static_cast<int>(colorType),
184                         static_cast<int>(alphaType));
185         if (result != SkCodec::kSuccess) {
186             return;
187         }
188     }
189 
190     //////////////////////////////////////////////////////////////////
191     // Transform `originalBitmapRgba8` into `originalBitmap` (into the
192     // `colorType` / `alphaType` that this test cares about).
193     SkBitmap originalBitmap;
194     {
195         SkImageInfo dstInfo =
196                 originalBitmapRgba8.info().makeColorType(colorType).makeAlphaType(alphaType);
197         originalBitmap.allocPixels(dstInfo);
198 
199         skcms_PixelFormat dstFormat;
200         switch (colorType) {
201             case kRGBA_8888_SkColorType:
202                 dstFormat = skcms_PixelFormat_RGBA_8888;
203                 break;
204             case kBGRA_8888_SkColorType:
205                 dstFormat = skcms_PixelFormat_BGRA_8888;
206                 break;
207             case kRGBA_F16_SkColorType:
208                 dstFormat = skcms_PixelFormat_RGBA_hhhh;
209                 break;
210             case kRGBA_F32_SkColorType:
211                 dstFormat = skcms_PixelFormat_RGBA_ffff;
212                 break;
213             default:
214                 SkUNREACHABLE;
215         }
216 
217         auto to_skcms_alpha = [](SkAlphaType alpha) -> skcms_AlphaFormat {
218             switch (alpha) {
219                 case kOpaque_SkAlphaType:
220                     return skcms_AlphaFormat_Opaque;
221                 case kPremul_SkAlphaType:
222                     return skcms_AlphaFormat_PremulAsEncoded;
223                 case kUnpremul_SkAlphaType:
224                     return skcms_AlphaFormat_Unpremul;
225                     break;
226                 case kUnknown_SkAlphaType:
227                     SkUNREACHABLE;
228             }
229             SkUNREACHABLE;
230         };
231         skcms_AlphaFormat srcAlpha = to_skcms_alpha(originalBitmapRgba8.alphaType());
232         skcms_AlphaFormat dstAlpha = to_skcms_alpha(alphaType);
233 
234         size_t npixels = originalBitmapRgba8.width() * originalBitmapRgba8.height();
235         bool success = skcms_Transform(originalBitmapRgba8.getAddr(0, 0),
236                                        skcms_PixelFormat_RGBA_8888,
237                                        srcAlpha,
238                                        nullptr,
239                                        originalBitmap.getAddr(0, 0),
240                                        dstFormat,
241                                        dstAlpha,
242                                        nullptr,
243                                        npixels);
244         REPORTER_ASSERT(r, success);
245         if (!success) {
246             return;
247         }
248     }
249 
250     /////////////////////////////////////////////
251     // Encode `originalBitmap` into `encodedPng`.
252     sk_sp<SkData> encodedPng;
253     {
254         SkPixmap src;
255         bool success = originalBitmap.peekPixels(&src);
256         REPORTER_ASSERT(r, success);
257         if (!success) {
258             return;
259         }
260         SkDynamicMemoryWStream buf;
261         success = SkPngEncoder::Encode(&buf, src, SkPngEncoder::Options());
262         REPORTER_ASSERT(r, success);
263         if (!success) {
264             return;
265         }
266         encodedPng = buf.detachAsData();
267     }
268 
269     /////////////////////////////////////////////////////
270     // Decode `encodedPng` into `roundtripBitmap` (RGBA8).
271     SkBitmap roundtripBitmap;
272     {
273         std::unique_ptr<SkCodec> codec = SkCodec::MakeFromData(encodedPng);
274         REPORTER_ASSERT(r, !!codec);
275         if (!codec) {
276             return;
277         }
278         SkImageInfo dstInfo = codec->getInfo().makeColorType(kRGBA_8888_SkColorType);
279         roundtripBitmap.allocPixels(dstInfo);
280         SkCodec::Result result =
281                 codec->getPixels(dstInfo, roundtripBitmap.getPixels(), roundtripBitmap.rowBytes());
282         REPORTER_ASSERT(r,
283                         result == SkCodec::kSuccess,
284                         "result=%s, color=%d, alpha=%d",
285                         SkCodec::ResultToString(result),
286                         static_cast<int>(colorType),
287                         static_cast<int>(alphaType));
288         if (result != SkCodec::kSuccess) {
289             return;
290         }
291     }
292 
293     //////////////////////////////////////////////////////////////////////////
294     // Ensure that `originalBitmap` into `roundtripBitmap` are (almost) equal.
295     // (We can't use the `almost_equals` overload which operates on `SkBitmap`s,
296     // because our bitmaps may expectedly have differente alpha types.)
297     if (originalBitmapRgba8.dimensions() != roundtripBitmap.dimensions()) {
298         REPORTER_ASSERT(r, false);
299         return;
300     }
301     for (int y = 0; y < originalBitmap.height(); y++) {
302         for (int x = 0; x < originalBitmap.width(); x++) {
303             SkColor originalColor = originalBitmap.getColor(x, y);
304             SkColor roundtripColor = roundtripBitmap.getColor(x, y);
305             SkPMColor originalPremulColor = SkPreMultiplyColor(originalColor);
306             SkPMColor roundtripPremulColor = SkPreMultiplyColor(roundtripColor);
307             bool almost_same = almost_equals(originalPremulColor, roundtripPremulColor, tolerance);
308             REPORTER_ASSERT(r,
309                             almost_same,
310                             "x=%d, y=%d, original=0x%08x, roundtrip=0x%08x, color=%d, alpha=%d",
311                             x,
312                             y,
313                             originalPremulColor,
314                             roundtripPremulColor,
315                             static_cast<int>(colorType),
316                             static_cast<int>(alphaType));
317             if (!almost_same) {
318                 return;
319             }
320         }
321     }
322 }
323 
DEF_TEST(Encode_png_roundtrip_for_different_source_formats,r)324 DEF_TEST(Encode_png_roundtrip_for_different_source_formats, r) {
325     test_png_encoding_roundtrip_from_specific_source_format(
326             r, kN32_SkColorType, kOpaque_SkAlphaType, 0);
327     test_png_encoding_roundtrip_from_specific_source_format(
328             r, kN32_SkColorType, kUnpremul_SkAlphaType, 0);
329     test_png_encoding_roundtrip_from_specific_source_format(
330             r, kN32_SkColorType, kPremul_SkAlphaType, 0);
331 
332     // PNG encoder used to narrow down `kRGBA_F16_SkColorType` from RGBA to RGB
333     // (BE16) by skipping the alpha channel via `png_set_filler`.  But this
334     // wasn't done quite right for `kRGBA_F32_SkColorType`, which motivated this
335     // test.  See the code review comments of http://review.skia.org/922676 for
336     // more details.
337     test_png_encoding_roundtrip_from_specific_source_format(
338             r, kRGBA_F16_SkColorType, kOpaque_SkAlphaType, 0);
339     test_png_encoding_roundtrip_from_specific_source_format(
340             r, kRGBA_F32_SkColorType, kOpaque_SkAlphaType, 0);
341 }
342 
DEF_TEST(Encode_JPG,r)343 DEF_TEST(Encode_JPG, r) {
344     auto image = ToolUtils::GetResourceAsImage("images/mandrill_128.png");
345     if (!image) {
346         return;
347     }
348 
349     for (auto ct : { kRGBA_8888_SkColorType,
350                      kBGRA_8888_SkColorType,
351                      kRGB_565_SkColorType,
352                      kARGB_4444_SkColorType,
353                      kGray_8_SkColorType,
354                      kRGBA_F16_SkColorType }) {
355         for (auto at : { kPremul_SkAlphaType, kUnpremul_SkAlphaType, kOpaque_SkAlphaType }) {
356             auto info = SkImageInfo::Make(image->width(), image->height(), ct, at);
357             auto surface = SkSurfaces::Raster(info);
358             auto canvas = surface->getCanvas();
359             canvas->drawImage(image, 0, 0);
360 
361             SkBitmap bm;
362             bm.allocPixels(info);
363             if (!surface->makeImageSnapshot()->readPixels(nullptr, bm.pixmap(), 0, 0)) {
364                 ERRORF(r, "failed to readPixels! ct: %i\tat: %i\n", ct, at);
365                 continue;
366             }
367             for (auto alphaOption : { SkJpegEncoder::AlphaOption::kIgnore,
368                                       SkJpegEncoder::AlphaOption::kBlendOnBlack }) {
369                 SkJpegEncoder::Options opts;
370                 opts.fAlphaOption = alphaOption;
371                 SkNullWStream ignored;
372                 if (!SkJpegEncoder::Encode(&ignored, bm.pixmap(), opts)) {
373                     REPORTER_ASSERT(r, ct == kARGB_4444_SkColorType
374                                     && alphaOption == SkJpegEncoder::AlphaOption::kBlendOnBlack);
375                 }
376             }
377         }
378     }
379 }
380 
DEF_TEST(Encode_JpegDownsample,r)381 DEF_TEST(Encode_JpegDownsample, r) {
382     SkBitmap bitmap;
383     bool success = ToolUtils::GetResourceAsBitmap("images/mandrill_128.png", &bitmap);
384     if (!success) {
385         return;
386     }
387 
388     SkPixmap src;
389     success = bitmap.peekPixels(&src);
390     REPORTER_ASSERT(r, success);
391     if (!success) {
392         return;
393     }
394 
395     SkDynamicMemoryWStream dst0, dst1, dst2;
396     SkJpegEncoder::Options options;
397     success = SkJpegEncoder::Encode(&dst0, src, options);
398     REPORTER_ASSERT(r, success);
399 
400     options.fDownsample = SkJpegEncoder::Downsample::k422;
401     success = SkJpegEncoder::Encode(&dst1, src, options);
402     REPORTER_ASSERT(r, success);
403 
404     options.fDownsample = SkJpegEncoder::Downsample::k444;
405     success = SkJpegEncoder::Encode(&dst2, src, options);
406     REPORTER_ASSERT(r, success);
407 
408     sk_sp<SkData> data0 = dst0.detachAsData();
409     sk_sp<SkData> data1 = dst1.detachAsData();
410     sk_sp<SkData> data2 = dst2.detachAsData();
411     REPORTER_ASSERT(r, data0->size() < data1->size());
412     REPORTER_ASSERT(r, data1->size() < data2->size());
413 
414     SkBitmap bm0, bm1, bm2;
415     SkImages::DeferredFromEncodedData(data0)->asLegacyBitmap(&bm0);
416     SkImages::DeferredFromEncodedData(data1)->asLegacyBitmap(&bm1);
417     SkImages::DeferredFromEncodedData(data2)->asLegacyBitmap(&bm2);
418     REPORTER_ASSERT(r, almost_equals(bm0, bm1, 60));
419     REPORTER_ASSERT(r, almost_equals(bm1, bm2, 60));
420 }
421 
pushComment(std::vector<std::string> & comments,const char * keyword,const char * text)422 static inline void pushComment(
423         std::vector<std::string>& comments, const char* keyword, const char* text) {
424     comments.push_back(keyword);
425     comments.push_back(text);
426 }
427 
testPngComments(const SkPixmap & src,SkPngEncoder::Options & options,skiatest::Reporter * r)428 static void testPngComments(const SkPixmap& src, SkPngEncoder::Options& options,
429         skiatest::Reporter* r) {
430     std::vector<std::string> commentStrings;
431     pushComment(commentStrings, "key", "text");
432     pushComment(commentStrings, "test", "something");
433     pushComment(commentStrings, "have some", "spaces in both");
434 
435     std::string longKey(PNG_KEYWORD_MAX_LENGTH, 'x');
436 #ifdef SK_DEBUG
437     commentStrings.push_back(longKey);
438 #else
439     // We call SkDEBUGFAILF it the key is too long so we'll only test this in release mode.
440     commentStrings.push_back(longKey + "x");
441 #endif
442     commentStrings.push_back("");
443 
444     std::vector<const char*> commentPointers;
445     std::vector<size_t> commentSizes;
446     for(auto& str : commentStrings) {
447         commentPointers.push_back(str.c_str());
448         commentSizes.push_back(str.length() + 1);
449     }
450 
451     options.fComments = SkDataTable::MakeCopyArrays((void const *const *)commentPointers.data(),
452             commentSizes.data(), commentStrings.size());
453 
454 
455     SkDynamicMemoryWStream dst;
456     bool success = SkPngEncoder::Encode(&dst, src, options);
457     REPORTER_ASSERT(r, success);
458 
459     std::vector<char> output(dst.bytesWritten());
460     dst.copyTo(output.data());
461 
462     // Each chunk is of the form length (4 bytes), chunk type (tEXt), data,
463     // checksum (4 bytes).  Make sure we find all of them in the encoded
464     // results.
465     const char kExpected1[] =
466         "\x00\x00\x00\x08tEXtkey\x00text\x9e\xe7\x66\x51";
467     const char kExpected2[] =
468         "\x00\x00\x00\x0etEXttest\x00something\x29\xba\xef\xac";
469     const char kExpected3[] =
470         "\x00\x00\x00\x18tEXthave some\x00spaces in both\x8d\x69\x34\x2d";
471     std::string longKeyRecord = "tEXt" + longKey; // A snippet of our long key comment
472     std::string tooLongRecord = "tExt" + longKey + "x"; // A snippet whose key is too long
473 
474     auto search1 = std::search(output.begin(), output.end(),
475             kExpected1, kExpected1 + sizeof(kExpected1));
476     auto search2 = std::search(output.begin(), output.end(),
477             kExpected2, kExpected2 + sizeof(kExpected2));
478     auto search3 = std::search(output.begin(), output.end(),
479             kExpected3, kExpected3 + sizeof(kExpected3));
480     auto search4 = std::search(output.begin(), output.end(),
481             longKeyRecord.begin(), longKeyRecord.end());
482     auto search5 = std::search(output.begin(), output.end(),
483             tooLongRecord.begin(), tooLongRecord.end());
484 
485     REPORTER_ASSERT(r, search1 != output.end());
486     REPORTER_ASSERT(r, search2 != output.end());
487     REPORTER_ASSERT(r, search3 != output.end());
488     REPORTER_ASSERT(r, search4 != output.end());
489     REPORTER_ASSERT(r, search5 == output.end());
490     // Comments test ends
491 }
492 
DEF_TEST(Encode_PngOptions,r)493 DEF_TEST(Encode_PngOptions, r) {
494     SkBitmap bitmap;
495     bool success = ToolUtils::GetResourceAsBitmap("images/mandrill_128.png", &bitmap);
496     if (!success) {
497         return;
498     }
499 
500     SkPixmap src;
501     success = bitmap.peekPixels(&src);
502     REPORTER_ASSERT(r, success);
503     if (!success) {
504         return;
505     }
506 
507     SkDynamicMemoryWStream dst0, dst1, dst2;
508     SkPngEncoder::Options options;
509     success = SkPngEncoder::Encode(&dst0, src, options);
510     REPORTER_ASSERT(r, success);
511 
512     options.fFilterFlags = SkPngEncoder::FilterFlag::kUp;
513     success = SkPngEncoder::Encode(&dst1, src, options);
514     REPORTER_ASSERT(r, success);
515 
516     options.fZLibLevel = 3;
517     success = SkPngEncoder::Encode(&dst2, src, options);
518     REPORTER_ASSERT(r, success);
519 
520     testPngComments(src, options, r);
521 
522     sk_sp<SkData> data0 = dst0.detachAsData();
523     sk_sp<SkData> data1 = dst1.detachAsData();
524     sk_sp<SkData> data2 = dst2.detachAsData();
525     REPORTER_ASSERT(r, data0->size() < data1->size());
526     REPORTER_ASSERT(r, data1->size() < data2->size());
527 
528     SkBitmap bm0, bm1, bm2;
529     SkImages::DeferredFromEncodedData(data0)->asLegacyBitmap(&bm0);
530     SkImages::DeferredFromEncodedData(data1)->asLegacyBitmap(&bm1);
531     SkImages::DeferredFromEncodedData(data2)->asLegacyBitmap(&bm2);
532     REPORTER_ASSERT(r, almost_equals(bm0, bm1, 0));
533     REPORTER_ASSERT(r, almost_equals(bm0, bm2, 0));
534 }
535 
536 #ifndef SK_BUILD_FOR_GOOGLE3
DEF_TEST(Encode_WebpQuality,r)537 DEF_TEST(Encode_WebpQuality, r) {
538     SkBitmap bm;
539     bm.allocN32Pixels(100, 100);
540     bm.eraseColor(SK_ColorBLUE);
541 
542     SkWebpEncoder::Options opts;
543     opts.fCompression = SkWebpEncoder::Compression::kLossless;
544     SkDynamicMemoryWStream stream;
545     SkASSERT_RELEASE(SkWebpEncoder::Encode(&stream, bm.pixmap(), opts));
546     auto dataLossLess = stream.detachAsData();
547 
548     opts.fCompression = SkWebpEncoder::Compression::kLossy;
549     opts.fQuality = 99;
550     stream.reset();
551     SkASSERT_RELEASE(SkWebpEncoder::Encode(&stream, bm.pixmap(), opts));
552     auto dataLossy = stream.detachAsData();
553 
554     enum Format {
555         kMixed    = 0,
556         kLossy    = 1,
557         kLossless = 2,
558     };
559 
560     auto test = [&r](const sk_sp<SkData>& data, Format expected) {
561         auto printFormat = [](int f) {
562             switch (f) {
563                 case kMixed:    return "mixed";
564                 case kLossy:    return "lossy";
565                 case kLossless: return "lossless";
566                 default:        return "unknown";
567             }
568         };
569 
570         if (!data) {
571             ERRORF(r, "Failed to encode. Expected %s", printFormat(expected));
572             return;
573         }
574 
575         WebPBitstreamFeatures features;
576         auto status = WebPGetFeatures(data->bytes(), data->size(), &features);
577         if (status != VP8_STATUS_OK) {
578             ERRORF(r, "Encode had an error %i. Expected %s", status, printFormat(expected));
579             return;
580         }
581 
582         if (expected != features.format) {
583             ERRORF(r, "Expected %s encode, but got format %s", printFormat(expected),
584                                                                printFormat(features.format));
585         }
586     };
587 
588     test(dataLossy,    kLossy);
589     test(dataLossLess, kLossless);
590 }
591 #endif
592 
DEF_TEST(Encode_WebpOptions,r)593 DEF_TEST(Encode_WebpOptions, r) {
594     SkBitmap bitmap;
595     bool success = ToolUtils::GetResourceAsBitmap("images/google_chrome.ico", &bitmap);
596     if (!success) {
597         return;
598     }
599 
600     SkPixmap src;
601     success = bitmap.peekPixels(&src);
602     REPORTER_ASSERT(r, success);
603     if (!success) {
604         return;
605     }
606 
607     SkDynamicMemoryWStream dst0, dst1, dst2, dst3;
608     SkWebpEncoder::Options options;
609     options.fCompression = SkWebpEncoder::Compression::kLossless;
610     options.fQuality = 0.0f;
611     success = SkWebpEncoder::Encode(&dst0, src, options);
612     REPORTER_ASSERT(r, success);
613 
614     options.fQuality = 100.0f;
615     success = SkWebpEncoder::Encode(&dst1, src, options);
616     REPORTER_ASSERT(r, success);
617 
618     options.fCompression = SkWebpEncoder::Compression::kLossy;
619     options.fQuality = 100.0f;
620     success = SkWebpEncoder::Encode(&dst2, src, options);
621     REPORTER_ASSERT(r, success);
622 
623     options.fCompression = SkWebpEncoder::Compression::kLossy;
624     options.fQuality = 50.0f;
625     success = SkWebpEncoder::Encode(&dst3, src, options);
626     REPORTER_ASSERT(r, success);
627 
628     sk_sp<SkData> data0 = dst0.detachAsData();
629     sk_sp<SkData> data1 = dst1.detachAsData();
630     sk_sp<SkData> data2 = dst2.detachAsData();
631     sk_sp<SkData> data3 = dst3.detachAsData();
632     REPORTER_ASSERT(r, data0->size() > data1->size());
633     REPORTER_ASSERT(r, data1->size() > data2->size());
634     REPORTER_ASSERT(r, data2->size() > data3->size());
635 
636     SkBitmap bm0, bm1, bm2, bm3;
637     SkImages::DeferredFromEncodedData(data0)->asLegacyBitmap(&bm0);
638     SkImages::DeferredFromEncodedData(data1)->asLegacyBitmap(&bm1);
639     SkImages::DeferredFromEncodedData(data2)->asLegacyBitmap(&bm2);
640     SkImages::DeferredFromEncodedData(data3)->asLegacyBitmap(&bm3);
641     REPORTER_ASSERT(r, almost_equals(bm0, bm1, 0));
642     REPORTER_ASSERT(r, almost_equals(bm0, bm2, 90));
643     REPORTER_ASSERT(r, almost_equals(bm2, bm3, 50));
644 }
645 
DEF_TEST(Encode_WebpAnimated,r)646 DEF_TEST(Encode_WebpAnimated, r) {
647     const int frameCount = 3;
648     const int width = 16;
649     const int height = 16;
650     auto info = SkImageInfo::MakeN32Premul(width, height);
651     std::vector<SkBitmap> bitmaps(frameCount);
652     std::vector<SkEncoder::Frame> frames(frameCount);
653     std::vector<int> durations = {50, 100, 150};
654     std::vector<SkColor> colors = {SK_ColorRED, SK_ColorBLUE, SK_ColorGREEN};
655 
656     for (int i = 0; i < frameCount; i++) {
657         bitmaps[i].allocPixels(info);
658         bitmaps[i].eraseColor(colors[i]);
659         REPORTER_ASSERT(r, bitmaps[i].peekPixels(&frames[i].pixmap));
660         frames[i].duration = durations[i];
661     }
662 
663     SkDynamicMemoryWStream stream;
664     SkWebpEncoder::Options options;
665     options.fCompression = SkWebpEncoder::Compression::kLossless;
666     options.fQuality = 100;
667 
668     REPORTER_ASSERT(r, SkWebpEncoder::EncodeAnimated(&stream, frames, options));
669 
670     auto codec = SkCodec::MakeFromData(stream.detachAsData());
671     REPORTER_ASSERT(r, !!codec);
672 
673     std::vector<SkCodec::FrameInfo> frameInfos = codec->getFrameInfo();
674     REPORTER_ASSERT(r, frameInfos.size() == frameCount);
675 
676     for (size_t i = 0; i < frameInfos.size(); ++i) {
677         SkBitmap bitmap;
678         bitmap.allocPixels(info);
679         bitmap.eraseColor(0);
680 
681         SkCodec::Options codecOptions;
682         codecOptions.fFrameIndex = (int)i;
683 
684         auto result = codec->getPixels(info, bitmap.getPixels(), bitmap.rowBytes(), &codecOptions);
685         if (result != SkCodec::kSuccess) {
686             ERRORF(r, "error in frame %zu: %s", i, SkCodec::ResultToString(result));
687         }
688 
689         REPORTER_ASSERT(r, almost_equals(bitmap, bitmaps[i], 0));
690         REPORTER_ASSERT(r, frameInfos[i].fDuration == durations[i]);
691     }
692 }
693 
DEF_TEST(Encode_WebpAnimated_FrameUnmatched,r)694 DEF_TEST(Encode_WebpAnimated_FrameUnmatched, r) {
695     // Create two frames with unmatched sizes and verify the encode should fail.
696     SkEncoder::Frame frame1;
697     SkBitmap bm1;
698     bm1.allocPixels(SkImageInfo::MakeN32Premul(8, 8));
699     bm1.eraseColor(SK_ColorYELLOW);
700     REPORTER_ASSERT(r, bm1.peekPixels(&frame1.pixmap));
701     frame1.duration = 200;
702 
703     SkEncoder::Frame frame2;
704     SkBitmap bm2;
705     bm2.allocPixels(SkImageInfo::MakeN32Premul(16, 16));
706     bm2.eraseColor(SK_ColorYELLOW);
707     REPORTER_ASSERT(r, bm2.peekPixels(&frame2.pixmap));
708     frame2.duration = 200;
709 
710     SkDynamicMemoryWStream stream;
711     SkWebpEncoder::Options options;
712     options.fCompression = SkWebpEncoder::Compression::kLossy;
713     options.fQuality = 100;
714     std::vector<SkEncoder::Frame> frames = {frame1, frame2};
715     bool output = SkWebpEncoder::EncodeAnimated(&stream, frames, options);
716     REPORTER_ASSERT(r, !output);
717 }
718 
DEF_TEST(Encode_Alpha,r)719 DEF_TEST(Encode_Alpha, r) {
720     // These formats have no sensible way to encode alpha images.
721     for (auto format : { SkEncodedImageFormat::kJPEG,
722                          SkEncodedImageFormat::kPNG,
723                          SkEncodedImageFormat::kWEBP }) {
724         for (int ctAsInt = kUnknown_SkColorType + 1; ctAsInt <= kLastEnum_SkColorType; ctAsInt++) {
725             auto ct = static_cast<SkColorType>(ctAsInt);
726             // Non-alpha-only colortypes are tested elsewhere.
727             if (!SkColorTypeIsAlphaOnly(ct)) continue;
728             SkBitmap bm;
729             bm.allocPixels(SkImageInfo::Make(10, 10, ct, kPremul_SkAlphaType));
730             sk_bzero(bm.getPixels(), bm.computeByteSize());
731             SkDynamicMemoryWStream stream;
732             bool success = false;
733             if (format == SkEncodedImageFormat::kJPEG) {
734                 success = SkJpegEncoder::Encode(&stream, bm.pixmap(), {});
735             } else if (format == SkEncodedImageFormat::kPNG) {
736                 success = SkPngEncoder::Encode(&stream, bm.pixmap(), {});
737             } else {
738                 success = SkWebpEncoder::Encode(&stream, bm.pixmap(), {});
739             }
740 
741             if ((format == SkEncodedImageFormat::kJPEG || format == SkEncodedImageFormat::kPNG) &&
742                 ct == kAlpha_8_SkColorType) {
743                 // We support encoding alpha8 to png and jpeg with our own private meaning.
744                 REPORTER_ASSERT(r, success);
745                 REPORTER_ASSERT(r, stream.bytesWritten() > 0);
746             } else {
747                 REPORTER_ASSERT(r, !success);
748                 REPORTER_ASSERT(r, stream.bytesWritten() == 0);
749             }
750         }
751     }
752 }
753