1 /*
2 * Copyright 2024 Google LLC.
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 "experimental/rust_png/decoder/SkPngRustDecoder.h"
9 #include "include/codec/SkCodec.h"
10 #include "include/codec/SkCodecAnimation.h"
11 #include "include/core/SkBitmap.h"
12 #include "include/core/SkColor.h"
13 #include "include/core/SkData.h"
14 #include "include/core/SkImage.h"
15 #include "include/core/SkImageInfo.h"
16 #include "include/core/SkPixmap.h"
17 #include "include/core/SkRefCnt.h"
18 #include "include/core/SkStream.h"
19 #include "tests/FakeStreams.h"
20 #include "tests/Test.h"
21 #include "tools/Resources.h"
22
23 #include <memory>
24 #include <utility>
25
26 #define REPORTER_ASSERT_SUCCESSFUL_CODEC_RESULT(r, actualResult) \
27 REPORTER_ASSERT(r, \
28 actualResult == SkCodec::kSuccess, \
29 "actualResult=\"%s\" != kSuccess", \
30 SkCodec::ResultToString(actualResult))
31
32 // Helper wrapping a call to `SkPngRustDecoder::Decode`.
SkPngRustDecoderDecode(skiatest::Reporter * r,const char * path)33 std::unique_ptr<SkCodec> SkPngRustDecoderDecode(skiatest::Reporter* r, const char* path) {
34 sk_sp<SkData> data = GetResourceAsData(path);
35 if (!data) {
36 ERRORF(r, "Missing resource: %s", path);
37 return nullptr;
38 }
39
40 SkCodec::Result result;
41 std::unique_ptr<SkCodec> codec =
42 SkPngRustDecoder::Decode(std::make_unique<SkMemoryStream>(std::move(data)), &result);
43 REPORTER_ASSERT_SUCCESSFUL_CODEC_RESULT(r, result);
44
45 return codec;
46 }
47
AssertPixelColor(skiatest::Reporter * r,const SkPixmap & pixmap,int x,int y,SkColor expectedColor,const char * description)48 void AssertPixelColor(skiatest::Reporter* r,
49 const SkPixmap& pixmap,
50 int x,
51 int y,
52 SkColor expectedColor,
53 const char* description) {
54 SkASSERT(r);
55 SkASSERT(x >= 0);
56 SkASSERT(y >= 0);
57 SkASSERT(description);
58
59 REPORTER_ASSERT(r, x < pixmap.width(), "x=%d >= width=%d", x, pixmap.width());
60 REPORTER_ASSERT(r, y < pixmap.height(), "y=%d >= height=%d", y, pixmap.height());
61 REPORTER_ASSERT(r,
62 kN32_SkColorType == pixmap.colorType(),
63 "kN32_SkColorType != pixmap.ColorType()=%d",
64 pixmap.colorType());
65
66 SkColor actualColor = pixmap.getColor(x, y);
67 REPORTER_ASSERT(r,
68 actualColor == expectedColor,
69 "actualColor=0x%08X != expectedColor==0x%08X at (%d,%d) (%s)",
70 actualColor,
71 expectedColor,
72 x,
73 y,
74 description);
75 }
76
AssertGreenPixel(skiatest::Reporter * r,const SkPixmap & pixmap,int x,int y,const char * description="Expecting a green pixel")77 void AssertGreenPixel(skiatest::Reporter* r,
78 const SkPixmap& pixmap,
79 int x,
80 int y,
81 const char* description = "Expecting a green pixel") {
82 AssertPixelColor(r, pixmap, x, y, SkColorSetRGB(0x00, 0xFF, 0x00), description);
83 }
84
AssertRedPixel(skiatest::Reporter * r,const SkPixmap & pixmap,int x,int y,const char * description="Expecting a red pixel")85 void AssertRedPixel(skiatest::Reporter* r,
86 const SkPixmap& pixmap,
87 int x,
88 int y,
89 const char* description = "Expecting a red pixel") {
90 AssertPixelColor(r, pixmap, x, y, SkColorSetRGB(0xFF, 0x00, 0x00), description);
91 }
92
AssertBluePixel(skiatest::Reporter * r,const SkPixmap & pixmap,int x,int y,const char * description="Expecting a blue pixel")93 void AssertBluePixel(skiatest::Reporter* r,
94 const SkPixmap& pixmap,
95 int x,
96 int y,
97 const char* description = "Expecting a blue pixel") {
98 AssertPixelColor(r, pixmap, x, y, SkColorSetRGB(0x00, 0x00, 0xFF), description);
99 }
100
AssertSingleGreenFrame(skiatest::Reporter * r,int expectedWidth,int expectedHeight,const char * resourcePath)101 void AssertSingleGreenFrame(skiatest::Reporter* r,
102 int expectedWidth,
103 int expectedHeight,
104 const char* resourcePath) {
105 std::unique_ptr<SkCodec> codec = SkPngRustDecoderDecode(r, resourcePath);
106 if (!codec) {
107 return;
108 }
109
110 REPORTER_ASSERT(r, codec->getFrameCount() == 1);
111 REPORTER_ASSERT(r, codec->getRepetitionCount() == 0);
112
113 SkCodec::FrameInfo info;
114 REPORTER_ASSERT(r, codec->getFrameInfo(0, &info));
115 REPORTER_ASSERT(r, info.fBlend == SkCodecAnimation::Blend::kSrc);
116 REPORTER_ASSERT(r, info.fDisposalMethod == SkCodecAnimation::DisposalMethod::kKeep);
117 REPORTER_ASSERT(r, info.fFrameRect == SkIRect::MakeWH(expectedWidth, expectedHeight));
118 REPORTER_ASSERT(r, info.fRequiredFrame == SkCodec::kNoFrame);
119
120 auto [image, result] = codec->getImage();
121 REPORTER_ASSERT_SUCCESSFUL_CODEC_RESULT(r, result);
122
123 REPORTER_ASSERT(r, image);
124 REPORTER_ASSERT(r,
125 image->width() == expectedWidth,
126 "actualWidth=%d != expectedWidth=%d",
127 image->width(),
128 expectedWidth);
129 REPORTER_ASSERT(r,
130 image->height() == expectedHeight,
131 "actualHeight=%d != expectedHeight=%d",
132 image->height(),
133 expectedHeight);
134
135 SkPixmap pixmap;
136 REPORTER_ASSERT(r, image->peekPixels(&pixmap));
137
138 AssertGreenPixel(r, pixmap, 0, 0);
139 AssertGreenPixel(r, pixmap, expectedWidth / 2, expectedHeight / 2);
140 }
141
DecodeLastFrame(skiatest::Reporter * r,SkCodec * codec)142 sk_sp<SkImage> DecodeLastFrame(skiatest::Reporter* r, SkCodec* codec) {
143 int frameCount = codec->getFrameCount();
144 sk_sp<SkImage> image;
145 SkCodec::Result result = SkCodec::kSuccess;
146 for (int i = 0; i < frameCount; i++) {
147 SkCodec::FrameInfo info;
148 REPORTER_ASSERT(r, codec->getFrameInfo(i, &info));
149
150 // This test method only supports `kKeep` disposal method.
151 SkASSERT(info.fDisposalMethod == SkCodecAnimation::DisposalMethod::kKeep);
152
153 if (!image) {
154 std::tie(image, result) = codec->getImage();
155 REPORTER_ASSERT_SUCCESSFUL_CODEC_RESULT(r, result);
156 if (result != SkCodec::kSuccess) {
157 return nullptr;
158 }
159 } else {
160 SkPixmap pixmap;
161 REPORTER_ASSERT(r, image->peekPixels(&pixmap));
162
163 SkCodec::Options options;
164 options.fZeroInitialized = SkCodec::kNo_ZeroInitialized;
165 options.fSubset = nullptr;
166 options.fFrameIndex = i;
167 options.fPriorFrame = i - 1;
168
169 result = codec->getPixels(pixmap, &options);
170 REPORTER_ASSERT_SUCCESSFUL_CODEC_RESULT(r, result);
171 if (result != SkCodec::kSuccess) {
172 return nullptr;
173 }
174 }
175 }
176
177 return image;
178 }
179
DecodeLastFrame(skiatest::Reporter * r,const char * resourcePath)180 sk_sp<SkImage> DecodeLastFrame(skiatest::Reporter* r, const char* resourcePath) {
181 std::unique_ptr<SkCodec> codec = SkPngRustDecoderDecode(r, resourcePath);
182 if (!codec) {
183 return nullptr;
184 }
185
186 return DecodeLastFrame(r, codec.get());
187 }
188
AssertAnimationRepetitionCount(skiatest::Reporter * r,int expectedRepetitionCount,const char * resourcePath)189 void AssertAnimationRepetitionCount(skiatest::Reporter* r,
190 int expectedRepetitionCount,
191 const char* resourcePath) {
192 std::unique_ptr<SkCodec> codec = SkPngRustDecoderDecode(r, resourcePath);
193 if (!codec) {
194 return;
195 }
196
197 int actualRepetitionCount = codec->getRepetitionCount();
198 REPORTER_ASSERT(r,
199 actualRepetitionCount == expectedRepetitionCount,
200 "actualRepetitionCount=%d != expectedRepetitionCount=%d",
201 actualRepetitionCount,
202 expectedRepetitionCount);
203 }
204
205 // Test based on
206 // https://philip.html5.org/tests/apng/tests.html#trivial-static-image
DEF_TEST(Codec_apng_basic_trivial_static_image,r)207 DEF_TEST(Codec_apng_basic_trivial_static_image, r) {
208 AssertSingleGreenFrame(r, 128, 64, "images/apng-test-suite--basic--trivial-static-image.png");
209 }
210
211 // Test based on
212 // https://philip.html5.org/tests/apng/tests.html#trivial-animated-image-one-frame-using-default-image
DEF_TEST(Codec_apng_basic_using_default_image,r)213 DEF_TEST(Codec_apng_basic_using_default_image, r) {
214 AssertSingleGreenFrame(r, 128, 64, "images/apng-test-suite--basic--using-default-image.png");
215 }
216
217 // Test based on
218 // https://philip.html5.org/tests/apng/tests.html#trivial-animated-image-one-frame-ignoring-default-image
219 //
220 // The input file contains the following PNG chunks: IHDR, acTL, IDAT, fcTL,
221 // fdAT, IEND. Presence of acTL chunk + no fcTL chunk before IDAT means that
222 // the IDAT chunk is *not* part of the animation (i.e. APNG-aware decoders
223 // should ignore IDAT frame and start with fdAT frame). This test will fail
224 // with non-APNG-aware decoders (e.g. with `SkPngCodec`), because the `IDAT`
225 // chunk represents a red image (the `fdAT` chunk represents a green image).
DEF_TEST(Codec_apng_basic_ignoring_default_image,r)226 DEF_TEST(Codec_apng_basic_ignoring_default_image, r) {
227 AssertSingleGreenFrame(r, 128, 64, "images/apng-test-suite--basic--ignoring-default-image.png");
228 }
229
230 // Test based on
231 // https://philip.html5.org/tests/apng/tests.html#apng-dispose-op-none-basic
232 //
233 // This test covers two aspects of `SkPngRustCodec` implementation:
234 //
235 // * Blink expects that `onGetFrameCount` returns the total frame count when
236 // the complete image resource is available (i.e. a lower frame count should
237 // only happen upon `SkCodec::kIncompleteInput`). Before http://review.skia.org/911038
238 // `SkPngRustCodec::onGetFrameCount` would not discover additional frames if
239 // previous frames haven't been decoded yet.
240 // * TODO(https://crbug.com/356922876): Skia client (e.g. Blink; or here the
241 // testcase) is expected to handle `SkCodecAnimation::DisposalMethod` and
242 // populate the target buffer (and `SkCodec::Options::fPriorFrame`) with the
243 // expected pixels. OTOH, `SkPngRustCodec` needs to handle
244 // `SkCodecAnimation::Blend` - without this the final frame in this test will
245 // contain red pixels.
DEF_TEST(Codec_apng_dispose_op_none_basic,r)246 DEF_TEST(Codec_apng_dispose_op_none_basic, r) {
247 std::unique_ptr<SkCodec> codec =
248 SkPngRustDecoderDecode(r, "images/apng-test-suite--dispose-ops--none-basic.png");
249 if (!codec) {
250 return;
251 }
252
253 REPORTER_ASSERT(r, codec->getFrameCount() == 3);
254 REPORTER_ASSERT(r, codec->getRepetitionCount() == 0);
255
256 // We should have `FrameInfo` for all 3 frames.
257 SkCodec::FrameInfo info[3];
258 REPORTER_ASSERT(r, codec->getFrameInfo(0, &info[0]));
259 REPORTER_ASSERT(r, codec->getFrameInfo(1, &info[1]));
260 REPORTER_ASSERT(r, codec->getFrameInfo(2, &info[2]));
261
262 // The codec should realize that the `SkStream` contains all the data of the
263 // first 2 frames. Currently `SkPngRustCodec::onGetFrameCount` stops after
264 // parsing the final, 3rd `fcTL` chunk and therefore it can't tell if the
265 // subsequent `fdAT` chunk has been fully received or not.
266 REPORTER_ASSERT(r, info[0].fFullyReceived);
267 REPORTER_ASSERT(r, info[1].fFullyReceived);
268 REPORTER_ASSERT(r, !info[2].fFullyReceived);
269
270 // Spot-check frame metadata.
271 REPORTER_ASSERT(r, info[1].fAlphaType == kUnpremul_SkAlphaType);
272 REPORTER_ASSERT(r, info[1].fBlend == SkCodecAnimation::Blend::kSrcOver);
273 REPORTER_ASSERT(r, info[1].fDisposalMethod == SkCodecAnimation::DisposalMethod::kKeep);
274 REPORTER_ASSERT(r, info[1].fDuration == 100, "dur = %d", info[1].fDuration);
275 REPORTER_ASSERT(r, info[1].fFrameRect == SkIRect::MakeWH(128, 64));
276 REPORTER_ASSERT(r, info[1].fHasAlphaWithinBounds);
277 REPORTER_ASSERT(r, info[1].fRequiredFrame == 0);
278
279 // Validate contents of the first frame.
280 auto [image, result] = codec->getImage();
281 REPORTER_ASSERT_SUCCESSFUL_CODEC_RESULT(r, result);
282 REPORTER_ASSERT(r, image);
283 REPORTER_ASSERT(r, image->width() == 128, "width %d != 128", image->width());
284 REPORTER_ASSERT(r, image->height() == 64, "height %d != 64", image->height());
285 SkPixmap pixmap;
286 REPORTER_ASSERT(r, image->peekPixels(&pixmap));
287 AssertRedPixel(r, pixmap, 0, 0, "Frame #0 should be red");
288
289 // Validate contents of the second frame.
290 SkCodec::Options options;
291 options.fZeroInitialized = SkCodec::kNo_ZeroInitialized;
292 options.fSubset = nullptr;
293 options.fFrameIndex = 1; // We want to decode the second frame.
294 options.fPriorFrame = 0; // `pixmap` contains the first frame before `getPixels` call.
295 REPORTER_ASSERT_SUCCESSFUL_CODEC_RESULT(r, codec->getPixels(pixmap, &options));
296 AssertGreenPixel(r, pixmap, 0, 0, "Frame #1 should be green");
297
298 // Validate contents of the third frame.
299 options.fFrameIndex = 2; // We want to decode the second frame.
300 options.fPriorFrame = 1; // `pixmap` contains the second frame before `getPixels` call.
301 REPORTER_ASSERT_SUCCESSFUL_CODEC_RESULT(r, codec->getPixels(pixmap, &options));
302 AssertGreenPixel(r, pixmap, 0, 0, "Frame #2 should be green");
303 }
304
305 // This test covers an incomplete input scenario:
306 //
307 // * Only half of 1st frame is available during `onGetFrameCount`.
308 // In this situation `onGetFrameCount` may consume the whole input in a
309 // (futile in this case) attempt to discover `fcTL` chunks for 2nd and 3rd
310 // frame. This will mean that the input stream is in the middle of the 1st
311 // frame - no longer positioned correctly for decoding the 1st frame.
312 // * Full input is available when subsequently decoding 1st frame.
DEF_TEST(Codec_apng_dispose_op_none_basic_incomplete_input1,r)313 DEF_TEST(Codec_apng_dispose_op_none_basic_incomplete_input1, r) {
314 const char* path = "images/apng-test-suite--dispose-ops--none-basic.png";
315 sk_sp<SkData> data = GetResourceAsData(path);
316 if (!data) {
317 ERRORF(r, "Missing resource: %s", path);
318 return;
319 }
320 size_t fullLength = data->size();
321
322 // Initially expose roughly middle of `IDAT` chunk (in this image `fcTL` is
323 // present before the `IDAT` chunk and therefore the `IDAT` chunk is part of
324 // the animated image).
325 constexpr size_t kInitialBytes = 0xAD;
326 auto streamForCodec = std::make_unique<HaltingStream>(std::move(data), kInitialBytes);
327 HaltingStream* retainedStream = streamForCodec.get();
328
329 SkCodec::Result result;
330 std::unique_ptr<SkCodec> codec = SkPngRustDecoder::Decode(std::move(streamForCodec), &result);
331 REPORTER_ASSERT_SUCCESSFUL_CODEC_RESULT(r, result);
332 if (!codec) {
333 return;
334 }
335
336 SkBitmap bitmap;
337 if (!bitmap.tryAllocN32Pixels(codec->dimensions().width(), codec->dimensions().height())) {
338 ERRORF(r, "Failed to allocate SkBitmap");
339 return;
340 }
341
342 // Try to provoke the codec to consume the currently-available part of the
343 // input stream.
344 //
345 // At this point only the metadata for the first frame is available.
346 int frameCount = codec->getFrameCount();
347 REPORTER_ASSERT(r, frameCount == 1);
348
349 // Make the rest of the input available to the codec.
350 retainedStream->addNewData(fullLength);
351
352 // Try to decode the first frame and check its contents.
353 sk_sp<SkImage> image;
354 std::tie(image, result) = codec->getImage();
355 REPORTER_ASSERT_SUCCESSFUL_CODEC_RESULT(r, result);
356 REPORTER_ASSERT(r, image);
357 REPORTER_ASSERT(r, image->width() == 128, "width %d != 128", image->width());
358 REPORTER_ASSERT(r, image->height() == 64, "height %d != 64", image->height());
359 SkPixmap pixmap;
360 REPORTER_ASSERT(r, image->peekPixels(&pixmap));
361 AssertRedPixel(r, pixmap, 0, 0, "Frame #0 should be red");
362 }
363
364 // This test covers an incomplete input scenario:
365 //
366 // * Only half of 1st frame is available during the initial `incrementalDecode`.
367 // * Before retrying, `getFrameCount` is called. This should *not* reposition
368 // the stream while in the middle of an active incremental decode.
369 // * Then we retry `incrementalDecode`.
DEF_TEST(Codec_apng_dispose_op_none_basic_incomplete_input2,r)370 DEF_TEST(Codec_apng_dispose_op_none_basic_incomplete_input2, r) {
371 const char* path = "images/apng-test-suite--dispose-ops--none-basic.png";
372 sk_sp<SkData> data = GetResourceAsData(path);
373 if (!data) {
374 ERRORF(r, "Missing resource: %s", path);
375 return;
376 }
377 size_t fullLength = data->size();
378
379 constexpr size_t kInitialBytes = 0x8D; // Roughly middle of IDAT chunk.
380 auto streamForCodec = std::make_unique<HaltingStream>(std::move(data), kInitialBytes);
381 HaltingStream* retainedStream = streamForCodec.get();
382
383 SkCodec::Result result;
384 std::unique_ptr<SkCodec> codec = SkPngRustDecoder::Decode(std::move(streamForCodec), &result);
385 REPORTER_ASSERT_SUCCESSFUL_CODEC_RESULT(r, result);
386 if (!codec) {
387 return;
388 }
389
390 SkBitmap bitmap;
391 if (!bitmap.tryAllocN32Pixels(codec->dimensions().width(), codec->dimensions().height())) {
392 ERRORF(r, "Failed to allocate SkBitmap");
393 return;
394 }
395 const SkPixmap& pixmap = bitmap.pixmap();
396
397 // Fill the `bitmap` with blue pixels to detect which pixels have been filled
398 // by the codec during a partially-successful `incrementalDecode`.
399 //
400 // (The first frame has all red pixels.)
401 bitmap.erase(SkColorSetRGB(0, 0, 0xFF), bitmap.bounds());
402 AssertBluePixel(r, pixmap, 0, 0);
403 AssertBluePixel(r, pixmap, 40, 29);
404 AssertBluePixel(r, pixmap, 80, 35);
405 AssertBluePixel(r, pixmap, 127, 63);
406
407 // Decode partially-available, incomplete image.
408 SkCodec::Options options;
409 options.fZeroInitialized = SkCodec::kNo_ZeroInitialized;
410 options.fSubset = nullptr;
411 options.fFrameIndex = 0;
412 options.fPriorFrame = SkCodec::kNoFrame;
413 result = codec->startIncrementalDecode(bitmap.pixmap().info(),
414 bitmap.pixmap().writable_addr(),
415 bitmap.pixmap().rowBytes(),
416 &options);
417 REPORTER_ASSERT(r, result == SkCodec::kSuccess);
418 int rowsDecoded = -1;
419 result = codec->incrementalDecode(&rowsDecoded);
420 REPORTER_ASSERT(r, result == SkCodec::kIncompleteInput);
421 REPORTER_ASSERT(r, rowsDecoded == 10, "actual rowsDecoded = %d", rowsDecoded);
422 AssertRedPixel(r, pixmap, 0, 0);
423 AssertBluePixel(r, pixmap, 40, 29);
424 AssertBluePixel(r, pixmap, 80, 35);
425 AssertBluePixel(r, pixmap, 127, 63);
426
427 // Make the rest of the input available to the codec.
428 retainedStream->addNewData(fullLength);
429
430 // Try to provoke the codec to consume further into the input stream (doing
431 // this would loose the position inside the currently active incremental
432 // decode).
433 //
434 // At this point metadata of all the frames is available, but the codec
435 // shouldn't read the other two `fcTL` chunks during an active incremental
436 // decode.
437 int frameCount = codec->getFrameCount();
438 REPORTER_ASSERT(r, frameCount == 1);
439
440 // Check that all the pixels of the first frame got decoded.
441 rowsDecoded = -1;
442 result = codec->incrementalDecode(&rowsDecoded);
443 REPORTER_ASSERT(r, result == SkCodec::kSuccess);
444 REPORTER_ASSERT(r, rowsDecoded == -1); // Not set when `kSuccess`.
445 AssertRedPixel(r, pixmap, 0, 0);
446 AssertRedPixel(r, pixmap, 40, 29);
447 AssertRedPixel(r, pixmap, 80, 35);
448 AssertRedPixel(r, pixmap, 127, 63);
449 }
450
451 // Test based on
452 // https://philip.html5.org/tests/apng/tests.html#apng-blend-op-source-on-solid-colour
DEF_TEST(Codec_apng_blend_ops_source_on_solid,r)453 DEF_TEST(Codec_apng_blend_ops_source_on_solid, r) {
454 std::unique_ptr<SkCodec> codec =
455 SkPngRustDecoderDecode(r, "images/apng-test-suite--blend-ops--source-on-solid.png");
456 if (!codec) {
457 return;
458 }
459 sk_sp<SkImage> image = DecodeLastFrame(r, codec.get());
460 if (!image) {
461 return;
462 }
463
464 SkPixmap pixmap;
465 REPORTER_ASSERT(r, image->peekPixels(&pixmap));
466 AssertGreenPixel(r, pixmap, 0, 0);
467
468 SkCodec::FrameInfo info;
469 REPORTER_ASSERT(r, codec->getFrameInfo(1, &info));
470 REPORTER_ASSERT(r, info.fBlend == SkCodecAnimation::Blend::kSrc);
471 REPORTER_ASSERT(r, info.fRequiredFrame == SkCodec::kNoFrame);
472 }
473
474 // Test based on
475 // https://philip.html5.org/tests/apng/tests.html#apng-blend-op-source-on-nearly-transparent-colour
DEF_TEST(Codec_apng_blend_ops_source_on_nearly_transparent,r)476 DEF_TEST(Codec_apng_blend_ops_source_on_nearly_transparent, r) {
477 sk_sp<SkImage> image = DecodeLastFrame(
478 r, "images/apng-test-suite--blend-ops--source-on-nearly-transparent.png");
479 if (!image) {
480 return;
481 }
482
483 SkPixmap pixmap;
484 REPORTER_ASSERT(r, image->peekPixels(&pixmap));
485 AssertPixelColor(r,
486 pixmap,
487 0,
488 0,
489 SkColorSetARGB(0x02, 0x00, 0xFF, 0x00),
490 "Expecting a nearly transparent pixel");
491 }
492
493 // Test based on
494 // https://philip.html5.org/tests/apng/tests.html#apng-blend-op-over-on-solid-and-transparent-colours
DEF_TEST(Codec_apng_blend_ops_over_on_solid_and_transparent,r)495 DEF_TEST(Codec_apng_blend_ops_over_on_solid_and_transparent, r) {
496 sk_sp<SkImage> image = DecodeLastFrame(
497 r, "images/apng-test-suite--blend-ops--over-on-solid-and-transparent.png");
498 if (!image) {
499 return;
500 }
501
502 SkPixmap pixmap;
503 REPORTER_ASSERT(r, image->peekPixels(&pixmap));
504 AssertGreenPixel(r, pixmap, 0, 0);
505 }
506
507 // Test based on
508 // https://philip.html5.org/tests/apng/tests.html#apng-blend-op-over-repeatedly-with-nearly-transparent-colours
DEF_TEST(Codec_apng_blend_ops_over_repeatedly,r)509 DEF_TEST(Codec_apng_blend_ops_over_repeatedly, r) {
510 sk_sp<SkImage> image =
511 DecodeLastFrame(r, "images/apng-test-suite--blend-ops--over-repeatedly.png");
512 if (!image) {
513 return;
514 }
515
516 SkPixmap pixmap;
517 REPORTER_ASSERT(r, image->peekPixels(&pixmap));
518 AssertGreenPixel(r, pixmap, 0, 0);
519 }
520
521 // Test based on
522 // https://philip.html5.org/tests/apng/tests.html#apng-dispose-op-none-in-region
DEF_TEST(Codec_apng_regions_dispose_op_none,r)523 DEF_TEST(Codec_apng_regions_dispose_op_none, r) {
524 sk_sp<SkImage> image =
525 DecodeLastFrame(r, "images/apng-test-suite--regions--dispose-op-none.png");
526 if (!image) {
527 return;
528 }
529
530 // Check all pixels.
531 //
532 // * The image (and the first frame) is 128x64
533 // * The 2nd frame is 64x32 at offset (32,16)
534 // * The 3rd frame is 1x1 at offset (0,0)
535 SkPixmap pixmap;
536 REPORTER_ASSERT(r, image->peekPixels(&pixmap));
537 for (int y = 0; y < pixmap.height(); y++) {
538 for (int x = 0; x < pixmap.width(); x++) {
539 AssertGreenPixel(r, pixmap, x, y);
540 }
541 }
542 }
543
544 // Test based on
545 // https://philip.html5.org/tests/apng/tests.html#num-plays-0
DEF_TEST(Codec_apng_num_plays_0,r)546 DEF_TEST(Codec_apng_num_plays_0, r) {
547 AssertAnimationRepetitionCount(
548 r, SkCodec::kRepetitionCountInfinite, "images/apng-test-suite--num-plays--0.png");
549 }
550
551 // Test based on
552 // https://philip.html5.org/tests/apng/tests.html#num-plays-1
DEF_TEST(Codec_apng_num_plays_1,r)553 DEF_TEST(Codec_apng_num_plays_1, r) {
554 AssertAnimationRepetitionCount(r, 0, "images/apng-test-suite--num-plays--1.png");
555 }
556
557 // Test based on
558 // https://philip.html5.org/tests/apng/tests.html#num-plays-2
DEF_TEST(Codec_apng_num_plays_2,r)559 DEF_TEST(Codec_apng_num_plays_2, r) {
560 AssertAnimationRepetitionCount(r, 1, "images/apng-test-suite--num-plays--2.png");
561 }
562
563 // Test based on
564 // https://philip.html5.org/tests/apng/tests.html#num-frames-outside-valid-range
565 //
566 // In this test the `acTL` chunk sets `num_frames` to `2147483649u` (or `0x80000001u`):
567 //
568 // * AFAICT version 1.0 of the APNG spec only says that "0 is not a valid value"
569 // * The test suite webpage says that at one point the APNG spec said that
570 // `num_frames` shall be "limited to the range 0 to (2^31)-1"
DEF_TEST(Codec_apng_invalid_num_frames_outside_valid_range,r)571 DEF_TEST(Codec_apng_invalid_num_frames_outside_valid_range, r) {
572 std::unique_ptr<SkCodec> codec = SkPngRustDecoderDecode(
573 r, "images/apng-test-suite--invalid--num-frames-outside-valid-range.png");
574 if (!codec) {
575 return;
576 }
577
578 // Calling `codec->getFrameCount` exercises the code used to discover and
579 // parse `fcTL` chunks. With the initial implementation of
580 // `SkPngRustCodec::getRawFrameCount` the call below would have failed
581 // `SkASSERT(fFrameAtCurrentStreamPosition < this->getRawFrameCount())`
582 // in `SkPngRustCodec::readToStartOfNextFrame`.
583 //
584 // Note that `SkPngRustCodec::onGetFrameCount` expectedly returns the number
585 // of successfully parsed `fcTL` chunks (1 chunk in the test input) rather
586 // than returning the raw `acTL.num_frames`.
587 REPORTER_ASSERT(r, codec->getFrameCount() == 1);
588 }
589
DEF_TEST(Codec_png_swizzling_target_unimplemented,r)590 DEF_TEST(Codec_png_swizzling_target_unimplemented, r) {
591 std::unique_ptr<SkCodec> codec =
592 SkPngRustDecoderDecode(r, "images/apng-test-suite--basic--ignoring-default-image.png");
593 if (!codec) {
594 return;
595 }
596 REPORTER_ASSERT(r, codec->getFrameCount() == 1);
597
598 // Ask to decode into an esoteric `SkColorType`:
599 //
600 // * Unsupported by `SkSwizzler`.
601 // * Supported by `SkCodec::conversionSupported`.
602 SkImageInfo dstInfo = SkImageInfo::Make(
603 codec->dimensions(), kBGRA_10101010_XR_SkColorType, kPremul_SkAlphaType);
604
605 auto [image, result] = codec->getImage(dstInfo);
606 REPORTER_ASSERT(r, result == SkCodec::kUnimplemented);
607 REPORTER_ASSERT(r, !image);
608 }
609
DEF_TEST(Codec_png_was_encoded_with_16_bits_or_more_per_component,r)610 DEF_TEST(Codec_png_was_encoded_with_16_bits_or_more_per_component, r) {
611 struct Test {
612 const char* fFilename;
613 bool fEncodedWith16bits;
614 };
615 const std::array<Test, 4> kTests = {
616 Test{"images/pngsuite/basn0g04.png", false}, // 4 bit (16 level) grayscale
617 Test{"images/pngsuite/basn2c08.png", false}, // 3x8 bits rgb color
618 Test{"images/pngsuite/basn2c16.png", true}, // 3x16 bits rgb color
619 Test{"images/pngsuite/basn3p01.png", false} // 1 bit (2 color) paletted
620 };
621 for (const auto& test : kTests) {
622 std::unique_ptr<SkCodec> codec = SkPngRustDecoderDecode(r, test.fFilename);
623 if (codec) {
624 REPORTER_ASSERT(r, codec->hasHighBitDepthEncodedData() == test.fEncodedWith16bits);
625 }
626 }
627 }
628
DEF_TEST(Codec_png_cicp,r)629 DEF_TEST(Codec_png_cicp, r) {
630 std::unique_ptr<SkCodec> codec = SkPngRustDecoderDecode(r, "images/cicp_pq.png");
631 if (!codec) {
632 return;
633 }
634
635 const skcms_ICCProfile* profile = codec->getICCProfile();
636 REPORTER_ASSERT(r, profile);
637 if (!profile) {
638 return;
639 }
640
641 REPORTER_ASSERT(r, skcms_TransferFunction_isPQish(&profile->trc[0].parametric));
642 }
643