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