xref: /aosp_15_r20/external/skia/tests/AnimatedImageTest.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1 /*
2  * Copyright 2018 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/android/SkAnimatedImage.h"
9 #include "include/codec/SkAndroidCodec.h"
10 #include "include/codec/SkCodec.h"
11 #include "include/core/SkAlphaType.h"
12 #include "include/core/SkBitmap.h"
13 #include "include/core/SkCanvas.h"
14 #include "include/core/SkColor.h"
15 #include "include/core/SkData.h"
16 #include "include/core/SkImageInfo.h"
17 #include "include/core/SkPicture.h"
18 #include "include/core/SkRect.h"
19 #include "include/core/SkRefCnt.h"
20 #include "include/core/SkSize.h"
21 #include "include/core/SkString.h"
22 #include "include/core/SkTypes.h"
23 #include "include/core/SkUnPreMultiply.h"
24 #include "tests/CodecPriv.h"
25 #include "tests/Test.h"
26 #include "tools/Resources.h"
27 #include "tools/ToolUtils.h"
28 
29 #include <cstddef>
30 #include <initializer_list>
31 #include <memory>
32 #include <utility>
33 #include <vector>
34 
DEF_TEST(AnimatedImage_simple,r)35 DEF_TEST(AnimatedImage_simple, r) {
36     if (GetResourcePath().isEmpty()) {
37         return;
38     }
39 
40     const char* file = "images/stoplight_h.webp";
41     auto data = GetResourceAsData(file);
42     if (!data) {
43         ERRORF(r, "Could not get %s", file);
44         return;
45     }
46 
47     // An animated image with a non-default exif orientation is no longer
48     // "simple"; verify that the assert has been removed.
49     auto androidCodec = SkAndroidCodec::MakeFromData(std::move(data));
50     auto animatedImage = SkAnimatedImage::Make(std::move(androidCodec));
51     REPORTER_ASSERT(r, animatedImage);
52 }
53 
DEF_TEST(AnimatedImage_rotation,r)54 DEF_TEST(AnimatedImage_rotation, r) {
55     if (GetResourcePath().isEmpty()) {
56         return;
57     }
58 
59     // These images use different exif orientations to achieve the same final
60     // dimensions
61     const auto expectedBounds = SkRect::MakeIWH(100, 80);
62     for (int i = 1; i <=8; i++) {
63         for (const SkString& name : { SkStringPrintf("images/orientation/%d.webp", i),
64                                       SkStringPrintf("images/orientation/%d_444.jpg", i) }) {
65 
66             const char* file = name.c_str();
67             auto data = GetResourceAsData(file);
68             if (!data) {
69                 ERRORF(r, "Could not get %s", file);
70                 return;
71             }
72 
73             auto androidCodec = SkAndroidCodec::MakeFromData(std::move(data));
74             auto animatedImage = SkAnimatedImage::Make(std::move(androidCodec));
75             if (!animatedImage) {
76                 ERRORF(r, "Failed to create animated image from %s", file);
77                 return;
78             }
79 
80             auto bounds = animatedImage->getBounds();
81             if (bounds != expectedBounds) {
82                 ERRORF(r, "Mismatched bounds for %s", file);
83                 bounds.dump();
84             }
85         }
86     }
87 }
88 
DEF_TEST(AnimatedImage_invalidCrop,r)89 DEF_TEST(AnimatedImage_invalidCrop, r) {
90     if (GetResourcePath().isEmpty()) {
91         return;
92     }
93 
94     const char* file = "images/alphabetAnim.gif";
95     auto data = GetResourceAsData(file);
96     if (!data) {
97         ERRORF(r, "Could not get %s", file);
98         return;
99     }
100 
101     const struct Rec {
102         bool    valid;
103         SkISize scaledSize;
104         SkIRect cropRect;
105     } gRecs[] = {
106         // cropRect contained by original dimensions
107         { true,  {100, 100}, {   0,  0, 100, 100} },
108         { true,  {100, 100}, {   0,  0,  50,  50} },
109         { true,  {100, 100}, {  10, 10, 100, 100} },
110         { true,  {100, 100}, {   0,  0, 100, 100} },
111 
112         // unsorted cropRect
113         { false, {100, 100}, {   0, 100, 100,   0} },
114         { false, {100, 100}, { 100,   0,   0, 100} },
115 
116         // cropRect not contained by original dimensions
117         { false, {100, 100}, {   0,   1, 100, 101} },
118         { false, {100, 100}, {   0,  -1, 100,  99} },
119         { false, {100, 100}, {  -1,   0,  99, 100} },
120         { false, {100, 100}, { 100, 100, 200, 200} },
121 
122         // cropRect contained by scaled dimensions
123         { true,  { 50,  50}, {   0,   0,  50,  50} },
124         { true,  { 50,  50}, {   0,   0,  25,  25} },
125         { true,  {200, 200}, {   0,   1, 100, 101} },
126 
127         // cropRect not contained by scaled dimensions
128         { false, { 50,  50}, {   0,   0,  75,  25} },
129         { false, { 50,  50}, {   0,   0,  25,  75} },
130 
131     };
132     for (const auto& rec : gRecs) {
133         auto codec = SkAndroidCodec::MakeFromData(data);
134         if (!codec) {
135             ERRORF(r, "Could not create codec for %s", file);
136             return;
137         }
138 
139         auto info = codec->getInfo();
140         REPORTER_ASSERT(r, info.dimensions() == SkISize::Make(100, 100));
141 
142         auto image = SkAnimatedImage::Make(std::move(codec), info.makeDimensions(rec.scaledSize),
143                 rec.cropRect, nullptr);
144 
145         REPORTER_ASSERT(r, rec.valid == !!image.get());
146     }
147 }
148 
DEF_TEST(AnimatedImage_scaled,r)149 DEF_TEST(AnimatedImage_scaled, r) {
150     if (GetResourcePath().isEmpty()) {
151         return;
152     }
153 
154     const char* file = "images/alphabetAnim.gif";
155     auto data = GetResourceAsData(file);
156     if (!data) {
157         ERRORF(r, "Could not get %s", file);
158         return;
159     }
160 
161     auto codec = SkAndroidCodec::MakeFromCodec(SkCodec::MakeFromData(data));
162     if (!codec) {
163         ERRORF(r, "Could not create codec for %s", file);
164         return;
165     }
166 
167     // Force the drawable follow its special case that requires scaling.
168     auto info = codec->getInfo();
169     info = info.makeWH(info.width() - 5, info.height() - 5);
170     auto rect = info.bounds();
171     auto image = SkAnimatedImage::Make(std::move(codec), info, rect, nullptr);
172     if (!image) {
173         ERRORF(r, "Failed to create animated image for %s", file);
174         return;
175     }
176 
177     // Clear a bitmap to non-transparent and draw to it. pixels that are transparent
178     // in the image should not replace the original non-transparent color.
179     SkBitmap bm;
180     bm.allocPixels(SkImageInfo::MakeN32Premul(info.width(), info.height()));
181     bm.eraseColor(SK_ColorBLUE);
182     SkCanvas canvas(bm);
183     image->draw(&canvas);
184     for (int i = 0; i < info.width();  ++i)
185     for (int j = 0; j < info.height(); ++j) {
186         if (*bm.getAddr32(i, j) == SK_ColorTRANSPARENT) {
187             ERRORF(r, "Erased color underneath!");
188             return;
189         }
190     }
191 }
192 
compare_bitmaps(skiatest::Reporter * r,const char * file,int expectedFrame,const SkBitmap & expectedBm,const SkBitmap & actualBm)193 static bool compare_bitmaps(skiatest::Reporter* r,
194                             const char* file,
195                             int expectedFrame,
196                             const SkBitmap& expectedBm,
197                             const SkBitmap& actualBm) {
198     REPORTER_ASSERT(r, expectedBm.colorType() == actualBm.colorType());
199     REPORTER_ASSERT(r, expectedBm.dimensions() == actualBm.dimensions());
200     for (int i = 0; i < actualBm.width();  ++i)
201     for (int j = 0; j < actualBm.height(); ++j) {
202         SkColor expected = SkUnPreMultiply::PMColorToColor(*expectedBm.getAddr32(i, j));
203         SkColor actual   = SkUnPreMultiply::PMColorToColor(*actualBm  .getAddr32(i, j));
204         if (expected != actual) {
205             ERRORF(r, "frame %i of %s does not match at pixel %i, %i!"
206                             " expected %x\tactual: %x",
207                             expectedFrame, file, i, j, expected, actual);
208             SkString expected_name = SkStringPrintf("expected_%c", '0' + expectedFrame);
209             SkString actual_name   = SkStringPrintf("actual_%c",   '0' + expectedFrame);
210             write_bm(expected_name.c_str(), expectedBm);
211             write_bm(actual_name.c_str(),   actualBm);
212             return false;
213         }
214     }
215     return true;
216 }
217 
DEF_TEST(AnimatedImage_copyOnWrite,r)218 DEF_TEST(AnimatedImage_copyOnWrite, r) {
219     if (GetResourcePath().isEmpty()) {
220         return;
221     }
222     for (const char* file : { "images/alphabetAnim.gif",
223                               "images/colorTables.gif",
224                               "images/stoplight.webp",
225                               "images/required.webp",
226                               }) {
227         auto data = GetResourceAsData(file);
228         if (!data) {
229             ERRORF(r, "Could not get %s", file);
230             continue;
231         }
232 
233         auto codec = SkCodec::MakeFromData(data);
234         if (!codec) {
235             ERRORF(r, "Could not create codec for %s", file);
236             continue;
237         }
238 
239         const auto imageInfo = codec->getInfo().makeAlphaType(kPremul_SkAlphaType);
240         const int frameCount = codec->getFrameCount();
241         auto androidCodec = SkAndroidCodec::MakeFromCodec(std::move(codec));
242         if (!androidCodec) {
243             ERRORF(r, "Could not create androidCodec for %s", file);
244             continue;
245         }
246 
247         auto animatedImage = SkAnimatedImage::Make(std::move(androidCodec));
248         if (!animatedImage) {
249             ERRORF(r, "Could not create animated image for %s", file);
250             continue;
251         }
252         animatedImage->setRepetitionCount(0);
253 
254         std::vector<SkBitmap> expected(frameCount);
255         std::vector<sk_sp<SkPicture>> pictures(frameCount);
256         for (int i = 0; i < frameCount; i++) {
257             SkBitmap& bm = expected[i];
258             bm.allocPixels(imageInfo);
259             bm.eraseColor(SK_ColorTRANSPARENT);
260             SkCanvas canvas(bm);
261 
262             pictures[i] = animatedImage->makePictureSnapshot();
263             canvas.drawPicture(pictures[i]);
264 
265             const auto duration = animatedImage->decodeNextFrame();
266             // We're attempting to decode i + 1, so decodeNextFrame will return
267             // kFinished if that is the last frame (or we attempt to decode one
268             // more).
269             if (i >= frameCount - 2) {
270                 REPORTER_ASSERT(r, duration == SkAnimatedImage::kFinished);
271             } else {
272                 REPORTER_ASSERT(r, duration != SkAnimatedImage::kFinished);
273             }
274         }
275 
276         for (int i = 0; i < frameCount; i++) {
277             SkBitmap test;
278             test.allocPixels(imageInfo);
279             test.eraseColor(SK_ColorTRANSPARENT);
280             SkCanvas canvas(test);
281 
282             canvas.drawPicture(pictures[i]);
283 
284             compare_bitmaps(r, file, i, expected[i], test);
285         }
286     }
287 }
288 
DEF_TEST(AnimatedImage,r)289 DEF_TEST(AnimatedImage, r) {
290     if (GetResourcePath().isEmpty()) {
291         return;
292     }
293     for (const char* file : { "images/alphabetAnim.gif",
294                               "images/colorTables.gif",
295                               "images/stoplight.webp",
296                               "images/required.webp",
297                               }) {
298         auto data = GetResourceAsData(file);
299         if (!data) {
300             ERRORF(r, "Could not get %s", file);
301             continue;
302         }
303 
304         auto codec = SkCodec::MakeFromData(data);
305         if (!codec) {
306             ERRORF(r, "Could not create codec for %s", file);
307             continue;
308         }
309 
310         std::vector<SkCodec::FrameInfo> frameInfos = codec->getFrameInfo();
311         std::vector<SkBitmap> frames(frameInfos.size());
312         // Used down below for our test image.
313         const auto imageInfo = codec->getInfo().makeAlphaType(kPremul_SkAlphaType);
314 
315         // Get the repetition count after the codec->getFrameInfo() call above
316         // has walked to the end of the encoded image.
317         //
318         // At the file format level, GIF images can declare their repetition
319         // count multiple times and our codec goes with "last one wins".
320         // Furthermore, for single-frame (still) GIF images, a zero, positive
321         // or infinite repetition count are all equivalent in practice (in all
322         // cases, the pixels do not change over time), so the codec has some
323         // leeway in what to return for single-frame GIF images, but it cannot
324         // distinguish single-frame from multiple-frame GIFs until we count the
325         // number of frames (e.g. call getFrameInfo).
326         const int defaultRepetitionCount = codec->getRepetitionCount();
327 
328         for (size_t i = 0; i < frameInfos.size(); ++i) {
329             auto info = codec->getInfo().makeAlphaType(frameInfos[i].fAlphaType);
330             auto& bm = frames[i];
331 
332             SkCodec::Options options;
333             options.fFrameIndex = (int) i;
334             options.fPriorFrame = frameInfos[i].fRequiredFrame;
335             if (options.fPriorFrame == SkCodec::kNoFrame) {
336                 bm.allocPixels(info);
337                 bm.eraseColor(0);
338             } else {
339                 const SkBitmap& priorFrame = frames[options.fPriorFrame];
340                 if (!ToolUtils::copy_to(&bm, priorFrame.colorType(), priorFrame)) {
341                     ERRORF(r, "Failed to copy %s frame %i", file, options.fPriorFrame);
342                     options.fPriorFrame = SkCodec::kNoFrame;
343                 }
344                 REPORTER_ASSERT(r, bm.setAlphaType(frameInfos[i].fAlphaType));
345             }
346 
347             auto result = codec->getPixels(info, bm.getPixels(), bm.rowBytes(), &options);
348             if (result != SkCodec::kSuccess) {
349                 ERRORF(r, "error in %s frame %zu: %s", file, i, SkCodec::ResultToString(result));
350             }
351         }
352 
353         auto androidCodec = SkAndroidCodec::MakeFromCodec(std::move(codec));
354         if (!androidCodec) {
355             ERRORF(r, "Could not create androidCodec for %s", file);
356             continue;
357         }
358 
359         auto animatedImage = SkAnimatedImage::Make(std::move(androidCodec));
360         if (!animatedImage) {
361             ERRORF(r, "Could not create animated image for %s", file);
362             continue;
363         }
364 
365         REPORTER_ASSERT(r, defaultRepetitionCount == animatedImage->getRepetitionCount());
366 
367         auto testDraw = [r, &frames, &imageInfo, file](const sk_sp<SkAnimatedImage>& animatedImage,
368                                                        int expectedFrame) {
369             SkBitmap test;
370             test.allocPixels(imageInfo);
371             test.eraseColor(0);
372             SkCanvas c(test);
373             animatedImage->draw(&c);
374 
375             const SkBitmap& frame = frames[expectedFrame];
376             return compare_bitmaps(r, file, expectedFrame, frame, test);
377         };
378 
379         REPORTER_ASSERT(r, animatedImage->currentFrameDuration() == frameInfos[0].fDuration);
380 
381         if (!testDraw(animatedImage, 0)) {
382             ERRORF(r, "Did not start with frame 0");
383             continue;
384         }
385 
386         // Start at an arbitrary time.
387         bool failed = false;
388         for (size_t i = 1; i < frameInfos.size(); ++i) {
389             const int frameTime = animatedImage->decodeNextFrame();
390             REPORTER_ASSERT(r, frameTime == animatedImage->currentFrameDuration());
391 
392             if (i == frameInfos.size() - 1 && defaultRepetitionCount == 0) {
393                 REPORTER_ASSERT(r, frameTime == SkAnimatedImage::kFinished);
394                 REPORTER_ASSERT(r, animatedImage->isFinished());
395             } else {
396                 REPORTER_ASSERT(r, frameTime == frameInfos[i].fDuration);
397                 REPORTER_ASSERT(r, !animatedImage->isFinished());
398             }
399 
400             if (!testDraw(animatedImage, i)) {
401                 ERRORF(r, "Did not update to %zu properly", i);
402                 failed = true;
403                 break;
404             }
405         }
406 
407         if (failed) {
408             continue;
409         }
410 
411         animatedImage->reset();
412         REPORTER_ASSERT(r, !animatedImage->isFinished());
413         if (!testDraw(animatedImage, 0)) {
414             ERRORF(r, "reset failed");
415             continue;
416         }
417 
418         // Test reset from all the frames.
419         // j is the frame to call reset on.
420         for (int j = 0; j < (int) frameInfos.size(); ++j) {
421             if (failed) {
422                 break;
423             }
424 
425             // i is the frame to decode.
426             for (int i = 0; i <= j; ++i) {
427                 if (i == j) {
428                     animatedImage->reset();
429                     if (!testDraw(animatedImage, 0)) {
430                         ERRORF(r, "reset failed for image %s from frame %i",
431                                 file, i);
432                         failed = true;
433                         break;
434                     }
435                 } else if (i != 0) {
436                     animatedImage->decodeNextFrame();
437                     if (!testDraw(animatedImage, i)) {
438                         ERRORF(r, "failed to match frame %i in %s on iteration %i",
439                                 i, file, j);
440                         failed = true;
441                         break;
442                     }
443                 }
444             }
445         }
446 
447         if (failed) {
448             continue;
449         }
450 
451         for (int loopCount : { 0, 1, 2, 5 }) {
452             animatedImage = SkAnimatedImage::Make(SkAndroidCodec::MakeFromCodec(
453                         SkCodec::MakeFromData(data)));
454             animatedImage->setRepetitionCount(loopCount);
455             REPORTER_ASSERT(r, animatedImage->getRepetitionCount() == loopCount);
456 
457             for (int loops = 0; loops <= loopCount; loops++) {
458                 if (failed) {
459                     break;
460                 }
461                 REPORTER_ASSERT(r, !animatedImage->isFinished());
462                 for (size_t i = 1; i <= frameInfos.size(); ++i) {
463                     const int frameTime = animatedImage->decodeNextFrame();
464                     if (frameTime == SkAnimatedImage::kFinished) {
465                         if (loops != loopCount) {
466                             ERRORF(r, "%s animation stopped early: loops: %i\tloopCount: %i",
467                                     file, loops, loopCount);
468                             failed = true;
469                         }
470                         if (i != frameInfos.size() - 1) {
471                             ERRORF(r, "%s animation stopped early: i: %zu\tsize: %zu",
472                                     file, i, frameInfos.size());
473                             failed = true;
474                         }
475                         break;
476                     }
477                 }
478             }
479 
480             if (!animatedImage->isFinished()) {
481                 ERRORF(r, "%s animation should have finished with specified loop count (%i)",
482                           file, loopCount);
483             }
484         }
485     }
486 }
487