1 /*
2 * Copyright 2015 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/SkAndroidCodec.h"
9 #include "include/codec/SkCodec.h"
10 #include "include/codec/SkEncodedImageFormat.h"
11 #include "include/codec/SkGifDecoder.h"
12 #include "include/codec/SkJpegDecoder.h"
13 #include "include/codec/SkPngChunkReader.h"
14 #include "include/core/SkAlphaType.h"
15 #include "include/core/SkBitmap.h"
16 #include "include/core/SkCanvas.h"
17 #include "include/core/SkColor.h"
18 #include "include/core/SkColorSpace.h"
19 #include "include/core/SkColorType.h"
20 #include "include/core/SkData.h"
21 #include "include/core/SkDataTable.h"
22 #include "include/core/SkImage.h"
23 #include "include/core/SkImageGenerator.h"
24 #include "include/core/SkImageInfo.h"
25 #include "include/core/SkPixmap.h"
26 #include "include/core/SkRect.h"
27 #include "include/core/SkRefCnt.h"
28 #include "include/core/SkSize.h"
29 #include "include/core/SkStream.h"
30 #include "include/core/SkString.h"
31 #include "include/core/SkTypes.h"
32 #include "include/encode/SkJpegEncoder.h"
33 #include "include/encode/SkPngEncoder.h"
34 #include "include/encode/SkWebpEncoder.h"
35 #include "include/private/base/SkAlign.h"
36 #include "include/private/base/SkDebug.h"
37 #include "include/private/base/SkMalloc.h"
38 #include "include/private/base/SkTemplates.h"
39 #include "modules/skcms/skcms.h"
40 #include "src/base/SkAutoMalloc.h"
41 #include "src/base/SkRandom.h"
42 #include "src/codec/SkCodecImageGenerator.h"
43 #include "src/core/SkColorSpacePriv.h"
44 #include "src/core/SkMD5.h"
45 #include "src/core/SkStreamPriv.h"
46 #include "tests/FakeStreams.h"
47 #include "tests/Test.h"
48 #include "tools/DecodeUtils.h"
49 #include "tools/Resources.h"
50 #include "tools/ToolUtils.h"
51
52 #ifdef SK_ENABLE_ANDROID_UTILS
53 #include "client_utils/android/FrontBufferedStream.h"
54 #endif
55
56 #include <png.h>
57 #include <pngconf.h>
58 #include <setjmp.h>
59
60 #include <algorithm>
61 #include <cstdint>
62 #include <cstring>
63 #include <initializer_list>
64 #include <memory>
65 #include <optional>
66 #include <utility>
67 #include <vector>
68
69 using namespace skia_private;
70
71 #if PNG_LIBPNG_VER_MAJOR == 1 && PNG_LIBPNG_VER_MINOR < 5
72 // FIXME (scroggo): Google3 needs to be updated to use a newer version of libpng. In
73 // the meantime, we had to break some pieces of SkPngCodec in order to support Google3.
74 // The parts that are broken are likely not used by Google3.
75 #define SK_PNG_DISABLE_TESTS
76 #endif
77
md5(const SkBitmap & bm)78 static SkMD5::Digest md5(const SkBitmap& bm) {
79 SkASSERT(bm.getPixels());
80 SkMD5 md5;
81 size_t rowLen = bm.info().bytesPerPixel() * bm.width();
82 for (int y = 0; y < bm.height(); ++y) {
83 md5.write(bm.getAddr(0, y), rowLen);
84 }
85 return md5.finish();
86 }
87
88 /**
89 * Compute the digest for bm and compare it to a known good digest.
90 * @param r Reporter to assert that bm's digest matches goodDigest.
91 * @param goodDigest The known good digest to compare to.
92 * @param bm The bitmap to test.
93 */
compare_to_good_digest(skiatest::Reporter * r,const SkMD5::Digest & goodDigest,const SkBitmap & bm)94 static void compare_to_good_digest(skiatest::Reporter* r, const SkMD5::Digest& goodDigest,
95 const SkBitmap& bm) {
96 SkMD5::Digest digest = md5(bm);
97 REPORTER_ASSERT(r, digest == goodDigest);
98 }
99
100 /**
101 * Test decoding an SkCodec to a particular SkImageInfo.
102 *
103 * Calling getPixels(info) should return expectedResult, and if goodDigest is non nullptr,
104 * the resulting decode should match.
105 */
106 template<typename Codec>
test_info(skiatest::Reporter * r,Codec * codec,const SkImageInfo & info,SkCodec::Result expectedResult,const SkMD5::Digest * goodDigest)107 static void test_info(skiatest::Reporter* r, Codec* codec, const SkImageInfo& info,
108 SkCodec::Result expectedResult, const SkMD5::Digest* goodDigest) {
109 SkBitmap bm;
110 bm.allocPixels(info);
111
112 SkCodec::Result result = codec->getPixels(info, bm.getPixels(), bm.rowBytes());
113 REPORTER_ASSERT(r, result == expectedResult);
114
115 if (goodDigest) {
116 compare_to_good_digest(r, *goodDigest, bm);
117 }
118 }
119
generate_random_subset(SkRandom * rand,int w,int h)120 SkIRect generate_random_subset(SkRandom* rand, int w, int h) {
121 SkIRect rect;
122 do {
123 rect.fLeft = rand->nextRangeU(0, w);
124 rect.fTop = rand->nextRangeU(0, h);
125 rect.fRight = rand->nextRangeU(0, w);
126 rect.fBottom = rand->nextRangeU(0, h);
127 rect.sort();
128 } while (rect.isEmpty());
129 return rect;
130 }
131
test_incremental_decode(skiatest::Reporter * r,SkCodec * codec,const SkImageInfo & info,const SkMD5::Digest & goodDigest)132 static void test_incremental_decode(skiatest::Reporter* r, SkCodec* codec, const SkImageInfo& info,
133 const SkMD5::Digest& goodDigest) {
134 SkBitmap bm;
135 bm.allocPixels(info);
136
137 REPORTER_ASSERT(r, SkCodec::kSuccess == codec->startIncrementalDecode(info, bm.getPixels(),
138 bm.rowBytes()));
139
140 REPORTER_ASSERT(r, SkCodec::kSuccess == codec->incrementalDecode());
141
142 compare_to_good_digest(r, goodDigest, bm);
143 }
144
145 // Test in stripes, similar to DM's kStripe_Mode
test_in_stripes(skiatest::Reporter * r,SkCodec * codec,const SkImageInfo & info,const SkMD5::Digest & goodDigest)146 static void test_in_stripes(skiatest::Reporter* r, SkCodec* codec, const SkImageInfo& info,
147 const SkMD5::Digest& goodDigest) {
148 SkBitmap bm;
149 bm.allocPixels(info);
150 bm.eraseColor(SK_ColorYELLOW);
151
152 const int height = info.height();
153 // Note that if numStripes does not evenly divide height there will be an extra
154 // stripe.
155 const int numStripes = 4;
156
157 if (numStripes > height) {
158 // Image is too small.
159 return;
160 }
161
162 const int stripeHeight = height / numStripes;
163
164 // Iterate through the image twice. Once to decode odd stripes, and once for even.
165 for (int oddEven = 1; oddEven >= 0; oddEven--) {
166 for (int y = oddEven * stripeHeight; y < height; y += 2 * stripeHeight) {
167 SkIRect subset = SkIRect::MakeLTRB(0, y, info.width(),
168 std::min(y + stripeHeight, height));
169 SkCodec::Options options;
170 options.fSubset = ⊂
171 if (SkCodec::kSuccess != codec->startIncrementalDecode(info, bm.getAddr(0, y),
172 bm.rowBytes(), &options)) {
173 ERRORF(r, "failed to start incremental decode!\ttop: %i\tbottom%i\n",
174 subset.top(), subset.bottom());
175 return;
176 }
177 if (SkCodec::kSuccess != codec->incrementalDecode()) {
178 ERRORF(r, "failed incremental decode starting from line %i\n", y);
179 return;
180 }
181 }
182 }
183
184 compare_to_good_digest(r, goodDigest, bm);
185 }
186
187 template<typename Codec>
test_codec(skiatest::Reporter * r,const char * path,Codec * codec,SkBitmap & bm,const SkImageInfo & info,const SkISize & size,SkCodec::Result expectedResult,SkMD5::Digest * digest,const SkMD5::Digest * goodDigest)188 static void test_codec(skiatest::Reporter* r, const char* path, Codec* codec, SkBitmap& bm,
189 const SkImageInfo& info, const SkISize& size, SkCodec::Result expectedResult,
190 SkMD5::Digest* digest, const SkMD5::Digest* goodDigest) {
191
192 REPORTER_ASSERT(r, info.dimensions() == size);
193 bm.allocPixels(info);
194
195 SkCodec::Result result = codec->getPixels(info, bm.getPixels(), bm.rowBytes());
196 REPORTER_ASSERT(r, result == expectedResult);
197
198 *digest = md5(bm);
199 if (goodDigest) {
200 REPORTER_ASSERT(r, *digest == *goodDigest);
201 }
202
203 {
204 // Test decoding to 565
205 SkImageInfo info565 = info.makeColorType(kRGB_565_SkColorType);
206 if (info.alphaType() == kOpaque_SkAlphaType) {
207 // Decoding to 565 should succeed.
208 SkBitmap bm565;
209 bm565.allocPixels(info565);
210
211 // This will allow comparison even if the image is incomplete.
212 bm565.eraseColor(SK_ColorBLACK);
213
214 auto actualResult = codec->getPixels(info565, bm565.getPixels(), bm565.rowBytes());
215 if (actualResult == expectedResult) {
216 SkMD5::Digest digest565 = md5(bm565);
217
218 // A request for non-opaque should also succeed.
219 for (auto alpha : { kPremul_SkAlphaType, kUnpremul_SkAlphaType }) {
220 info565 = info565.makeAlphaType(alpha);
221 test_info(r, codec, info565, expectedResult, &digest565);
222 }
223 } else {
224 ERRORF(r, "Decoding %s to 565 failed with result \"%s\"\n\t\t\t\texpected:\"%s\"",
225 path,
226 SkCodec::ResultToString(actualResult),
227 SkCodec::ResultToString(expectedResult));
228 }
229 } else {
230 test_info(r, codec, info565, SkCodec::kInvalidConversion, nullptr);
231 }
232 }
233
234 if (codec->getInfo().colorType() == kGray_8_SkColorType) {
235 SkImageInfo grayInfo = codec->getInfo();
236 SkBitmap grayBm;
237 grayBm.allocPixels(grayInfo);
238
239 grayBm.eraseColor(SK_ColorBLACK);
240
241 REPORTER_ASSERT(r, expectedResult == codec->getPixels(grayInfo,
242 grayBm.getPixels(), grayBm.rowBytes()));
243
244 SkMD5::Digest grayDigest = md5(grayBm);
245
246 for (auto alpha : { kPremul_SkAlphaType, kUnpremul_SkAlphaType }) {
247 grayInfo = grayInfo.makeAlphaType(alpha);
248 test_info(r, codec, grayInfo, expectedResult, &grayDigest);
249 }
250 }
251
252 // Verify that re-decoding gives the same result. It is interesting to check this after
253 // a decode to 565, since choosing to decode to 565 may result in some of the decode
254 // options being modified. These options should return to their defaults on another
255 // decode to kN32, so the new digest should match the old digest.
256 test_info(r, codec, info, expectedResult, digest);
257
258 {
259 // Check alpha type conversions
260 if (info.alphaType() == kOpaque_SkAlphaType) {
261 test_info(r, codec, info.makeAlphaType(kUnpremul_SkAlphaType),
262 expectedResult, digest);
263 test_info(r, codec, info.makeAlphaType(kPremul_SkAlphaType),
264 expectedResult, digest);
265 } else {
266 // Decoding to opaque should fail
267 test_info(r, codec, info.makeAlphaType(kOpaque_SkAlphaType),
268 SkCodec::kInvalidConversion, nullptr);
269 SkAlphaType otherAt = info.alphaType();
270 if (kPremul_SkAlphaType == otherAt) {
271 otherAt = kUnpremul_SkAlphaType;
272 } else {
273 otherAt = kPremul_SkAlphaType;
274 }
275 // The other non-opaque alpha type should always succeed, but not match.
276 test_info(r, codec, info.makeAlphaType(otherAt), expectedResult, nullptr);
277 }
278 }
279 }
280
supports_partial_scanlines(const char path[])281 static bool supports_partial_scanlines(const char path[]) {
282 static const char* const exts[] = {
283 "jpg", "jpeg", "png", "webp",
284 "JPG", "JPEG", "PNG", "WEBP"
285 };
286
287 for (uint32_t i = 0; i < std::size(exts); i++) {
288 if (SkStrEndsWith(path, exts[i])) {
289 return true;
290 }
291 }
292 return false;
293 }
294
check_scanline_decode(skiatest::Reporter * r,SkCodec * codec,SkMD5::Digest * codecDigest,const SkImageInfo & info,const char path[],SkISize size,bool supportsScanlineDecoding,bool supportsIncomplete,bool supportsNewScanlineDecoding)295 static void check_scanline_decode(skiatest::Reporter* r,
296 SkCodec* codec,
297 SkMD5::Digest* codecDigest,
298 const SkImageInfo& info,
299 const char path[],
300 SkISize size,
301 bool supportsScanlineDecoding,
302 bool supportsIncomplete,
303 bool supportsNewScanlineDecoding) {
304
305 // Test full image decodes with SkCodec
306 SkBitmap bm;
307 const SkCodec::Result expectedResult = supportsIncomplete ? SkCodec::kIncompleteInput
308 : SkCodec::kSuccess;
309 test_codec(r, path, codec, bm, info, size, expectedResult, codecDigest, nullptr);
310
311 // Scanline decoding follows.
312 if (supportsNewScanlineDecoding && !supportsIncomplete) {
313 test_incremental_decode(r, codec, info, *codecDigest);
314 // This is only supported by codecs that use incremental decoding to
315 // support subset decodes - png and jpeg (once SkJpegCodec is
316 // converted).
317 if (SkStrEndsWith(path, "png") || SkStrEndsWith(path, "PNG")) {
318 test_in_stripes(r, codec, info, *codecDigest);
319 }
320 }
321
322 // Need to call startScanlineDecode() first.
323 REPORTER_ASSERT(r, codec->getScanlines(bm.getAddr(0, 0), 1, 0) == 0);
324 REPORTER_ASSERT(r, !codec->skipScanlines(1));
325 const SkCodec::Result startResult = codec->startScanlineDecode(info);
326 if (supportsScanlineDecoding) {
327 bm.eraseColor(SK_ColorYELLOW);
328
329 REPORTER_ASSERT(r, startResult == SkCodec::kSuccess);
330
331 for (int y = 0; y < info.height(); y++) {
332 const int lines = codec->getScanlines(bm.getAddr(0, y), 1, 0);
333 if (!supportsIncomplete) {
334 REPORTER_ASSERT(r, 1 == lines);
335 }
336 }
337 // verify that scanline decoding gives the same result.
338 if (SkCodec::kTopDown_SkScanlineOrder == codec->getScanlineOrder()) {
339 compare_to_good_digest(r, *codecDigest, bm);
340 }
341
342 // Cannot continue to decode scanlines beyond the end
343 REPORTER_ASSERT(r, codec->getScanlines(bm.getAddr(0, 0), 1, 0)
344 == 0);
345
346 // Interrupting a scanline decode with a full decode starts from
347 // scratch
348 {
349 REPORTER_ASSERT(r, codec->startScanlineDecode(info) == SkCodec::kSuccess);
350 const int lines = codec->getScanlines(bm.getAddr(0, 0), 1, 0);
351 if (!supportsIncomplete) {
352 REPORTER_ASSERT(r, lines == 1);
353 }
354 REPORTER_ASSERT(r, codec->getPixels(bm.info(), bm.getPixels(), bm.rowBytes())
355 == expectedResult);
356 REPORTER_ASSERT(r, codec->getScanlines(bm.getAddr(0, 0), 1, 0)
357 == 0);
358 REPORTER_ASSERT(r, codec->skipScanlines(1)
359 == 0);
360 }
361
362 // Test partial scanline decodes
363 if (supports_partial_scanlines(path) && info.width() >= 3) {
364 SkCodec::Options options;
365 int width = info.width();
366 int height = info.height();
367 SkIRect subset = SkIRect::MakeXYWH(2 * (width / 3), 0, width / 3, height);
368 options.fSubset = ⊂
369
370 const auto partialStartResult = codec->startScanlineDecode(info, &options);
371 REPORTER_ASSERT(r, partialStartResult == SkCodec::kSuccess);
372
373 for (int y = 0; y < height; y++) {
374 const int lines = codec->getScanlines(bm.getAddr(0, y), 1, 0);
375 if (!supportsIncomplete) {
376 REPORTER_ASSERT(r, 1 == lines);
377 }
378 }
379 }
380 } else {
381 REPORTER_ASSERT(r, startResult == SkCodec::kUnimplemented);
382 }
383 }
384
check_subset_decode(skiatest::Reporter * r,SkCodec * codec,const SkImageInfo & info,SkISize size,bool supportsSubsetDecoding,bool supportsIncomplete)385 static void check_subset_decode(skiatest::Reporter* r,
386 SkCodec* codec,
387 const SkImageInfo& info,
388 SkISize size,
389 bool supportsSubsetDecoding,
390 bool supportsIncomplete) {
391 // This function tests decoding subsets, and will decode a handful of randomly-sized subsets.
392 // Do not attempt to decode subsets of an image of only one pixel, since there is no
393 // meaningful subset.
394 if (size.width() * size.height() == 1) {
395 return;
396 }
397
398 SkRandom rand;
399 SkIRect subset;
400 SkCodec::Options opts;
401 opts.fSubset = ⊂
402 for (int i = 0; i < 5; i++) {
403 subset = generate_random_subset(&rand, size.width(), size.height());
404 SkASSERT(!subset.isEmpty());
405 const bool supported = codec->getValidSubset(&subset);
406 REPORTER_ASSERT(r, supported == supportsSubsetDecoding);
407
408 SkImageInfo subsetInfo = info.makeDimensions(subset.size());
409 SkBitmap bm;
410 bm.allocPixels(subsetInfo);
411 const auto result = codec->getPixels(bm.info(), bm.getPixels(), bm.rowBytes(), &opts);
412
413 if (supportsSubsetDecoding) {
414 if (!supportsIncomplete) {
415 REPORTER_ASSERT(r, result == SkCodec::kSuccess);
416 }
417 // Webp is the only codec that supports subsets, and it will have modified the subset
418 // to have even left/top.
419 REPORTER_ASSERT(r, SkIsAlign2(subset.fLeft) && SkIsAlign2(subset.fTop));
420 } else {
421 // No subsets will work.
422 REPORTER_ASSERT(r, result == SkCodec::kUnimplemented);
423 }
424 }
425 }
426
check_android_codec(skiatest::Reporter * r,std::unique_ptr<SkCodec> codec,const SkMD5::Digest & codecDigest,const SkImageInfo & info,const char path[],SkISize size,bool supportsScanlineDecoding,bool supportsSubsetDecoding,bool supportsIncomplete,bool supportsNewScanlineDecoding)427 static void check_android_codec(skiatest::Reporter* r,
428 std::unique_ptr<SkCodec> codec,
429 const SkMD5::Digest& codecDigest,
430 const SkImageInfo& info,
431 const char path[],
432 SkISize size,
433 bool supportsScanlineDecoding,
434 bool supportsSubsetDecoding,
435 bool supportsIncomplete,
436 bool supportsNewScanlineDecoding) {
437 if (supportsScanlineDecoding || supportsSubsetDecoding || supportsNewScanlineDecoding) {
438 auto androidCodec = SkAndroidCodec::MakeFromCodec(std::move(codec));
439 if (!androidCodec) {
440 ERRORF(r, "Unable to decode '%s'", path);
441 return;
442 }
443
444 SkBitmap bm;
445 SkMD5::Digest androidCodecDigest;
446 const SkCodec::Result expectedResult = supportsIncomplete ? SkCodec::kIncompleteInput
447 : SkCodec::kSuccess;
448 test_codec(r, path, androidCodec.get(), bm, info, size, expectedResult, &androidCodecDigest,
449 &codecDigest);
450 }
451 }
452
check_codec_image_generator(skiatest::Reporter * r,const SkMD5::Digest & codecDigest,const SkImageInfo & info,const char path[],bool supportsIncomplete)453 static void check_codec_image_generator(skiatest::Reporter* r,
454 const SkMD5::Digest& codecDigest,
455 const SkImageInfo& info,
456 const char path[],
457 bool supportsIncomplete) {
458 // Test SkCodecImageGenerator
459 if (!supportsIncomplete) {
460 std::unique_ptr<SkStream> stream(GetResourceAsStream(path));
461 sk_sp<SkData> fullData(SkData::MakeFromStream(stream.get(), stream->getLength()));
462 std::unique_ptr<SkImageGenerator> gen(
463 SkCodecImageGenerator::MakeFromEncodedCodec(fullData));
464 SkBitmap bm;
465 bm.allocPixels(info);
466 REPORTER_ASSERT(r, gen->getPixels(info, bm.getPixels(), bm.rowBytes()));
467 compare_to_good_digest(r, codecDigest, bm);
468
469 #if !defined(SK_PNG_DISABLE_TESTS) && defined(SK_ENABLE_ANDROID_UTILS)
470 // Test using FrontBufferedStream, as Android does
471 auto bufferedStream = android::skia::FrontBufferedStream::Make(
472 SkMemoryStream::Make(std::move(fullData)), SkCodec::MinBufferedBytesNeeded());
473 REPORTER_ASSERT(r, bufferedStream);
474 std::unique_ptr<SkCodec> codec = SkCodec::MakeFromStream(std::move(bufferedStream));
475 REPORTER_ASSERT(r, codec);
476 if (codec) {
477 test_info(r, codec.get(), info, SkCodec::kSuccess, &codecDigest);
478 }
479 #endif
480 }
481 }
482
check(skiatest::Reporter * r,const char path[],SkISize size,bool supportsScanlineDecoding,bool supportsSubsetDecoding,bool supportsIncomplete,bool supportsNewScanlineDecoding=false)483 static void check(skiatest::Reporter* r,
484 const char path[],
485 SkISize size,
486 bool supportsScanlineDecoding,
487 bool supportsSubsetDecoding,
488 bool supportsIncomplete,
489 bool supportsNewScanlineDecoding = false) {
490 skiatest::ReporterContext context(r, path);
491 // If we're testing incomplete decodes, let's run the same test on full decodes.
492 if (supportsIncomplete) {
493 check(r, path, size, supportsScanlineDecoding, supportsSubsetDecoding,
494 /*supportsIncomplete=*/false, supportsNewScanlineDecoding);
495 }
496
497 // Initialize a codec with a data stream.
498 std::unique_ptr<SkStream> stream(GetResourceAsStream(path));
499 if (!stream) {
500 return;
501 }
502
503 std::unique_ptr<SkCodec> codec;
504 if (supportsIncomplete) {
505 size_t length = stream->getLength();
506 codec = SkCodec::MakeFromData(SkData::MakeFromStream(stream.get(), 2 * length / 3));
507 } else {
508 codec = SkCodec::MakeFromStream(std::move(stream));
509 }
510 if (!codec) {
511 ERRORF(r, "Unable to decode '%s'", path);
512 return;
513 }
514
515 const SkImageInfo info = codec->getInfo().makeColorType(kN32_SkColorType);
516
517 // Run tests with this codec.
518 SkMD5::Digest codecDigest;
519 check_scanline_decode(r, codec.get(), &codecDigest, info, path, size, supportsScanlineDecoding,
520 supportsIncomplete, supportsNewScanlineDecoding);
521
522 check_subset_decode(r, codec.get(), info, size, supportsSubsetDecoding, supportsIncomplete);
523
524 check_android_codec(r, std::move(codec), codecDigest, info, path, size,
525 supportsScanlineDecoding, supportsSubsetDecoding, supportsIncomplete,
526 supportsNewScanlineDecoding);
527
528 check_codec_image_generator(r, codecDigest, info, path, supportsIncomplete);
529 }
530
decodeToSkImage(skiatest::Reporter * r,const char * resourcePath,SkColorType dstColorType,SkAlphaType dstAlphaType)531 static sk_sp<SkImage> decodeToSkImage(skiatest::Reporter* r,
532 const char* resourcePath,
533 SkColorType dstColorType,
534 SkAlphaType dstAlphaType) {
535 std::unique_ptr<SkStream> stream(GetResourceAsStream(resourcePath));
536 REPORTER_ASSERT(r, !!stream);
537 if (!stream) {
538 return nullptr;
539 }
540
541 std::unique_ptr<SkCodec> codec = SkCodec::MakeFromStream(std::move(stream));
542 REPORTER_ASSERT(r, !!codec);
543 if (!codec) {
544 return nullptr;
545 }
546
547 SkImageInfo dstInfo = codec->getInfo()
548 .makeColorType(dstColorType)
549 .makeAlphaType(dstAlphaType)
550 .makeColorSpace(SkColorSpace::MakeSRGB());
551 sk_sp<SkImage> image;
552 SkCodec::Result result;
553 std::tie(image, result) = codec->getImage(dstInfo);
554 REPORTER_ASSERT(r, !!result == SkCodec::kSuccess);
555 REPORTER_ASSERT(r, !!image);
556 return image;
557 }
558
decodeSingleRawPixelAsUint32(skiatest::Reporter * r,const char * resourcePath,SkColorType dstColorType,SkAlphaType dstAlphaType,int x=0,int y=0)559 static std::optional<uint32_t> decodeSingleRawPixelAsUint32(skiatest::Reporter* r,
560 const char* resourcePath,
561 SkColorType dstColorType,
562 SkAlphaType dstAlphaType,
563 int x = 0,
564 int y = 0) {
565 SkASSERT(dstColorType == kBGRA_8888_SkColorType || dstColorType == kRGBA_8888_SkColorType);
566 sk_sp<SkImage> image = decodeToSkImage(r, resourcePath, dstColorType, dstAlphaType);
567 if (!image) {
568 return std::nullopt; // REPORTER_ASSERT should already fire in `decodeToSkImage`.
569 }
570
571 SkPixmap pixmap;
572 if (!image->peekPixels(&pixmap)) {
573 REPORTER_ASSERT(r, false, "peekPixels failed");
574 return std::nullopt;
575 }
576
577 return *pixmap.addr32(x, y);
578 }
579
DEF_TEST(Codec_wbmp,r)580 DEF_TEST(Codec_wbmp, r) {
581 check(r, "images/mandrill.wbmp", SkISize::Make(512, 512), true, false, true);
582 }
583
DEF_TEST(Codec_webp,r)584 DEF_TEST(Codec_webp, r) {
585 check(r, "images/baby_tux.webp", SkISize::Make(386, 395), false, true, true);
586 check(r, "images/color_wheel.webp", SkISize::Make(128, 128), false, true, true);
587 check(r, "images/yellow_rose.webp", SkISize::Make(400, 301), false, true, true);
588 }
589
DEF_TEST(Codec_bmp,r)590 DEF_TEST(Codec_bmp, r) {
591 check(r, "images/randPixels.bmp", SkISize::Make(8, 8), true, false, true);
592 check(r, "images/rle.bmp", SkISize::Make(320, 240), true, false, true);
593 }
594
595 #if defined(SK_CODEC_DECODES_ICO)
DEF_TEST(Codec_ico,r)596 DEF_TEST(Codec_ico, r) {
597 // FIXME: We are not ready to test incomplete ICOs
598 // These two tests examine interestingly different behavior:
599 // Decodes an embedded BMP image
600 check(r, "images/color_wheel.ico", SkISize::Make(128, 128), true, false, false);
601 // Decodes an embedded PNG image
602 check(r, "images/google_chrome.ico", SkISize::Make(256, 256), false, false, false, true);
603 }
604 #endif
605
DEF_TEST(Codec_gif,r)606 DEF_TEST(Codec_gif, r) {
607 check(r, "images/box.gif", SkISize::Make(200, 55), false, false, true, true);
608 check(r, "images/color_wheel.gif", SkISize::Make(128, 128), false, false, true, true);
609 // randPixels.gif is too small to test incomplete
610 check(r, "images/randPixels.gif", SkISize::Make(8, 8), false, false, false, true);
611 }
612
DEF_TEST(Codec_jpg,r)613 DEF_TEST(Codec_jpg, r) {
614 check(r, "images/CMYK.jpg", SkISize::Make(642, 516), true, false, true);
615 check(r, "images/color_wheel.jpg", SkISize::Make(128, 128), true, false, true);
616 // grayscale.jpg is too small to test incomplete
617 check(r, "images/grayscale.jpg", SkISize::Make(128, 128), true, false, false);
618 check(r, "images/mandrill_512_q075.jpg", SkISize::Make(512, 512), true, false, true);
619 // randPixels.jpg is too small to test incomplete
620 check(r, "images/randPixels.jpg", SkISize::Make(8, 8), true, false, false);
621 }
622
DEF_TEST(Codec_png,r)623 DEF_TEST(Codec_png, r) {
624 check(r, "images/arrow.png", SkISize::Make(187, 312), false, false, true, true);
625 check(r, "images/baby_tux.png", SkISize::Make(240, 246), false, false, true, true);
626 check(r, "images/color_wheel.png", SkISize::Make(128, 128), false, false, true, true);
627 // half-transparent-white-pixel.png is too small to test incomplete
628 check(r, "images/half-transparent-white-pixel.png", SkISize::Make(1, 1), false, false, false, true);
629 check(r, "images/mandrill_128.png", SkISize::Make(128, 128), false, false, true, true);
630 // mandrill_16.png is too small (relative to embedded sRGB profile) to test incomplete
631 check(r, "images/mandrill_16.png", SkISize::Make(16, 16), false, false, false, true);
632 check(r, "images/mandrill_256.png", SkISize::Make(256, 256), false, false, true, true);
633 check(r, "images/mandrill_32.png", SkISize::Make(32, 32), false, false, true, true);
634 check(r, "images/mandrill_512.png", SkISize::Make(512, 512), false, false, true, true);
635 check(r, "images/mandrill_64.png", SkISize::Make(64, 64), false, false, true, true);
636 check(r, "images/plane.png", SkISize::Make(250, 126), false, false, true, true);
637 check(r, "images/plane_interlaced.png", SkISize::Make(250, 126), false, false, true, true);
638 check(r, "images/randPixels.png", SkISize::Make(8, 8), false, false, true, true);
639 check(r, "images/yellow_rose.png", SkISize::Make(400, 301), false, false, true, true);
640 }
641
verifyFirstFourDecodedBytes(skiatest::Reporter * r,const char * fileName,SkColorType dstColorType,SkAlphaType dstAlphaType,std::array<uint8_t,4> expected)642 static void verifyFirstFourDecodedBytes(skiatest::Reporter* r,
643 const char* fileName,
644 SkColorType dstColorType,
645 SkAlphaType dstAlphaType,
646 std::array<uint8_t, 4> expected) {
647 std::string resourcePath = "images/";
648 resourcePath += fileName;
649
650 std::optional<uint32_t> maybeColor =
651 decodeSingleRawPixelAsUint32(r, resourcePath.c_str(), dstColorType, dstAlphaType);
652 if (!maybeColor.has_value()) {
653 // `REPORTER_ASSERT` should already fire in `decodeSingleRaw...`.
654 return;
655 }
656 const uint8_t* pixel = static_cast<const uint8_t*>(static_cast<const void*>(&*maybeColor));
657
658 std::string testName = "";
659 switch (dstColorType) {
660 case kRGBA_8888_SkColorType:
661 testName += "RGBA";
662 break;
663 case kBGRA_8888_SkColorType:
664 testName += "BGRA";
665 break;
666 default:
667 SkUNREACHABLE;
668 }
669 switch (dstAlphaType) {
670 case kPremul_SkAlphaType:
671 testName += " (premul)";
672 break;
673 case kUnpremul_SkAlphaType:
674 testName += " (premul)";
675 break;
676 default:
677 SkUNREACHABLE;
678 }
679
680 for (int i = 0; i < 4; i++) {
681 REPORTER_ASSERT(r,
682 pixel[i] == expected[i],
683 "%s: Byte #%d: actual = %d; expected = %d",
684 testName.c_str(),
685 i,
686 pixel[i],
687 expected[i]);
688 }
689 }
690
DEF_TEST(Codec_png_plte_trns,r)691 DEF_TEST(Codec_png_plte_trns, r) {
692 auto t = verifyFirstFourDecodedBytes; // Help with succint formatting below...
693
694 // RGB in `PLTE` chunk is: 100 (0x64), 150 (0x96), 200 (0xC8)
695 // Alpha in `tRNS` chunk is: 64 (i.e. 25% or 0x40)
696 //
697 // After alpha premultiplication by 25% we should get: R=25, G=38, B=50.
698 t(r, "plte_trns.png", kRGBA_8888_SkColorType, kUnpremul_SkAlphaType, {100, 150, 200, 64});
699 t(r, "plte_trns.png", kBGRA_8888_SkColorType, kUnpremul_SkAlphaType, {200, 150, 100, 64});
700 t(r, "plte_trns.png", kRGBA_8888_SkColorType, kPremul_SkAlphaType, {25, 38, 50, 64});
701 t(r, "plte_trns.png", kBGRA_8888_SkColorType, kPremul_SkAlphaType, {50, 38, 25, 64});
702 }
703
DEF_TEST(Codec_png_plte_trns_gama,r)704 DEF_TEST(Codec_png_plte_trns_gama, r) {
705 auto t = verifyFirstFourDecodedBytes; // Help with succint formatting below...
706
707 // RGB in `PLTE` chunk is: 100 (0x64), 150 (0x96), 200 (0xC8)
708 // Alpha in `tRNS` chunk is: 64 (i.e. 25% or 0x40)
709 //
710 // After `gAMA` transformation we should get: R=161, G=197, B=227.
711 //
712 // After alpha premultiplication by 25% we should get: R=40, G=49, B=57.
713 t(r, "plte_trns_gama.png", kRGBA_8888_SkColorType, kUnpremul_SkAlphaType, {161, 197, 227, 64});
714 t(r, "plte_trns_gama.png", kBGRA_8888_SkColorType, kUnpremul_SkAlphaType, {227, 197, 161, 64});
715 t(r, "plte_trns_gama.png", kRGBA_8888_SkColorType, kPremul_SkAlphaType, {40, 49, 57, 64});
716 t(r, "plte_trns_gama.png", kBGRA_8888_SkColorType, kPremul_SkAlphaType, {57, 49, 40, 64});
717 }
718
719 // Disable RAW tests for Win32.
720 #if defined(SK_CODEC_DECODES_RAW) && !defined(_WIN32)
DEF_TEST(Codec_raw,r)721 DEF_TEST(Codec_raw, r) {
722 check(r, "images/sample_1mp.dng", SkISize::Make(600, 338), false, false, false);
723 check(r, "images/sample_1mp_rotated.dng", SkISize::Make(600, 338), false, false, false);
724 check(r, "images/dng_with_preview.dng", SkISize::Make(600, 338), true, false, false);
725 }
726 #endif
727
test_invalid_stream(skiatest::Reporter * r,const void * stream,size_t len)728 static void test_invalid_stream(skiatest::Reporter* r, const void* stream, size_t len) {
729 // Neither of these calls should return a codec. Bots should catch us if we leaked anything.
730 REPORTER_ASSERT(r, !SkCodec::MakeFromStream(
731 std::make_unique<SkMemoryStream>(stream, len, false)));
732 REPORTER_ASSERT(r, !SkAndroidCodec::MakeFromStream(
733 std::make_unique<SkMemoryStream>(stream, len, false)));
734 }
735
736 // Ensure that SkCodec::NewFromStream handles freeing the passed in SkStream,
737 // even on failure. Test some bad streams.
DEF_TEST(Codec_leaks,r)738 DEF_TEST(Codec_leaks, r) {
739 // No codec should claim this as their format, so this tests SkCodec::NewFromStream.
740 const char nonSupportedStream[] = "hello world";
741 // The other strings should look like the beginning of a file type, so we'll call some
742 // internal version of NewFromStream, which must also delete the stream on failure.
743 const unsigned char emptyPng[] = { 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a };
744 const unsigned char emptyJpeg[] = { 0xFF, 0xD8, 0xFF };
745 const char emptyWebp[] = "RIFF1234WEBPVP";
746 const char emptyBmp[] = { 'B', 'M' };
747 const char emptyIco[] = { '\x00', '\x00', '\x01', '\x00' };
748 const char emptyGif[] = "GIFVER";
749
750 test_invalid_stream(r, nonSupportedStream, sizeof(nonSupportedStream));
751 test_invalid_stream(r, emptyPng, sizeof(emptyPng));
752 test_invalid_stream(r, emptyJpeg, sizeof(emptyJpeg));
753 test_invalid_stream(r, emptyWebp, sizeof(emptyWebp));
754 test_invalid_stream(r, emptyBmp, sizeof(emptyBmp));
755 test_invalid_stream(r, emptyIco, sizeof(emptyIco));
756 test_invalid_stream(r, emptyGif, sizeof(emptyGif));
757 }
758
DEF_TEST(Codec_null,r)759 DEF_TEST(Codec_null, r) {
760 // Attempting to create an SkCodec or an SkAndroidCodec with null should not
761 // crash.
762 REPORTER_ASSERT(r, !SkCodec::MakeFromStream(nullptr));
763 REPORTER_ASSERT(r, !SkAndroidCodec::MakeFromStream(nullptr));
764 }
765
test_dimensions(skiatest::Reporter * r,const char path[])766 static void test_dimensions(skiatest::Reporter* r, const char path[]) {
767 // Create the codec from the resource file
768 std::unique_ptr<SkStream> stream(GetResourceAsStream(path));
769 if (!stream) {
770 return;
771 }
772 std::unique_ptr<SkAndroidCodec> codec(SkAndroidCodec::MakeFromStream(std::move(stream)));
773 if (!codec) {
774 ERRORF(r, "Unable to create codec '%s'", path);
775 return;
776 }
777
778 // Check that the decode is successful for a variety of scales
779 for (int sampleSize = 1; sampleSize < 32; sampleSize++) {
780 // Scale the output dimensions
781 SkISize scaledDims = codec->getSampledDimensions(sampleSize);
782 SkImageInfo scaledInfo = codec->getInfo()
783 .makeDimensions(scaledDims)
784 .makeColorType(kN32_SkColorType);
785
786 // Set up for the decode
787 size_t rowBytes = scaledDims.width() * sizeof(SkPMColor);
788 size_t totalBytes = scaledInfo.computeByteSize(rowBytes);
789 AutoTMalloc<SkPMColor> pixels(totalBytes);
790
791 SkAndroidCodec::AndroidOptions options;
792 options.fSampleSize = sampleSize;
793 SkCodec::Result result =
794 codec->getAndroidPixels(scaledInfo, pixels.get(), rowBytes, &options);
795 if (result != SkCodec::kSuccess) {
796 ERRORF(r, "Failed to decode %s with sample size %i; error: %s", path, sampleSize,
797 SkCodec::ResultToString(result));
798 }
799 }
800 }
801
802 // Ensure that onGetScaledDimensions returns valid image dimensions to use for decodes
DEF_TEST(Codec_Dimensions,r)803 DEF_TEST(Codec_Dimensions, r) {
804 // JPG
805 test_dimensions(r, "images/CMYK.jpg");
806 test_dimensions(r, "images/color_wheel.jpg");
807 test_dimensions(r, "images/grayscale.jpg");
808 test_dimensions(r, "images/mandrill_512_q075.jpg");
809 test_dimensions(r, "images/randPixels.jpg");
810
811 // Decoding small images with very large scaling factors is a potential
812 // source of bugs and crashes. We disable these tests in Gold because
813 // tiny images are not very useful to look at.
814 // Here we make sure that we do not crash or access illegal memory when
815 // performing scaled decodes on small images.
816 test_dimensions(r, "images/1x1.png");
817 test_dimensions(r, "images/2x2.png");
818 test_dimensions(r, "images/3x3.png");
819 test_dimensions(r, "images/3x1.png");
820 test_dimensions(r, "images/1x1.png");
821 test_dimensions(r, "images/16x1.png");
822 test_dimensions(r, "images/1x16.png");
823 test_dimensions(r, "images/mandrill_16.png");
824
825 // RAW
826 // Disable RAW tests for Win32.
827 #if defined(SK_CODEC_DECODES_RAW) && !defined(_WIN32)
828 test_dimensions(r, "images/sample_1mp.dng");
829 test_dimensions(r, "images/sample_1mp_rotated.dng");
830 test_dimensions(r, "images/dng_with_preview.dng");
831 #endif
832 }
833
test_invalid(skiatest::Reporter * r,const char path[])834 static void test_invalid(skiatest::Reporter* r, const char path[]) {
835 auto data = GetResourceAsData(path);
836 if (!data) {
837 ERRORF(r, "Failed to get resource %s", path);
838 return;
839 }
840
841 REPORTER_ASSERT(r, !SkCodec::MakeFromData(data));
842 }
843
DEF_TEST(Codec_Empty,r)844 DEF_TEST(Codec_Empty, r) {
845 if (GetResourcePath().isEmpty()) {
846 return;
847 }
848
849 // Test images that should not be able to create a codec
850 test_invalid(r, "empty_images/zero-dims.gif");
851 test_invalid(r, "empty_images/zero-embedded.ico");
852 test_invalid(r, "empty_images/zero-width.bmp");
853 test_invalid(r, "empty_images/zero-height.bmp");
854 test_invalid(r, "empty_images/zero-width.jpg");
855 test_invalid(r, "empty_images/zero-height.jpg");
856 test_invalid(r, "empty_images/zero-width.png");
857 test_invalid(r, "empty_images/zero-height.png");
858 test_invalid(r, "empty_images/zero-width.wbmp");
859 test_invalid(r, "empty_images/zero-height.wbmp");
860 // This image is an ico with an embedded mask-bmp. This is illegal.
861 test_invalid(r, "invalid_images/mask-bmp-ico.ico");
862 // It is illegal for a webp frame to not be fully contained by the canvas.
863 test_invalid(r, "invalid_images/invalid-offset.webp");
864 #if defined(SK_CODEC_DECODES_RAW) && (!defined(_WIN32))
865 test_invalid(r, "empty_images/zero_height.tiff");
866 #endif
867 test_invalid(r, "invalid_images/b37623797.ico");
868 test_invalid(r, "invalid_images/osfuzz6295.webp");
869 test_invalid(r, "invalid_images/osfuzz6288.bmp");
870 test_invalid(r, "invalid_images/ossfuzz6347");
871 }
872
873 #ifdef PNG_READ_UNKNOWN_CHUNKS_SUPPORTED
874
875 #ifndef SK_PNG_DISABLE_TESTS // reading chunks does not work properly with older versions.
876 // It does not appear that anyone in Google3 is reading chunks.
877
codex_test_write_fn(png_structp png_ptr,png_bytep data,png_size_t len)878 static void codex_test_write_fn(png_structp png_ptr, png_bytep data, png_size_t len) {
879 SkWStream* sk_stream = (SkWStream*)png_get_io_ptr(png_ptr);
880 if (!sk_stream->write(data, len)) {
881 png_error(png_ptr, "sk_write_fn Error!");
882 }
883 }
884
DEF_TEST(Codec_pngChunkReader,r)885 DEF_TEST(Codec_pngChunkReader, r) {
886 // Create a bitmap for hashing. Use unpremul RGBA for libpng.
887 SkBitmap bm;
888 const int w = 1;
889 const int h = 1;
890 const SkImageInfo bmInfo = SkImageInfo::Make(w, h, kRGBA_8888_SkColorType,
891 kUnpremul_SkAlphaType);
892 bm.setInfo(bmInfo);
893 bm.allocPixels();
894 bm.eraseColor(SK_ColorBLUE);
895 SkMD5::Digest goodDigest = md5(bm);
896
897 // Write to a png file.
898 png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
899 REPORTER_ASSERT(r, png);
900 if (!png) {
901 return;
902 }
903
904 png_infop info = png_create_info_struct(png);
905 REPORTER_ASSERT(r, info);
906 if (!info) {
907 png_destroy_write_struct(&png, nullptr);
908 return;
909 }
910
911 if (setjmp(png_jmpbuf(png))) {
912 ERRORF(r, "failed writing png");
913 png_destroy_write_struct(&png, &info);
914 return;
915 }
916
917 SkDynamicMemoryWStream wStream;
918 png_set_write_fn(png, (void*) (&wStream), codex_test_write_fn, nullptr);
919
920 png_set_IHDR(png, info, (png_uint_32)w, (png_uint_32)h, 8,
921 PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE,
922 PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
923
924 #define PNG_BYTES(str) reinterpret_cast<png_byte*>(const_cast<char*>(str))
925
926 // Create some chunks that match the Android framework's use.
927 static png_unknown_chunk gUnknowns[] = {
928 { "npOl", PNG_BYTES("outline"), sizeof("outline"), PNG_HAVE_IHDR },
929 { "npLb", PNG_BYTES("layoutBounds"), sizeof("layoutBounds"), PNG_HAVE_IHDR },
930 { "npTc", PNG_BYTES("ninePatchData"), sizeof("ninePatchData"), PNG_HAVE_IHDR },
931 };
932
933 png_set_keep_unknown_chunks(png, PNG_HANDLE_CHUNK_ALWAYS, PNG_BYTES("npOl\0npLb\0npTc\0"), 3);
934 png_set_unknown_chunks(png, info, gUnknowns, std::size(gUnknowns));
935 #if PNG_LIBPNG_VER < 10600
936 /* Deal with unknown chunk location bug in 1.5.x and earlier */
937 png_set_unknown_chunk_location(png, info, 0, PNG_HAVE_IHDR);
938 png_set_unknown_chunk_location(png, info, 1, PNG_HAVE_IHDR);
939 #endif
940
941 png_write_info(png, info);
942
943 for (int j = 0; j < h; j++) {
944 png_bytep row = (png_bytep)(bm.getAddr(0, j));
945 png_write_rows(png, &row, 1);
946 }
947 png_write_end(png, info);
948 png_destroy_write_struct(&png, &info);
949
950 class ChunkReader : public SkPngChunkReader {
951 public:
952 ChunkReader(skiatest::Reporter* r)
953 : fReporter(r)
954 {
955 this->reset();
956 }
957
958 bool readChunk(const char tag[], const void* data, size_t length) override {
959 for (size_t i = 0; i < std::size(gUnknowns); ++i) {
960 if (!strcmp(tag, (const char*) gUnknowns[i].name)) {
961 // Tag matches. This should have been the first time we see it.
962 REPORTER_ASSERT(fReporter, !fSeen[i]);
963 fSeen[i] = true;
964
965 // Data and length should match
966 REPORTER_ASSERT(fReporter, length == gUnknowns[i].size);
967 REPORTER_ASSERT(fReporter, !strcmp((const char*) data,
968 (const char*) gUnknowns[i].data));
969 return true;
970 }
971 }
972 ERRORF(fReporter, "Saw an unexpected unknown chunk.");
973 return true;
974 }
975
976 bool allHaveBeenSeen() {
977 bool ret = true;
978 for (auto seen : fSeen) {
979 ret &= seen;
980 }
981 return ret;
982 }
983
984 void reset() {
985 sk_bzero(fSeen, sizeof(fSeen));
986 }
987
988 private:
989 skiatest::Reporter* fReporter; // Unowned
990 bool fSeen[3];
991 };
992
993 ChunkReader chunkReader(r);
994
995 // Now read the file with SkCodec.
996 std::unique_ptr<SkCodec> codec(SkCodec::MakeFromData(wStream.detachAsData(), &chunkReader));
997 REPORTER_ASSERT(r, codec);
998 if (!codec) {
999 return;
1000 }
1001
1002 // Now compare to the original.
1003 SkBitmap decodedBm;
1004 decodedBm.setInfo(codec->getInfo());
1005 decodedBm.allocPixels();
1006 SkCodec::Result result = codec->getPixels(codec->getInfo(), decodedBm.getPixels(),
1007 decodedBm.rowBytes());
1008 REPORTER_ASSERT(r, SkCodec::kSuccess == result);
1009
1010 if (decodedBm.colorType() != bm.colorType()) {
1011 SkBitmap tmp;
1012 bool success = ToolUtils::copy_to(&tmp, bm.colorType(), decodedBm);
1013 REPORTER_ASSERT(r, success);
1014 if (!success) {
1015 return;
1016 }
1017
1018 tmp.swap(decodedBm);
1019 }
1020
1021 compare_to_good_digest(r, goodDigest, decodedBm);
1022 REPORTER_ASSERT(r, chunkReader.allHaveBeenSeen());
1023
1024 // Decoding again will read the chunks again.
1025 chunkReader.reset();
1026 REPORTER_ASSERT(r, !chunkReader.allHaveBeenSeen());
1027 result = codec->getPixels(codec->getInfo(), decodedBm.getPixels(), decodedBm.rowBytes());
1028 REPORTER_ASSERT(r, SkCodec::kSuccess == result);
1029 REPORTER_ASSERT(r, chunkReader.allHaveBeenSeen());
1030 }
1031 #endif // SK_PNG_DISABLE_TESTS
1032 #endif // PNG_READ_UNKNOWN_CHUNKS_SUPPORTED
1033
1034 // Stream that can only peek up to a limit
1035 class LimitedPeekingMemStream : public SkStream {
1036 public:
LimitedPeekingMemStream(sk_sp<SkData> data,size_t limit)1037 LimitedPeekingMemStream(sk_sp<SkData> data, size_t limit)
1038 : fStream(std::move(data))
1039 , fLimit(limit) {}
1040
peek(void * buf,size_t bytes) const1041 size_t peek(void* buf, size_t bytes) const override {
1042 return fStream.peek(buf, std::min(bytes, fLimit));
1043 }
read(void * buf,size_t bytes)1044 size_t read(void* buf, size_t bytes) override {
1045 return fStream.read(buf, bytes);
1046 }
rewind()1047 bool rewind() override {
1048 return fStream.rewind();
1049 }
isAtEnd() const1050 bool isAtEnd() const override {
1051 return fStream.isAtEnd();
1052 }
1053 private:
1054 SkMemoryStream fStream;
1055 const size_t fLimit;
1056 };
1057
1058 // Disable RAW tests for Win32.
1059 #if defined(SK_CODEC_DECODES_RAW) && !defined(_WIN32)
1060 // Test that the RawCodec works also for not asset stream. This will test the code path using
1061 // SkRawBufferedStream instead of SkRawAssetStream.
DEF_TEST(Codec_raw_notseekable,r)1062 DEF_TEST(Codec_raw_notseekable, r) {
1063 constexpr char path[] = "images/dng_with_preview.dng";
1064 sk_sp<SkData> data(GetResourceAsData(path));
1065 if (!data) {
1066 SkDebugf("Missing resource '%s'\n", path);
1067 return;
1068 }
1069
1070 std::unique_ptr<SkCodec> codec(SkCodec::MakeFromStream(
1071 std::make_unique<NotAssetMemStream>(std::move(data))));
1072 REPORTER_ASSERT(r, codec);
1073
1074 test_info(r, codec.get(), codec->getInfo(), SkCodec::kSuccess, nullptr);
1075 }
1076 #endif
1077
1078 // Test that even if webp_parse_header fails to peek enough, it will fall back to read()
1079 // + rewind() and succeed.
DEF_TEST(Codec_webp_peek,r)1080 DEF_TEST(Codec_webp_peek, r) {
1081 constexpr char path[] = "images/baby_tux.webp";
1082 auto data = GetResourceAsData(path);
1083 if (!data) {
1084 SkDebugf("Missing resource '%s'\n", path);
1085 return;
1086 }
1087
1088 // The limit is less than webp needs to peek or read.
1089 std::unique_ptr<SkCodec> codec(SkCodec::MakeFromStream(
1090 std::make_unique<LimitedPeekingMemStream>(data, 25)));
1091 REPORTER_ASSERT(r, codec);
1092
1093 test_info(r, codec.get(), codec->getInfo(), SkCodec::kSuccess, nullptr);
1094
1095 // Similarly, a stream which does not peek should still succeed.
1096 codec = SkCodec::MakeFromStream(std::make_unique<LimitedPeekingMemStream>(data, 0));
1097 REPORTER_ASSERT(r, codec);
1098
1099 test_info(r, codec.get(), codec->getInfo(), SkCodec::kSuccess, nullptr);
1100 }
1101
1102 // SkCodec's wbmp decoder was initially unnecessarily restrictive.
1103 // It required the second byte to be zero. The wbmp specification allows
1104 // a couple of bits to be 1 (so long as they do not overlap with 0x9F).
1105 // Test that SkCodec now supports an image with these bits set.
DEF_TEST(Codec_wbmp_restrictive,r)1106 DEF_TEST(Codec_wbmp_restrictive, r) {
1107 const char* path = "images/mandrill.wbmp";
1108 std::unique_ptr<SkStream> stream(GetResourceAsStream(path));
1109 if (!stream) {
1110 return;
1111 }
1112
1113 // Modify the stream to contain a second byte with some bits set.
1114 auto data = SkCopyStreamToData(stream.get());
1115 uint8_t* writeableData = static_cast<uint8_t*>(data->writable_data());
1116 writeableData[1] = static_cast<uint8_t>(~0x9F);
1117
1118 // SkCodec should support this.
1119 std::unique_ptr<SkCodec> codec(SkCodec::MakeFromData(data));
1120 REPORTER_ASSERT(r, codec);
1121 if (!codec) {
1122 return;
1123 }
1124 test_info(r, codec.get(), codec->getInfo(), SkCodec::kSuccess, nullptr);
1125 }
1126
1127 // wbmp images have a header that can be arbitrarily large, depending on the
1128 // size of the image. We cap the size at 65535, meaning we only need to look at
1129 // 8 bytes to determine whether we can read the image. This is important
1130 // because SkCodec only passes a limited number of bytes to SkWbmpCodec to
1131 // determine whether the image is a wbmp.
DEF_TEST(Codec_wbmp_max_size,r)1132 DEF_TEST(Codec_wbmp_max_size, r) {
1133 const unsigned char maxSizeWbmp[] = { 0x00, 0x00, // Header
1134 0x83, 0xFF, 0x7F, // W: 65535
1135 0x83, 0xFF, 0x7F }; // H: 65535
1136 std::unique_ptr<SkStream> stream(new SkMemoryStream(maxSizeWbmp, sizeof(maxSizeWbmp), false));
1137 std::unique_ptr<SkCodec> codec(SkCodec::MakeFromStream(std::move(stream)));
1138
1139 REPORTER_ASSERT(r, codec);
1140 if (!codec) return;
1141
1142 REPORTER_ASSERT(r, codec->getInfo().width() == 65535);
1143 REPORTER_ASSERT(r, codec->getInfo().height() == 65535);
1144
1145 // Now test an image which is too big. Any image with a larger header (i.e.
1146 // has bigger width/height) is also too big.
1147 const unsigned char tooBigWbmp[] = { 0x00, 0x00, // Header
1148 0x84, 0x80, 0x00, // W: 65536
1149 0x84, 0x80, 0x00 }; // H: 65536
1150 stream = std::make_unique<SkMemoryStream>(tooBigWbmp, sizeof(tooBigWbmp), false);
1151 codec = SkCodec::MakeFromStream(std::move(stream));
1152
1153 REPORTER_ASSERT(r, !codec);
1154 }
1155
DEF_TEST(Codec_jpeg_rewind,r)1156 DEF_TEST(Codec_jpeg_rewind, r) {
1157 const char* path = "images/mandrill_512_q075.jpg";
1158 sk_sp<SkData> data(GetResourceAsData(path));
1159 if (!data) {
1160 return;
1161 }
1162
1163 data = SkData::MakeSubset(data.get(), 0, data->size() / 2);
1164 std::unique_ptr<SkAndroidCodec> codec(SkAndroidCodec::MakeFromData(data));
1165 if (!codec) {
1166 ERRORF(r, "Unable to create codec '%s'.", path);
1167 return;
1168 }
1169
1170 const int width = codec->getInfo().width();
1171 const int height = codec->getInfo().height();
1172 size_t rowBytes = sizeof(SkPMColor) * width;
1173 SkAutoMalloc pixelStorage(height * rowBytes);
1174
1175 // Perform a sampled decode.
1176 SkAndroidCodec::AndroidOptions opts;
1177 opts.fSampleSize = 12;
1178 auto sampledInfo = codec->getInfo().makeWH(width / 12, height / 12);
1179 auto result = codec->getAndroidPixels(sampledInfo, pixelStorage.get(), rowBytes, &opts);
1180 REPORTER_ASSERT(r, SkCodec::kIncompleteInput == result);
1181
1182 // Rewind the codec and perform a full image decode.
1183 result = codec->getPixels(codec->getInfo(), pixelStorage.get(), rowBytes);
1184 REPORTER_ASSERT(r, SkCodec::kIncompleteInput == result);
1185
1186 // Now perform a subset decode.
1187 {
1188 opts.fSampleSize = 1;
1189 SkIRect subset = SkIRect::MakeWH(100, 100);
1190 opts.fSubset = ⊂
1191 result = codec->getAndroidPixels(codec->getInfo().makeWH(100, 100), pixelStorage.get(),
1192 rowBytes, &opts);
1193 // Though we only have half the data, it is enough to decode this subset.
1194 REPORTER_ASSERT(r, SkCodec::kSuccess == result);
1195 }
1196
1197 // Perform another full image decode. ASAN will detect if we look at the subset when it is
1198 // out of scope. This would happen if we depend on the old state in the codec.
1199 // This tests two layers of bugs: both SkJpegCodec::readRows and SkCodec::fillIncompleteImage
1200 // used to look at the old subset.
1201 opts.fSubset = nullptr;
1202 result = codec->getAndroidPixels(codec->getInfo(), pixelStorage.get(), rowBytes, &opts);
1203 REPORTER_ASSERT(r, SkCodec::kIncompleteInput == result);
1204 }
1205
check_color_xform(skiatest::Reporter * r,const char * path)1206 static void check_color_xform(skiatest::Reporter* r, const char* path) {
1207 std::unique_ptr<SkAndroidCodec> codec(SkAndroidCodec::MakeFromStream(GetResourceAsStream(path)));
1208
1209 SkAndroidCodec::AndroidOptions opts;
1210 opts.fSampleSize = 3;
1211 const int subsetWidth = codec->getInfo().width() / 2;
1212 const int subsetHeight = codec->getInfo().height() / 2;
1213 SkIRect subset = SkIRect::MakeWH(subsetWidth, subsetHeight);
1214 opts.fSubset = ⊂
1215
1216 const int dstWidth = subsetWidth / opts.fSampleSize;
1217 const int dstHeight = subsetHeight / opts.fSampleSize;
1218 auto colorSpace = SkColorSpace::MakeRGB(SkNamedTransferFn::k2Dot2, SkNamedGamut::kAdobeRGB);
1219 SkImageInfo dstInfo = codec->getInfo().makeWH(dstWidth, dstHeight)
1220 .makeColorType(kN32_SkColorType)
1221 .makeColorSpace(colorSpace);
1222
1223 size_t rowBytes = dstInfo.minRowBytes();
1224 SkAutoMalloc pixelStorage(dstInfo.computeByteSize(rowBytes));
1225 SkCodec::Result result = codec->getAndroidPixels(dstInfo, pixelStorage.get(), rowBytes, &opts);
1226 REPORTER_ASSERT(r, SkCodec::kSuccess == result);
1227 }
1228
DEF_TEST(Codec_ColorXform,r)1229 DEF_TEST(Codec_ColorXform, r) {
1230 check_color_xform(r, "images/mandrill_512_q075.jpg");
1231 check_color_xform(r, "images/mandrill_512.png");
1232 }
1233
color_type_match(SkColorType origColorType,SkColorType codecColorType)1234 static bool color_type_match(SkColorType origColorType, SkColorType codecColorType) {
1235 switch (origColorType) {
1236 case kRGBA_8888_SkColorType:
1237 case kBGRA_8888_SkColorType:
1238 return kRGBA_8888_SkColorType == codecColorType ||
1239 kBGRA_8888_SkColorType == codecColorType;
1240 default:
1241 return origColorType == codecColorType;
1242 }
1243 }
1244
alpha_type_match(SkAlphaType origAlphaType,SkAlphaType codecAlphaType)1245 static bool alpha_type_match(SkAlphaType origAlphaType, SkAlphaType codecAlphaType) {
1246 switch (origAlphaType) {
1247 case kUnpremul_SkAlphaType:
1248 case kPremul_SkAlphaType:
1249 return kUnpremul_SkAlphaType == codecAlphaType ||
1250 kPremul_SkAlphaType == codecAlphaType;
1251 default:
1252 return origAlphaType == codecAlphaType;
1253 }
1254 }
1255
check_round_trip(skiatest::Reporter * r,SkCodec * origCodec,const SkImageInfo & info)1256 static void check_round_trip(skiatest::Reporter* r, SkCodec* origCodec, const SkImageInfo& info) {
1257 SkBitmap bm1;
1258 bm1.allocPixels(info);
1259 SkCodec::Result result = origCodec->getPixels(info, bm1.getPixels(), bm1.rowBytes());
1260 REPORTER_ASSERT(r, SkCodec::kSuccess == result);
1261
1262 // Encode the image to png.
1263 SkDynamicMemoryWStream stream;
1264 SkASSERT_RELEASE(SkPngEncoder::Encode(&stream, bm1.pixmap(), {}));
1265
1266 std::unique_ptr<SkCodec> codec(SkCodec::MakeFromData(stream.detachAsData()));
1267 REPORTER_ASSERT(r, color_type_match(info.colorType(), codec->getInfo().colorType()));
1268 REPORTER_ASSERT(r, alpha_type_match(info.alphaType(), codec->getInfo().alphaType()));
1269
1270 SkBitmap bm2;
1271 bm2.allocPixels(info);
1272 result = codec->getPixels(info, bm2.getPixels(), bm2.rowBytes());
1273 REPORTER_ASSERT(r, SkCodec::kSuccess == result);
1274
1275 REPORTER_ASSERT(r, md5(bm1) == md5(bm2));
1276 }
1277
DEF_TEST(Codec_PngRoundTrip,r)1278 DEF_TEST(Codec_PngRoundTrip, r) {
1279 auto codec = SkCodec::MakeFromStream(GetResourceAsStream("images/mandrill_512_q075.jpg"));
1280
1281 SkColorType colorTypesOpaque[] = {
1282 kRGB_565_SkColorType, kRGBA_8888_SkColorType, kBGRA_8888_SkColorType
1283 };
1284 for (SkColorType colorType : colorTypesOpaque) {
1285 SkImageInfo newInfo = codec->getInfo().makeColorType(colorType);
1286 check_round_trip(r, codec.get(), newInfo);
1287 }
1288
1289 codec = SkCodec::MakeFromStream(GetResourceAsStream("images/grayscale.jpg"));
1290 check_round_trip(r, codec.get(), codec->getInfo());
1291
1292 codec = SkCodec::MakeFromStream(GetResourceAsStream("images/yellow_rose.png"));
1293
1294 SkColorType colorTypesWithAlpha[] = {
1295 kRGBA_8888_SkColorType, kBGRA_8888_SkColorType
1296 };
1297 SkAlphaType alphaTypes[] = {
1298 kUnpremul_SkAlphaType, kPremul_SkAlphaType
1299 };
1300 for (SkColorType colorType : colorTypesWithAlpha) {
1301 for (SkAlphaType alphaType : alphaTypes) {
1302 // Set color space to nullptr because color correct premultiplies do not round trip.
1303 SkImageInfo newInfo = codec->getInfo().makeColorType(colorType)
1304 .makeAlphaType(alphaType)
1305 .makeColorSpace(nullptr);
1306 check_round_trip(r, codec.get(), newInfo);
1307 }
1308 }
1309
1310 codec = SkCodec::MakeFromStream(GetResourceAsStream("images/index8.png"));
1311
1312 for (SkAlphaType alphaType : alphaTypes) {
1313 SkImageInfo newInfo = codec->getInfo().makeAlphaType(alphaType)
1314 .makeColorSpace(nullptr);
1315 check_round_trip(r, codec.get(), newInfo);
1316 }
1317 }
1318
test_conversion_possible(skiatest::Reporter * r,const char * path,bool supportsScanlineDecoder,bool supportsIncrementalDecoder)1319 static void test_conversion_possible(skiatest::Reporter* r, const char* path,
1320 bool supportsScanlineDecoder,
1321 bool supportsIncrementalDecoder) {
1322 std::unique_ptr<SkStream> stream(GetResourceAsStream(path));
1323 if (!stream) {
1324 return;
1325 }
1326
1327 std::unique_ptr<SkCodec> codec(SkCodec::MakeFromStream(std::move(stream)));
1328 if (!codec) {
1329 ERRORF(r, "failed to create a codec for %s", path);
1330 return;
1331 }
1332
1333 SkImageInfo infoF16 = codec->getInfo().makeColorType(kRGBA_F16_SkColorType);
1334
1335 SkBitmap bm;
1336 bm.allocPixels(infoF16);
1337 SkCodec::Result result = codec->getPixels(infoF16, bm.getPixels(), bm.rowBytes());
1338 REPORTER_ASSERT(r, SkCodec::kSuccess == result);
1339
1340 result = codec->startScanlineDecode(infoF16);
1341 if (supportsScanlineDecoder) {
1342 REPORTER_ASSERT(r, SkCodec::kSuccess == result);
1343 } else {
1344 REPORTER_ASSERT(r, SkCodec::kUnimplemented == result
1345 || SkCodec::kSuccess == result);
1346 }
1347
1348 result = codec->startIncrementalDecode(infoF16, bm.getPixels(), bm.rowBytes());
1349 if (supportsIncrementalDecoder) {
1350 REPORTER_ASSERT(r, SkCodec::kSuccess == result);
1351 } else {
1352 REPORTER_ASSERT(r, SkCodec::kUnimplemented == result
1353 || SkCodec::kSuccess == result);
1354 }
1355
1356 infoF16 = infoF16.makeColorSpace(infoF16.colorSpace()->makeLinearGamma());
1357 result = codec->getPixels(infoF16, bm.getPixels(), bm.rowBytes());
1358 REPORTER_ASSERT(r, SkCodec::kSuccess == result);
1359 result = codec->startScanlineDecode(infoF16);
1360 if (supportsScanlineDecoder) {
1361 REPORTER_ASSERT(r, SkCodec::kSuccess == result);
1362 } else {
1363 REPORTER_ASSERT(r, SkCodec::kUnimplemented == result);
1364 }
1365
1366 result = codec->startIncrementalDecode(infoF16, bm.getPixels(), bm.rowBytes());
1367 if (supportsIncrementalDecoder) {
1368 REPORTER_ASSERT(r, SkCodec::kSuccess == result);
1369 } else {
1370 REPORTER_ASSERT(r, SkCodec::kUnimplemented == result);
1371 }
1372 }
1373
DEF_TEST(Codec_F16ConversionPossible,r)1374 DEF_TEST(Codec_F16ConversionPossible, r) {
1375 test_conversion_possible(r, "images/color_wheel.webp", false, false);
1376 test_conversion_possible(r, "images/mandrill_512_q075.jpg", true, false);
1377 test_conversion_possible(r, "images/yellow_rose.png", false, true);
1378 }
1379
decode_frame(skiatest::Reporter * r,SkCodec * codec,size_t frame)1380 static void decode_frame(skiatest::Reporter* r, SkCodec* codec, size_t frame) {
1381 SkBitmap bm;
1382 auto info = codec->getInfo().makeColorType(kN32_SkColorType);
1383 bm.allocPixels(info);
1384
1385 SkCodec::Options opts;
1386 opts.fFrameIndex = frame;
1387 REPORTER_ASSERT(r, SkCodec::kSuccess == codec->getPixels(info,
1388 bm.getPixels(), bm.rowBytes(), &opts));
1389 }
1390
1391 // For an animated GIF, we should only read enough to decode frame 0 if the
1392 // client never calls getFrameInfo and only decodes frame 0.
DEF_TEST(Codec_skipFullParse,r)1393 DEF_TEST(Codec_skipFullParse, r) {
1394 auto path = "images/test640x479.gif";
1395 auto streamObj = GetResourceAsStream(path);
1396 if (!streamObj) {
1397 return;
1398 }
1399 SkStream* stream = streamObj.get();
1400
1401 // Note that we cheat and hold on to the stream pointer, but SkCodec will
1402 // take ownership. We will not refer to the stream after the SkCodec
1403 // deletes it.
1404 std::unique_ptr<SkCodec> codec(SkCodec::MakeFromStream(std::move(streamObj)));
1405 if (!codec) {
1406 ERRORF(r, "Failed to create codec for %s", path);
1407 return;
1408 }
1409
1410 REPORTER_ASSERT(r, stream->hasPosition());
1411 const size_t sizePosition = stream->getPosition();
1412 REPORTER_ASSERT(r, stream->hasLength() && sizePosition < stream->getLength());
1413
1414 // This should read more of the stream, but not the whole stream.
1415 decode_frame(r, codec.get(), 0);
1416 const size_t positionAfterFirstFrame = stream->getPosition();
1417 REPORTER_ASSERT(r, positionAfterFirstFrame > sizePosition
1418 && positionAfterFirstFrame < stream->getLength());
1419
1420 // There is more data in the stream.
1421 auto frameInfo = codec->getFrameInfo();
1422 REPORTER_ASSERT(r, frameInfo.size() == 4);
1423 REPORTER_ASSERT(r, stream->getPosition() > positionAfterFirstFrame);
1424 }
1425
1426 // Only rewinds up to a limit.
1427 class LimitedRewindingStream : public SkStream {
1428 public:
Make(const char path[],size_t limit)1429 static std::unique_ptr<SkStream> Make(const char path[], size_t limit) {
1430 auto stream = GetResourceAsStream(path);
1431 if (!stream) {
1432 return nullptr;
1433 }
1434 return std::unique_ptr<SkStream>(new LimitedRewindingStream(std::move(stream), limit));
1435 }
1436
read(void * buffer,size_t size)1437 size_t read(void* buffer, size_t size) override {
1438 const size_t bytes = fStream->read(buffer, size);
1439 fPosition += bytes;
1440 return bytes;
1441 }
1442
isAtEnd() const1443 bool isAtEnd() const override {
1444 return fStream->isAtEnd();
1445 }
1446
rewind()1447 bool rewind() override {
1448 if (fPosition <= fLimit && fStream->rewind()) {
1449 fPosition = 0;
1450 return true;
1451 }
1452
1453 return false;
1454 }
1455
1456 private:
1457 std::unique_ptr<SkStream> fStream;
1458 const size_t fLimit;
1459 size_t fPosition;
1460
LimitedRewindingStream(std::unique_ptr<SkStream> stream,size_t limit)1461 LimitedRewindingStream(std::unique_ptr<SkStream> stream, size_t limit)
1462 : fStream(std::move(stream))
1463 , fLimit(limit)
1464 , fPosition(0)
1465 {
1466 SkASSERT(fStream);
1467 }
1468 };
1469
DEF_TEST(Codec_fallBack,r)1470 DEF_TEST(Codec_fallBack, r) {
1471 // SkAndroidCodec needs to be able to fall back to scanline decoding
1472 // if incremental decoding does not work. Make sure this does not
1473 // require a rewind.
1474
1475 // Formats that currently do not support incremental decoding
1476 auto files = {
1477 "images/CMYK.jpg",
1478 "images/mandrill.wbmp",
1479 "images/randPixels.bmp",
1480 #if defined(SK_CODEC_DECODES_ICO)
1481 "images/color_wheel.ico",
1482 #endif
1483 };
1484 for (auto file : files) {
1485 auto stream = LimitedRewindingStream::Make(file, SkCodec::MinBufferedBytesNeeded());
1486 if (!stream) {
1487 SkDebugf("Missing resources (%s). Set --resourcePath.\n", file);
1488 return;
1489 }
1490
1491 std::unique_ptr<SkCodec> codec(SkCodec::MakeFromStream(std::move(stream)));
1492 if (!codec) {
1493 ERRORF(r, "Failed to create codec for %s,", file);
1494 continue;
1495 }
1496
1497 SkImageInfo info = codec->getInfo().makeColorType(kN32_SkColorType);
1498 SkBitmap bm;
1499 bm.allocPixels(info);
1500
1501 if (SkCodec::kUnimplemented != codec->startIncrementalDecode(info, bm.getPixels(),
1502 bm.rowBytes())) {
1503 ERRORF(r, "Is scanline decoding now implemented for %s?", file);
1504 continue;
1505 }
1506
1507 // Scanline decoding should not require a rewind.
1508 SkCodec::Result result = codec->startScanlineDecode(info);
1509 if (SkCodec::kSuccess != result) {
1510 ERRORF(r, "Scanline decoding failed for %s with %i", file, result);
1511 }
1512 }
1513 }
1514
seek_and_decode(const char * file,std::unique_ptr<SkStream> stream,skiatest::Reporter * r)1515 static void seek_and_decode(const char* file, std::unique_ptr<SkStream> stream,
1516 skiatest::Reporter* r) {
1517 if (!stream) {
1518 SkDebugf("Missing resources (%s). Set --resourcePath.\n", file);
1519 return;
1520 }
1521
1522 std::unique_ptr<SkCodec> codec(SkCodec::MakeFromStream(std::move(stream)));
1523 if (!codec) {
1524 ERRORF(r, "Failed to create codec for %s,", file);
1525 return;
1526 }
1527
1528 // Trigger reading through the stream, so that decoding the first frame will
1529 // require a rewind.
1530 (void) codec->getFrameCount();
1531
1532 SkImageInfo info = codec->getInfo().makeColorType(kN32_SkColorType);
1533 SkBitmap bm;
1534 bm.allocPixels(info);
1535 auto result = codec->getPixels(bm.pixmap());
1536 if (result != SkCodec::kSuccess) {
1537 ERRORF(r, "Failed to decode %s with error %s", file, SkCodec::ResultToString(result));
1538 }
1539 }
1540
DEF_TEST(Wuffs_seek_and_decode,r)1541 DEF_TEST(Wuffs_seek_and_decode, r) {
1542 const char* file = "images/flightAnim.gif";
1543 auto stream = LimitedRewindingStream::Make(file, SkCodec::MinBufferedBytesNeeded());
1544 seek_and_decode(file, std::move(stream), r);
1545
1546 #if defined(SK_ENABLE_ANDROID_UTILS)
1547 // Test using FrontBufferedStream, as Android does
1548 auto bufferedStream = android::skia::FrontBufferedStream::Make(
1549 GetResourceAsStream(file), SkCodec::MinBufferedBytesNeeded());
1550 seek_and_decode(file, std::move(bufferedStream), r);
1551 #endif
1552 }
1553
1554 // This test verifies that we fixed an assert statement that fired when reusing a png codec
1555 // after scaling.
DEF_TEST(Codec_reusePng,r)1556 DEF_TEST(Codec_reusePng, r) {
1557 std::unique_ptr<SkStream> stream(GetResourceAsStream("images/plane.png"));
1558 if (!stream) {
1559 return;
1560 }
1561
1562 std::unique_ptr<SkAndroidCodec> codec(SkAndroidCodec::MakeFromStream(std::move(stream)));
1563 if (!codec) {
1564 ERRORF(r, "Failed to create codec\n");
1565 return;
1566 }
1567
1568 SkAndroidCodec::AndroidOptions opts;
1569 opts.fSampleSize = 5;
1570 auto size = codec->getSampledDimensions(opts.fSampleSize);
1571 auto info = codec->getInfo().makeDimensions(size).makeColorType(kN32_SkColorType);
1572 SkBitmap bm;
1573 bm.allocPixels(info);
1574 auto result = codec->getAndroidPixels(info, bm.getPixels(), bm.rowBytes(), &opts);
1575 REPORTER_ASSERT(r, result == SkCodec::kSuccess);
1576
1577 info = codec->getInfo().makeColorType(kN32_SkColorType);
1578 bm.allocPixels(info);
1579 opts.fSampleSize = 1;
1580 result = codec->getAndroidPixels(info, bm.getPixels(), bm.rowBytes(), &opts);
1581 REPORTER_ASSERT(r, result == SkCodec::kSuccess);
1582 }
1583
DEF_TEST(Codec_rowsDecoded,r)1584 DEF_TEST(Codec_rowsDecoded, r) {
1585 auto file = "images/plane_interlaced.png";
1586 std::unique_ptr<SkStream> stream(GetResourceAsStream(file));
1587 if (!stream) {
1588 return;
1589 }
1590
1591 // This is enough to read the header etc, but no rows.
1592 std::unique_ptr<SkCodec> codec(SkCodec::MakeFromData(SkData::MakeFromStream(stream.get(), 99)));
1593 if (!codec) {
1594 ERRORF(r, "Failed to create codec\n");
1595 return;
1596 }
1597
1598 auto info = codec->getInfo().makeColorType(kN32_SkColorType);
1599 SkBitmap bm;
1600 bm.allocPixels(info);
1601 auto result = codec->startIncrementalDecode(info, bm.getPixels(), bm.rowBytes());
1602 REPORTER_ASSERT(r, result == SkCodec::kSuccess);
1603
1604 // This is an arbitrary value. The important fact is that it is not zero, and rowsDecoded
1605 // should get set to zero by incrementalDecode.
1606 int rowsDecoded = 77;
1607 result = codec->incrementalDecode(&rowsDecoded);
1608 REPORTER_ASSERT(r, result == SkCodec::kIncompleteInput);
1609 REPORTER_ASSERT(r, rowsDecoded == 0);
1610 }
1611
test_invalid_images(skiatest::Reporter * r,const char * path,SkCodec::Result expectedResult)1612 static void test_invalid_images(skiatest::Reporter* r, const char* path,
1613 SkCodec::Result expectedResult) {
1614 auto stream = GetResourceAsStream(path);
1615 if (!stream) {
1616 return;
1617 }
1618
1619 std::unique_ptr<SkCodec> codec(SkCodec::MakeFromStream(std::move(stream)));
1620 REPORTER_ASSERT(r, codec);
1621
1622 test_info(r, codec.get(), codec->getInfo().makeColorType(kN32_SkColorType), expectedResult,
1623 nullptr);
1624 }
1625
DEF_TEST(Codec_InvalidImages,r)1626 DEF_TEST(Codec_InvalidImages, r) {
1627 test_invalid_images(r, "invalid_images/b33251605.bmp", SkCodec::kIncompleteInput);
1628 test_invalid_images(r, "invalid_images/bad_palette.png", SkCodec::kInvalidInput);
1629 test_invalid_images(r, "invalid_images/many-progressive-scans.jpg", SkCodec::kInvalidInput);
1630
1631 // An earlier revision of this test case passed kErrorInInput (instead of
1632 // kSuccess) as the third argument (expectedResult). However, after
1633 // https://skia-review.googlesource.com/c/skia/+/414417 `SkWuffsCodec:
1634 // ignore too much pixel data` combined with
1635 // https://github.com/google/wuffs/commit/e44920d3 `Let gif "ignore too
1636 // much" quirk skip lzw errors`, the codec silently accepts skbug5887.gif
1637 // (without the ASAN buffer-overflow violation that lead to that test case
1638 // in the first place), even though it's technically an invalid GIF.
1639 //
1640 // Note that, in practice, real world GIF decoders already diverge (in
1641 // different ways) from the GIF specification. For compatibility, (ad hoc)
1642 // implementation often trumps specification.
1643 // https://github.com/google/wuffs/blob/e44920d3/test/data/artificial-gif/frame-out-of-bounds.gif.make-artificial.txt#L30-L31
1644 test_invalid_images(r, "invalid_images/skbug5887.gif", SkCodec::kSuccess);
1645 }
1646
test_invalid_header(skiatest::Reporter * r,const char * path)1647 static void test_invalid_header(skiatest::Reporter* r, const char* path) {
1648 auto data = GetResourceAsData(path);
1649 if (!data) {
1650 return;
1651 }
1652 std::unique_ptr<SkStreamAsset> stream(new SkMemoryStream(std::move(data)));
1653 if (!stream) {
1654 return;
1655 }
1656 std::unique_ptr<SkCodec> codec(SkCodec::MakeFromStream(std::move(stream)));
1657 REPORTER_ASSERT(r, !codec);
1658 }
1659
DEF_TEST(Codec_InvalidHeader,r)1660 DEF_TEST(Codec_InvalidHeader, r) {
1661 #if defined(SK_CODEC_DECODES_ICO)
1662 test_invalid_header(r, "invalid_images/int_overflow.ico");
1663 #endif
1664
1665 // These files report values that have caused problems with SkFILEStreams.
1666 // They are invalid, and should not create SkCodecs.
1667 test_invalid_header(r, "invalid_images/b33651913.bmp");
1668 test_invalid_header(r, "invalid_images/b34778578.bmp");
1669 }
1670
1671 /*
1672 For the Codec_InvalidAnimated test, immediately below,
1673 resources/invalid_images/skbug6046.gif is:
1674
1675 00000000: 4749 4638 3961 2000 0000 0000 002c ff00 GIF89a ......,..
1676 00000010: 7400 0600 0000 4001 0021 f904 0a00 0000 t.....@..!......
1677 00000020: 002c ff00 0000 ff00 7400 0606 0606 0601 .,......t.......
1678 00000030: 0021 f904 0000 0000 002c ff00 0000 ffcc .!.......,......
1679 00000040: 1b36 5266 deba 543d .6Rf..T=
1680
1681 It nominally contains 3 frames, but only the first one is valid. It came from a
1682 fuzzer doing random mutations and copies. The breakdown:
1683
1684 @000 6 bytes magic "GIF89a"
1685 @006 7 bytes Logical Screen Descriptor: 0x20 0x00 ... 0x00
1686 - width = 32
1687 - height = 0
1688 - flags = 0x00
1689 - background color index, pixel aspect ratio bytes ignored
1690 @00D 10 bytes Image Descriptor header: 0x2C 0xFF ... 0x40
1691 - origin_x = 255
1692 - origin_y = 116
1693 - width = 6
1694 - height = 0
1695 - flags = 0x40, interlaced
1696 @017 2 bytes Image Descriptor body (pixel data): 0x01 0x00
1697 - lit_width = 1
1698 - 0x00 byte means "end of data" for this frame
1699 @019 8 bytes Graphic Control Extension: 0x21 0xF9 ... 0x00
1700 - valid, but irrelevant here.
1701 @021 10 bytes Image Descriptor header: 0x2C 0xFF ... 0x06
1702 - origin_x = 255
1703 - origin_y = 0
1704 - width = 255
1705 - height = 116
1706 - flags = 0x06, INVALID, 0x80 BIT ZERO IMPLIES 0x07 BITS SHOULD BE ZERO
1707 @02B 14 bytes Image Descriptor body (pixel data): 0x06 0x06 ... 0x00
1708 - lit_width = 6
1709 - 0x06 precedes a 6 byte block of data
1710 - 0x04 precedes a 4 byte block of data
1711 - 0x00 byte means "end of data" for this frame
1712 @039 10 bytes Image Descriptor header: 0x2C 0xFF ... 0x06
1713 - origin_x = 255
1714 - origin_y = 0
1715 - width = 52479
1716 - height = 13851
1717 - flags = 0x52, INVALID, 0x80 BIT ZERO IMPLIES 0x07 BITS SHOULD BE ZERO
1718 @043 5 bytes Image Descriptor body (pixel data): 0x66 0xDE ... unexpected-EOF
1719 - lit_width = 102, INVALID, GREATER THAN 8
1720 - 0xDE precedes a 222 byte block of data, INVALIDLY TRUNCATED
1721
1722 On Image Descriptor flags INVALIDITY,
1723 https://www.w3.org/Graphics/GIF/spec-gif89a.txt section 20.c says that "Size of
1724 Local Color Table [the low 3 bits]... should be 0 if there is no Local Color
1725 Table specified [the high bit]."
1726
1727 On LZW literal width (also known as Minimum Code Size) INVALIDITY,
1728 https://www.w3.org/Graphics/GIF/spec-gif89a.txt Appendix F says that "Normally
1729 this will be the same as the number of [palette index] bits. Because of some
1730 algorithmic constraints however, black & white images which have one color bit
1731 must be indicated as having a code size of 2." In practice, some GIF decoders,
1732 including both the old third_party/gif code and the Wuffs GIF decoder, don't
1733 enforce this "at least 2" constraint. Nonetheless, any width greater than 8 is
1734 invalid, as there are only 8 bits in a byte.
1735 */
1736
DEF_TEST(Codec_InvalidAnimated,r)1737 DEF_TEST(Codec_InvalidAnimated, r) {
1738 // ASAN will complain if there is an issue.
1739 auto path = "invalid_images/skbug6046.gif";
1740 auto stream = GetResourceAsStream(path);
1741 if (!stream) {
1742 return;
1743 }
1744
1745 std::unique_ptr<SkCodec> codec(SkCodec::MakeFromStream(std::move(stream)));
1746 REPORTER_ASSERT(r, codec);
1747 if (!codec) {
1748 return;
1749 }
1750
1751 const auto info = codec->getInfo().makeColorType(kN32_SkColorType);
1752 SkBitmap bm;
1753 bm.allocPixels(info);
1754
1755 auto frameInfos = codec->getFrameInfo();
1756 SkCodec::Options opts;
1757 for (int i = 0; static_cast<size_t>(i) < frameInfos.size(); i++) {
1758 opts.fFrameIndex = i;
1759 const auto reqFrame = frameInfos[i].fRequiredFrame;
1760 opts.fPriorFrame = reqFrame == i - 1 ? reqFrame : SkCodec::kNoFrame;
1761 auto result = codec->startIncrementalDecode(info, bm.getPixels(), bm.rowBytes(), &opts);
1762
1763 if (result != SkCodec::kSuccess) {
1764 ERRORF(r, "Failed to start decoding frame %i (out of %zu) with error %i\n", i,
1765 frameInfos.size(), result);
1766 continue;
1767 }
1768
1769 codec->incrementalDecode();
1770 }
1771 }
1772
encode_format(SkDynamicMemoryWStream * stream,const SkPixmap & pixmap,SkEncodedImageFormat format)1773 static void encode_format(SkDynamicMemoryWStream* stream, const SkPixmap& pixmap,
1774 SkEncodedImageFormat format) {
1775 switch (format) {
1776 case SkEncodedImageFormat::kPNG:
1777 SkPngEncoder::Encode(stream, pixmap, SkPngEncoder::Options());
1778 break;
1779 case SkEncodedImageFormat::kJPEG:
1780 SkJpegEncoder::Encode(stream, pixmap, SkJpegEncoder::Options());
1781 break;
1782 case SkEncodedImageFormat::kWEBP:
1783 SkWebpEncoder::Encode(stream, pixmap, SkWebpEncoder::Options());
1784 break;
1785 default:
1786 SkASSERT(false);
1787 break;
1788 }
1789 }
1790
test_encode_icc(skiatest::Reporter * r,SkEncodedImageFormat format)1791 static void test_encode_icc(skiatest::Reporter* r, SkEncodedImageFormat format) {
1792 // Test with sRGB color space.
1793 SkBitmap srgbBitmap;
1794 SkImageInfo srgbInfo = SkImageInfo::MakeS32(1, 1, kOpaque_SkAlphaType);
1795 srgbBitmap.allocPixels(srgbInfo);
1796 *srgbBitmap.getAddr32(0, 0) = 0;
1797 SkPixmap pixmap;
1798 srgbBitmap.peekPixels(&pixmap);
1799 SkDynamicMemoryWStream srgbBuf;
1800 encode_format(&srgbBuf, pixmap, format);
1801 sk_sp<SkData> srgbData = srgbBuf.detachAsData();
1802 std::unique_ptr<SkCodec> srgbCodec(SkCodec::MakeFromData(srgbData));
1803 REPORTER_ASSERT(r, srgbCodec->getInfo().colorSpace() == sk_srgb_singleton());
1804
1805 // Test with P3 color space.
1806 SkDynamicMemoryWStream p3Buf;
1807 sk_sp<SkColorSpace> p3 = SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, SkNamedGamut::kDisplayP3);
1808 pixmap.setColorSpace(p3);
1809 encode_format(&p3Buf, pixmap, format);
1810 sk_sp<SkData> p3Data = p3Buf.detachAsData();
1811 std::unique_ptr<SkCodec> p3Codec(SkCodec::MakeFromData(p3Data));
1812 REPORTER_ASSERT(r, p3Codec->getInfo().colorSpace()->gammaCloseToSRGB());
1813 skcms_Matrix3x3 mat0, mat1;
1814 bool success = p3->toXYZD50(&mat0);
1815 REPORTER_ASSERT(r, success);
1816 success = p3Codec->getInfo().colorSpace()->toXYZD50(&mat1);
1817 REPORTER_ASSERT(r, success);
1818
1819 for (int i = 0; i < 3; i++) {
1820 for (int j = 0; j < 3; j++) {
1821 REPORTER_ASSERT(r, color_space_almost_equal(mat0.vals[i][j], mat1.vals[i][j]));
1822 }
1823 }
1824 }
1825
DEF_TEST(Codec_EncodeICC,r)1826 DEF_TEST(Codec_EncodeICC, r) {
1827 test_encode_icc(r, SkEncodedImageFormat::kPNG);
1828 test_encode_icc(r, SkEncodedImageFormat::kJPEG);
1829 test_encode_icc(r, SkEncodedImageFormat::kWEBP);
1830 }
1831
DEF_TEST(Codec_webp_rowsDecoded,r)1832 DEF_TEST(Codec_webp_rowsDecoded, r) {
1833 const char* path = "images/baby_tux.webp";
1834 sk_sp<SkData> data(GetResourceAsData(path));
1835 if (!data) {
1836 return;
1837 }
1838
1839 // Truncate this file so that the header is available but no rows can be
1840 // decoded. This should create a codec but fail to decode.
1841 size_t truncatedSize = 5000;
1842 sk_sp<SkData> subset = SkData::MakeSubset(data.get(), 0, truncatedSize);
1843 std::unique_ptr<SkCodec> codec = SkCodec::MakeFromData(std::move(subset));
1844 if (!codec) {
1845 ERRORF(r, "Failed to create a codec for %s truncated to only %zu bytes",
1846 path, truncatedSize);
1847 return;
1848 }
1849
1850 test_info(r, codec.get(), codec->getInfo(), SkCodec::kInvalidInput, nullptr);
1851 }
1852
1853 /*
1854 For the Codec_ossfuzz6274 test, immediately below,
1855 resources/invalid_images/ossfuzz6274.gif is:
1856
1857 00000000: 4749 4638 3961 2000 2000 f120 2020 2020 GIF89a . ..
1858 00000010: 2020 2020 2020 2020 2021 f903 ff20 2020 !...
1859 00000020: 002c 0000 0000 2000 2000 2000 00 .,.... . . ..
1860
1861 @000 6 bytes magic "GIF89a"
1862 @006 7 bytes Logical Screen Descriptor: 0x20 0x00 ... 0x00
1863 - width = 32
1864 - height = 32
1865 - flags = 0xF1, global color table, 4 RGB entries
1866 - background color index, pixel aspect ratio bytes ignored
1867 @00D 12 bytes Color Table: 0x20 0x20 ... 0x20
1868 @019 20 bytes Graphic Control Extension: 0x21 0xF9 ... unexpected-EOF
1869 - 0x03 precedes a 3 byte block of data, INVALID, MUST BE 4
1870 - 0x20 precedes a 32 byte block of data, INVALIDly truncated
1871
1872 https://www.w3.org/Graphics/GIF/spec-gif89a.txt section 23.c says that the
1873 block size (for an 0x21 0xF9 Graphic Control Extension) must be "the fixed
1874 value 4".
1875 */
1876
DEF_TEST(Codec_ossfuzz6274,r)1877 DEF_TEST(Codec_ossfuzz6274, r) {
1878 if (GetResourcePath().isEmpty()) {
1879 return;
1880 }
1881
1882 const char* file = "invalid_images/ossfuzz6274.gif";
1883 auto image = ToolUtils::GetResourceAsImage(file);
1884
1885 if (image) {
1886 ERRORF(r, "Invalid data gave non-nullptr image");
1887 }
1888 }
1889
DEF_TEST(Codec_78329453,r)1890 DEF_TEST(Codec_78329453, r) {
1891 if (GetResourcePath().isEmpty()) {
1892 return;
1893 }
1894
1895 const char* file = "images/b78329453.jpeg";
1896 auto data = GetResourceAsData(file);
1897 if (!data) {
1898 ERRORF(r, "Missing %s", file);
1899 return;
1900 }
1901
1902 auto codec = SkAndroidCodec::MakeFromCodec(SkCodec::MakeFromData(data));
1903 if (!codec) {
1904 ERRORF(r, "failed to create codec from %s", file);
1905 return;
1906 }
1907
1908 // A bug in jpeg_skip_scanlines resulted in an infinite loop for this specific
1909 // sample size on this image. Other sample sizes could have had the same result,
1910 // but the ones tested by DM happen to not.
1911 constexpr int kSampleSize = 19;
1912 const auto size = codec->getSampledDimensions(kSampleSize);
1913 auto info = codec->getInfo().makeDimensions(size);
1914 SkBitmap bm;
1915 bm.allocPixels(info);
1916 bm.eraseColor(SK_ColorTRANSPARENT);
1917
1918 SkAndroidCodec::AndroidOptions options;
1919 options.fSampleSize = kSampleSize;
1920 auto result = codec->getAndroidPixels(info, bm.getPixels(), bm.rowBytes(), &options);
1921 if (result != SkCodec::kSuccess) {
1922 ERRORF(r, "failed to decode with error %s", SkCodec::ResultToString(result));
1923 }
1924 }
1925
DEF_TEST(Codec_A8,r)1926 DEF_TEST(Codec_A8, r) {
1927 if (GetResourcePath().isEmpty()) {
1928 return;
1929 }
1930
1931 const char* file = "images/mandrill_cmyk.jpg";
1932 auto data = GetResourceAsData(file);
1933 if (!data) {
1934 ERRORF(r, "missing %s", file);
1935 return;
1936 }
1937
1938 auto codec = SkCodec::MakeFromData(std::move(data));
1939 auto info = codec->getInfo().makeColorType(kAlpha_8_SkColorType);
1940 SkBitmap bm;
1941 bm.allocPixels(info);
1942 REPORTER_ASSERT(r, codec->getPixels(bm.pixmap()) == SkCodec::kInvalidConversion);
1943 }
1944
DEF_TEST(Codec_crbug807324,r)1945 DEF_TEST(Codec_crbug807324, r) {
1946 if (GetResourcePath().isEmpty()) {
1947 return;
1948 }
1949
1950 const char* file = "images/crbug807324.png";
1951 auto image = ToolUtils::GetResourceAsImage(file);
1952 if (!image) {
1953 ERRORF(r, "Missing %s", file);
1954 return;
1955 }
1956
1957 const int kWidth = image->width();
1958 const int kHeight = image->height();
1959
1960 SkBitmap bm;
1961 if (!bm.tryAllocPixels(SkImageInfo::MakeN32Premul(kWidth, kHeight))) {
1962 ERRORF(r, "Could not allocate pixels (%i x %i)", kWidth, kHeight);
1963 return;
1964 }
1965
1966 bm.eraseColor(SK_ColorTRANSPARENT);
1967
1968 SkCanvas canvas(bm);
1969 canvas.drawImage(image, 0, 0);
1970
1971 for (int i = 0; i < kWidth; ++i)
1972 for (int j = 0; j < kHeight; ++j) {
1973 if (*bm.getAddr32(i, j) == SK_ColorTRANSPARENT) {
1974 ERRORF(r, "image should not be transparent! %i, %i is 0", i, j);
1975 return;
1976 }
1977 }
1978 }
1979
DEF_TEST(Codec_F16_noColorSpace,r)1980 DEF_TEST(Codec_F16_noColorSpace, r) {
1981 const char* path = "images/color_wheel.png";
1982 auto data = GetResourceAsData(path);
1983 if (!data) {
1984 return;
1985 }
1986
1987 auto codec = SkCodec::MakeFromData(std::move(data));
1988 SkImageInfo info = codec->getInfo().makeColorType(kRGBA_F16_SkColorType)
1989 .makeColorSpace(nullptr);
1990 test_info(r, codec.get(), info, SkCodec::kSuccess, nullptr);
1991 }
1992
1993 // These test images have ICC profiles that do not map to an SkColorSpace.
1994 // Verify that decoding them with a null destination space does not perform
1995 // color space transformations.
DEF_TEST(Codec_noConversion,r)1996 DEF_TEST(Codec_noConversion, r) {
1997 const struct Rec {
1998 const char* name;
1999 SkColor color;
2000 } recs[] = {
2001 { "images/cmyk_yellow_224_224_32.jpg", 0xFFD8FC04 },
2002 { "images/wide_gamut_yellow_224_224_64.jpeg",0xFFE0E040 },
2003 };
2004
2005 for (const auto& rec : recs) {
2006 auto data = GetResourceAsData(rec.name);
2007 if (!data) {
2008 continue;
2009 }
2010
2011 auto codec = SkCodec::MakeFromData(std::move(data));
2012 if (!codec) {
2013 ERRORF(r, "Failed to create a codec from %s", rec.name);
2014 continue;
2015 }
2016
2017 const auto* profile = codec->getICCProfile();
2018 if (!profile) {
2019 ERRORF(r, "Expected %s to have a profile", rec.name);
2020 continue;
2021 }
2022
2023 auto cs = SkColorSpace::Make(*profile);
2024 REPORTER_ASSERT(r, !cs.get());
2025
2026 SkImageInfo info = codec->getInfo().makeColorSpace(nullptr);
2027 SkBitmap bm;
2028 bm.allocPixels(info);
2029 if (codec->getPixels(info, bm.getPixels(), bm.rowBytes()) != SkCodec::kSuccess) {
2030 ERRORF(r, "Failed to decode %s", rec.name);
2031 continue;
2032 }
2033 REPORTER_ASSERT(r, bm.getColor(0, 0) == rec.color);
2034 }
2035 }
2036
DEF_TEST(Codec_kBGR_101010x_XR_SkColorType_supported,r)2037 DEF_TEST(Codec_kBGR_101010x_XR_SkColorType_supported, r) {
2038 SkBitmap srcBm;
2039 SkImageInfo srcInfo = SkImageInfo()
2040 .makeWH(100, 100)
2041 .makeColorSpace(SkColorSpace::MakeSRGB())
2042 .makeColorType(kBGRA_8888_SkColorType)
2043 .makeAlphaType(kOpaque_SkAlphaType);
2044 SkImageInfo dstInfo = srcInfo.makeColorType(kBGR_101010x_XR_SkColorType);
2045 srcBm.allocPixels(srcInfo);
2046 SkDynamicMemoryWStream stream;
2047 SkASSERT_RELEASE(SkPngEncoder::Encode(&stream, srcBm.pixmap(), {}));
2048 std::unique_ptr<SkCodec> codec(SkCodec::MakeFromData(stream.detachAsData()));
2049 SkBitmap dstBm;
2050 dstBm.allocPixels(dstInfo);
2051 bool success = codec->getPixels(dstInfo, dstBm.getPixels(), dstBm.rowBytes());
2052 REPORTER_ASSERT(r, SkCodec::kSuccess == success);
2053 }
2054
DEF_TEST(Codec_gif_notseekable,r)2055 DEF_TEST(Codec_gif_notseekable, r) {
2056 constexpr char path[] = "images/flightAnim.gif";
2057 sk_sp<SkData> data(GetResourceAsData(path));
2058 if (!data) {
2059 SkDebugf("Missing resource '%s'\n", path);
2060 return;
2061 }
2062
2063 // Verify that using a non-seekable stream works the same as a seekable one for
2064 // decoding the first frame.
2065 const SkMD5::Digest goodDigest = [data, &r]() {
2066 auto codec = SkCodec::MakeFromStream(std::make_unique<SkMemoryStream>(data),
2067 nullptr, nullptr,
2068 SkCodec::SelectionPolicy::kPreferAnimation);
2069 REPORTER_ASSERT(r, codec->getFrameCount() == 60);
2070 const auto info = codec->getInfo();
2071
2072 SkBitmap bm;
2073 bm.allocPixels(info);
2074
2075 SkCodec::Result result = codec->getPixels(info, bm.getPixels(), bm.rowBytes());
2076 REPORTER_ASSERT(r, result == SkCodec::kSuccess);
2077 return md5(bm);
2078 }();
2079
2080 auto codec = SkCodec::MakeFromStream(std::make_unique<NonseekableStream>(std::move(data)),
2081 nullptr, nullptr,
2082 SkCodec::SelectionPolicy::kPreferStillImage);
2083 REPORTER_ASSERT(r, codec->getFrameCount() == 1);
2084
2085 test_info(r, codec.get(), codec->getInfo(), SkCodec::kSuccess, &goodDigest);
2086 }
2087
DEF_TEST(Codec_gif_notseekable2,r)2088 DEF_TEST(Codec_gif_notseekable2, r) {
2089 constexpr char path[] = "images/flightAnim.gif";
2090 sk_sp<SkData> data(GetResourceAsData(path));
2091 if (!data) {
2092 SkDebugf("Missing resource '%s'\n", path);
2093 return;
2094 }
2095
2096 // Verify that using a non-seekable stream works the same as a seekable one for
2097 // decoding a later frame.
2098 SkCodec::Options options;
2099 options.fFrameIndex = 5;
2100
2101 const SkMD5::Digest goodDigest = [data, &r, &options]() {
2102 auto codec = SkCodec::MakeFromStream(std::make_unique<SkMemoryStream>(data),
2103 nullptr, nullptr,
2104 SkCodec::SelectionPolicy::kPreferAnimation);
2105 REPORTER_ASSERT(r, codec->getFrameCount() == 60);
2106 const auto info = codec->getInfo();
2107
2108 SkBitmap bm;
2109 bm.allocPixels(info);
2110
2111 SkCodec::Result result = codec->getPixels(info, bm.getPixels(), bm.rowBytes(), &options);
2112 REPORTER_ASSERT(r, result == SkCodec::kSuccess);
2113 return md5(bm);
2114 }();
2115
2116 // This should copy the non seekable stream.
2117 auto codec = SkCodec::MakeFromStream(std::make_unique<NonseekableStream>(std::move(data)),
2118 nullptr, nullptr,
2119 SkCodec::SelectionPolicy::kPreferAnimation);
2120 REPORTER_ASSERT(r, codec->getFrameCount() == 60);
2121
2122 SkBitmap bm;
2123 bm.allocPixels(codec->getInfo());
2124
2125 SkCodec::Result result = codec->getPixels(codec->getInfo(), bm.getPixels(), bm.rowBytes(),
2126 &options);
2127 REPORTER_ASSERT(r, result == SkCodec::kSuccess);
2128 compare_to_good_digest(r, goodDigest, bm);
2129 }
2130
DEF_TEST(Codec_gif_null_param,r)2131 DEF_TEST(Codec_gif_null_param, r) {
2132 constexpr char path[] = "images/flightAnim.gif";
2133 sk_sp<SkData> data(GetResourceAsData(path));
2134 if (!data) {
2135 SkDebugf("Missing resource '%s'\n", path);
2136 return;
2137 }
2138
2139 SkCodec::Result result;
2140 auto codec = SkGifDecoder::Decode(std::make_unique<SkMemoryStream>(std::move(data)),
2141 &result, nullptr);
2142 REPORTER_ASSERT(r, result == SkCodec::kSuccess);
2143 REPORTER_ASSERT(r, codec);
2144
2145 auto [image, res] = codec->getImage();
2146 REPORTER_ASSERT(r, res == SkCodec::kSuccess);
2147 REPORTER_ASSERT(r, image);
2148 REPORTER_ASSERT(r, image->width() == 320, "width %d != 320", image->width());
2149 REPORTER_ASSERT(r, image->height() == 240, "height %d != 240", image->height());
2150
2151 // Decoding the image this way loses the original data.
2152 REPORTER_ASSERT(r, !image->refEncodedData());
2153 }
2154
DEF_TEST(Codec_gif_can_preserve_original_data,r)2155 DEF_TEST(Codec_gif_can_preserve_original_data, r) {
2156 constexpr char path[] = "images/flightAnim.gif";
2157 sk_sp<SkData> data(GetResourceAsData(path));
2158 if (!data) {
2159 SkDebugf("Missing resource '%s'\n", path);
2160 return;
2161 }
2162
2163 SkCodec::Result result;
2164 auto codec = SkGifDecoder::Decode(data, &result, nullptr);
2165 REPORTER_ASSERT(r, result == SkCodec::kSuccess);
2166 REPORTER_ASSERT(r, codec);
2167
2168 sk_sp<SkImage> image = SkCodecs::DeferredImage(std::move(codec), kPremul_SkAlphaType);
2169 REPORTER_ASSERT(r, image);
2170 REPORTER_ASSERT(r, image->width() == 320, "width %d != 320", image->width());
2171 REPORTER_ASSERT(r, image->height() == 240, "height %d != 240", image->height());
2172 REPORTER_ASSERT(r,
2173 image->alphaType() == kPremul_SkAlphaType,
2174 "AlphaType is wrong %d",
2175 image->alphaType());
2176
2177 // The whole point of DeferredFromCodec is that it allows the client
2178 // to hold onto the original image data for later.
2179 sk_sp<SkData> encodedData = image->refEncodedData();
2180 REPORTER_ASSERT(r, encodedData != nullptr);
2181 // The returned data should the same as what went in.
2182 REPORTER_ASSERT(r, encodedData->size() == data->size());
2183 REPORTER_ASSERT(r, encodedData->bytes() == data->bytes());
2184 }
2185
DEF_TEST(Codec_jpeg_can_return_data_from_original_stream,r)2186 DEF_TEST(Codec_jpeg_can_return_data_from_original_stream, r) {
2187 constexpr char path[] = "images/dog.jpg";
2188 // stream will be an SkFILEStream, not a SkMemoryStream (exercised above)
2189 std::unique_ptr<SkStreamAsset> stream = GetResourceAsStream(path, true);
2190 if (!stream) {
2191 SkDebugf("Missing resource '%s'\n", path);
2192 return;
2193 }
2194 size_t expectedBytes = stream->getLength();
2195
2196 SkCodec::Result result;
2197 auto codec = SkJpegDecoder::Decode(std::move(stream), &result, nullptr);
2198 REPORTER_ASSERT(r, result == SkCodec::kSuccess);
2199 REPORTER_ASSERT(r, codec);
2200
2201 sk_sp<SkImage> image = SkCodecs::DeferredImage(std::move(codec), kUnpremul_SkAlphaType);
2202 REPORTER_ASSERT(r, image);
2203 REPORTER_ASSERT(r, image->width() == 180, "width %d != 180", image->width());
2204 REPORTER_ASSERT(r, image->height() == 180, "height %d != 180", image->height());
2205 REPORTER_ASSERT(r,
2206 image->alphaType() == kUnpremul_SkAlphaType,
2207 "AlphaType is wrong %d",
2208 image->alphaType());
2209
2210 // The whole point of DeferredFromCodec is that it allows the client
2211 // to hold onto the original image data for later.
2212 sk_sp<SkData> encodedData = image->refEncodedData();
2213 REPORTER_ASSERT(r, encodedData != nullptr);
2214 // The returned data should the same as what went in.
2215 REPORTER_ASSERT(r, encodedData->size() == expectedBytes);
2216 REPORTER_ASSERT(r, SkJpegDecoder::IsJpeg(encodedData->data(), encodedData->size()));
2217 }
2218
DEF_TEST(Codec_jpeg_decode_progressive_truncated_stream,r)2219 DEF_TEST(Codec_jpeg_decode_progressive_truncated_stream, r) {
2220 constexpr char path[] = "images/progressive_kitten_missing_eof.jpg";
2221 std::unique_ptr<SkStream> stream(GetResourceAsStream(path));
2222 if (!stream) {
2223 SkDebugf("Missing resource '%s'\n", path);
2224 return;
2225 }
2226 std::unique_ptr<SkCodec> codec = SkCodec::MakeFromStream(std::move(stream));
2227 REPORTER_ASSERT(r, codec);
2228
2229 SkBitmap bm;
2230 SkImageInfo info = codec->getInfo();
2231 REPORTER_ASSERT(r, bm.tryAllocPixels(info));
2232 SkCodec::Result result = codec->getPixels(info, bm.getPixels(), bm.rowBytes());
2233 REPORTER_ASSERT(r, result == SkCodec::kSuccess);
2234 }
2235
DEF_TEST(Codec_jpeg_decode_progressive_stream_incomplete,r)2236 DEF_TEST(Codec_jpeg_decode_progressive_stream_incomplete, r) {
2237 constexpr char path[] = "images/progressive_kitten_missing_eof.jpg";
2238 std::unique_ptr<SkStream> stream(GetResourceAsStream(path));
2239 if (!stream) {
2240 SkDebugf("Missing resource '%s'\n", path);
2241 return;
2242 }
2243 size_t length = stream->getLength();
2244 std::unique_ptr<SkCodec> codec = SkCodec::MakeFromData(SkData::MakeFromStream(stream.get(), 1 * length / 10));
2245 REPORTER_ASSERT(r, codec);
2246
2247 SkBitmap bm;
2248 SkImageInfo info = codec->getInfo();
2249 REPORTER_ASSERT(r, bm.tryAllocPixels(info));
2250 SkCodec::Result result = codec->getPixels(info, bm.getPixels(), bm.rowBytes());
2251 REPORTER_ASSERT(r, result == SkCodec::kIncompleteInput);
2252 }
2253