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