xref: /aosp_15_r20/external/skia/tests/SkPngRustDecoderTest.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
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