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