blob: 91c6606f182abe2bb63c95177ef313bd96a60dbb [file] [log] [blame]
Leon Scroggins III4c119452018-01-20 10:33:24 -05001/*
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
Mike Kleinc0bd9f92019-04-23 12:05:21 -05008#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"
Leon Scroggins III4c119452018-01-20 10:33:24 -050026
Ben Wagner9707a7e2019-05-06 17:17:19 -040027#include <initializer_list>
Ben Wagner1ebeefe2018-03-02 16:59:53 -050028#include <memory>
Ben Wagner9707a7e2019-05-06 17:17:19 -040029#include <utility>
Leon Scroggins III4c119452018-01-20 10:33:24 -050030#include <vector>
31
Leon Scroggins IIIe4271602020-12-15 15:52:39 -050032DEF_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
Leon Scroggins III7c40b672021-05-10 16:27:02 -040051DEF_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
Leon Scrogginsafdbd9d2020-11-06 11:07:37 -050086DEF_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
Leon Scroggins III2f862202018-03-05 14:19:40 -0500146DEF_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.
Leon Scroggins III85463352019-02-04 15:22:10 -0500165 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);
Leon Scroggins III2f862202018-03-05 14:19:40 -0500169 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;
Leon Scroggins III85463352019-02-04 15:22:10 -0500177 bm.allocPixels(SkImageInfo::MakeN32Premul(info.width(), info.height()));
Leon Scroggins III2f862202018-03-05 14:19:40 -0500178 bm.eraseColor(SK_ColorBLUE);
179 SkCanvas canvas(bm);
180 image->draw(&canvas);
Leon Scroggins III85463352019-02-04 15:22:10 -0500181 for (int i = 0; i < info.width(); ++i)
182 for (int j = 0; j < info.height(); ++j) {
Leon Scroggins III2f862202018-03-05 14:19:40 -0500183 if (*bm.getAddr32(i, j) == SK_ColorTRANSPARENT) {
184 ERRORF(r, "Erased color underneath!");
185 return;
186 }
187 }
188}
189
Leon Scroggins III4aafb3a2018-05-23 16:15:09 -0400190static 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
215DEF_TEST(AnimatedImage_copyOnWrite, r) {
216 if (GetResourcePath().isEmpty()) {
217 return;
218 }
219 for (const char* file : { "images/alphabetAnim.gif",
220 "images/colorTables.gif",
Leon Scrogginsbc098ef2020-10-27 15:24:18 -0400221 "images/stoplight.webp",
Leon Scroggins III4aafb3a2018-05-23 16:15:09 -0400222 "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];
Leon Scroggins III698219e2019-02-04 10:12:33 -0500255 bm.allocPixels(imageInfo);
Leon Scroggins III4aafb3a2018-05-23 16:15:09 -0400256 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;
Leon Scroggins III698219e2019-02-04 10:12:33 -0500275 test.allocPixels(imageInfo);
Leon Scroggins III4aafb3a2018-05-23 16:15:09 -0400276 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
Leon Scroggins III4c119452018-01-20 10:33:24 -0500286DEF_TEST(AnimatedImage, r) {
287 if (GetResourcePath().isEmpty()) {
288 return;
289 }
290 for (const char* file : { "images/alphabetAnim.gif",
291 "images/colorTables.gif",
Leon Scrogginsbc098ef2020-10-27 15:24:18 -0400292 "images/stoplight.webp",
Leon Scroggins III4c119452018-01-20 10:33:24 -0500293 "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;
Nigel Tao66bc5242018-08-22 10:56:03 +1000320 if (options.fPriorFrame == SkCodec::kNoFrame) {
Leon Scroggins III4c119452018-01-20 10:33:24 -0500321 bm.allocPixels(info);
322 bm.eraseColor(0);
323 } else {
324 const SkBitmap& priorFrame = frames[options.fPriorFrame];
Mike Kleinea3f0142019-03-20 11:12:10 -0500325 if (!ToolUtils::copy_to(&bm, priorFrame.colorType(), priorFrame)) {
Leon Scroggins III4c119452018-01-20 10:33:24 -0500326 ERRORF(r, "Failed to copy %s frame %i", file, options.fPriorFrame);
Nigel Tao66bc5242018-08-22 10:56:03 +1000327 options.fPriorFrame = SkCodec::kNoFrame;
Leon Scroggins III4c119452018-01-20 10:33:24 -0500328 }
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
Leon Scroggins IIIabe639c2018-01-26 11:06:12 -0500350 REPORTER_ASSERT(r, defaultRepetitionCount == animatedImage->getRepetitionCount());
351
Leon Scroggins III4c119452018-01-20 10:33:24 -0500352 auto testDraw = [r, &frames, &imageInfo, file](const sk_sp<SkAnimatedImage>& animatedImage,
353 int expectedFrame) {
354 SkBitmap test;
Leon Scroggins III698219e2019-02-04 10:12:33 -0500355 test.allocPixels(imageInfo);
Leon Scroggins III4c119452018-01-20 10:33:24 -0500356 test.eraseColor(0);
357 SkCanvas c(test);
358 animatedImage->draw(&c);
359
360 const SkBitmap& frame = frames[expectedFrame];
Leon Scroggins III4aafb3a2018-05-23 16:15:09 -0400361 return compare_bitmaps(r, file, expectedFrame, frame, test);
Leon Scroggins III4c119452018-01-20 10:33:24 -0500362 };
363
Leon Scroggins III495e0f02018-01-29 19:35:55 -0500364 REPORTER_ASSERT(r, animatedImage->currentFrameDuration() == frameInfos[0].fDuration);
365
Leon Scroggins III4c119452018-01-20 10:33:24 -0500366 if (!testDraw(animatedImage, 0)) {
367 ERRORF(r, "Did not start with frame 0");
368 continue;
369 }
370
Leon Scroggins III4c119452018-01-20 10:33:24 -0500371 // Start at an arbitrary time.
Leon Scroggins III4c119452018-01-20 10:33:24 -0500372 bool failed = false;
Leon Scroggins III495e0f02018-01-29 19:35:55 -0500373 for (size_t i = 1; i < frameInfos.size(); ++i) {
374 const int frameTime = animatedImage->decodeNextFrame();
375 REPORTER_ASSERT(r, frameTime == animatedImage->currentFrameDuration());
376
Leon Scroggins III4c119452018-01-20 10:33:24 -0500377 if (i == frameInfos.size() - 1 && defaultRepetitionCount == 0) {
Leon Scroggins III495e0f02018-01-29 19:35:55 -0500378 REPORTER_ASSERT(r, frameTime == SkAnimatedImage::kFinished);
Leon Scroggins III2cb6cb12018-01-21 15:50:12 -0500379 REPORTER_ASSERT(r, animatedImage->isFinished());
Leon Scroggins III4c119452018-01-20 10:33:24 -0500380 } else {
Leon Scroggins III495e0f02018-01-29 19:35:55 -0500381 REPORTER_ASSERT(r, frameTime == frameInfos[i].fDuration);
Leon Scroggins III2cb6cb12018-01-21 15:50:12 -0500382 REPORTER_ASSERT(r, !animatedImage->isFinished());
Leon Scroggins III4c119452018-01-20 10:33:24 -0500383 }
384
385 if (!testDraw(animatedImage, i)) {
Adlai Holler684838f2020-05-12 10:41:04 -0400386 ERRORF(r, "Did not update to %zu properly", i);
Leon Scroggins III4c119452018-01-20 10:33:24 -0500387 failed = true;
388 break;
389 }
Leon Scroggins III4c119452018-01-20 10:33:24 -0500390 }
391
392 if (failed) {
393 continue;
394 }
395
Leon Scroggins III495e0f02018-01-29 19:35:55 -0500396 animatedImage->reset();
397 REPORTER_ASSERT(r, !animatedImage->isFinished());
398 if (!testDraw(animatedImage, 0)) {
399 ERRORF(r, "reset failed");
400 continue;
401 }
Leon Scroggins III4c119452018-01-20 10:33:24 -0500402
Leon Scroggins III495e0f02018-01-29 19:35:55 -0500403 // 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) {
Leon Scroggins III4c119452018-01-20 10:33:24 -0500407 break;
408 }
409
Leon Scroggins III495e0f02018-01-29 19:35:55 -0500410 // 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 }
Leon Scroggins III4c119452018-01-20 10:33:24 -0500429 }
Leon Scroggins III4c119452018-01-20 10:33:24 -0500430 }
431
432 if (failed) {
Leon Scroggins III4c119452018-01-20 10:33:24 -0500433 continue;
434 }
435
436 for (int loopCount : { 0, 1, 2, 5 }) {
437 animatedImage = SkAnimatedImage::Make(SkAndroidCodec::MakeFromCodec(
438 SkCodec::MakeFromData(data)));
Leon Scroggins III4c119452018-01-20 10:33:24 -0500439 animatedImage->setRepetitionCount(loopCount);
Leon Scroggins IIIabe639c2018-01-26 11:06:12 -0500440 REPORTER_ASSERT(r, animatedImage->getRepetitionCount() == loopCount);
441
Leon Scroggins III4c119452018-01-20 10:33:24 -0500442 for (int loops = 0; loops <= loopCount; loops++) {
Leon Scroggins III495e0f02018-01-29 19:35:55 -0500443 if (failed) {
444 break;
445 }
Leon Scroggins III2cb6cb12018-01-21 15:50:12 -0500446 REPORTER_ASSERT(r, !animatedImage->isFinished());
Leon Scroggins III495e0f02018-01-29 19:35:55 -0500447 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) {
Adlai Holler684838f2020-05-12 10:41:04 -0400456 ERRORF(r, "%s animation stopped early: i: %zu\tsize: %zu",
Leon Scroggins III495e0f02018-01-29 19:35:55 -0500457 file, i, frameInfos.size());
458 failed = true;
459 }
460 break;
Leon Scroggins III4c119452018-01-20 10:33:24 -0500461 }
462 }
463 }
Leon Scroggins III8524c302018-01-22 12:31:21 -0500464
Leon Scroggins III2cb6cb12018-01-21 15:50:12 -0500465 if (!animatedImage->isFinished()) {
466 ERRORF(r, "%s animation should have finished with specified loop count (%i)",
467 file, loopCount);
468 }
Leon Scroggins III4c119452018-01-20 10:33:24 -0500469 }
470 }
471}