Flutter Engine
The Flutter Engine
Functions
AnimatedImageTest.cpp File Reference
#include "include/android/SkAnimatedImage.h"
#include "include/codec/SkAndroidCodec.h"
#include "include/codec/SkCodec.h"
#include "include/core/SkAlphaType.h"
#include "include/core/SkBitmap.h"
#include "include/core/SkCanvas.h"
#include "include/core/SkColor.h"
#include "include/core/SkData.h"
#include "include/core/SkImageInfo.h"
#include "include/core/SkPicture.h"
#include "include/core/SkRect.h"
#include "include/core/SkRefCnt.h"
#include "include/core/SkSize.h"
#include "include/core/SkString.h"
#include "include/core/SkTypes.h"
#include "include/core/SkUnPreMultiply.h"
#include "tests/CodecPriv.h"
#include "tests/Test.h"
#include "tools/Resources.h"
#include "tools/ToolUtils.h"
#include <cstddef>
#include <initializer_list>
#include <memory>
#include <utility>
#include <vector>

Go to the source code of this file.

Functions

 DEF_TEST (AnimatedImage_simple, r)
 
 DEF_TEST (AnimatedImage_rotation, r)
 
 DEF_TEST (AnimatedImage_invalidCrop, r)
 
 DEF_TEST (AnimatedImage_scaled, r)
 
static bool compare_bitmaps (skiatest::Reporter *r, const char *file, int expectedFrame, const SkBitmap &expectedBm, const SkBitmap &actualBm)
 
 DEF_TEST (AnimatedImage_copyOnWrite, r)
 
 DEF_TEST (AnimatedImage, r)
 

Function Documentation

◆ compare_bitmaps()

static bool compare_bitmaps ( skiatest::Reporter r,
const char *  file,
int  expectedFrame,
const SkBitmap expectedBm,
const SkBitmap actualBm 
)
static

Definition at line 193 of file AnimatedImageTest.cpp.

197 {
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}
void write_bm(const char *name, const SkBitmap &bm)
Definition: CodecPriv.h:33
uint32_t SkColor
Definition: SkColor.h:37
SK_API SkString SkStringPrintf(const char *format,...) SK_PRINTF_LIKE(1
Creates a new string and writes into it using a printf()-style format.
#define REPORTER_ASSERT(r, cond,...)
Definition: Test.h:286
#define ERRORF(r,...)
Definition: Test.h:293
SkISize dimensions() const
Definition: SkBitmap.h:388
int width() const
Definition: SkBitmap.h:149
SkColorType colorType() const
Definition: SkBitmap.h:160
int height() const
Definition: SkBitmap.h:158
uint32_t * getAddr32(int x, int y) const
Definition: SkBitmap.h:1260
const char * c_str() const
Definition: SkString.h:133
static SkColor PMColorToColor(SkPMColor c)

◆ DEF_TEST() [1/6]

DEF_TEST ( AnimatedImage  ,
 
)

Definition at line 289 of file AnimatedImageTest.cpp.

289 {
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 }) {
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
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) {
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) {
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 }) {
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}
static bool compare_bitmaps(skiatest::Reporter *r, const char *file, int expectedFrame, const SkBitmap &expectedBm, const SkBitmap &actualBm)
const char * options
static void info(const char *fmt,...) SK_PRINTF_LIKE(1
Definition: DM.cpp:213
#define test(name)
SkAlphaType fAlphaType
sk_sp< SkData > GetResourceAsData(const char *resource)
Definition: Resources.cpp:42
SkString GetResourcePath(const char *resource)
Definition: Resources.cpp:23
@ kPremul_SkAlphaType
pixel components are premultiplied by alpha
Definition: SkAlphaType.h:29
static std::unique_ptr< SkAndroidCodec > MakeFromCodec(std::unique_ptr< SkCodec >)
static sk_sp< SkAnimatedImage > Make(std::unique_ptr< SkAndroidCodec >, const SkImageInfo &info, SkIRect cropRect, sk_sp< SkPicture > postProcess)
static constexpr int kFinished
static std::unique_ptr< SkCodec > MakeFromData(sk_sp< SkData >, SkSpan< const SkCodecs::Decoder > decoders, SkPngChunkReader *=nullptr)
Definition: SkCodec.cpp:241
static const char * ResultToString(Result)
Definition: SkCodec.cpp:880
@ kSuccess
Definition: SkCodec.h:80
static constexpr int kNoFrame
Definition: SkCodec.h:650
double frame
Definition: examples.cpp:31
GAsyncResult * result
bool copy_to(SkBitmap *dst, SkColorType dstColorType, const SkBitmap &src)
Definition: ToolUtils.cpp:394
std::shared_ptr< const fml::Mapping > data
Definition: texture_gles.cc:63

◆ DEF_TEST() [2/6]

DEF_TEST ( AnimatedImage_copyOnWrite  ,
 
)

Definition at line 218 of file AnimatedImageTest.cpp.

218 {
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 }) {
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);
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) {
271 } else {
273 }
274 }
275
276 for (int i = 0; i < frameCount; i++) {
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}
constexpr SkColor SK_ColorTRANSPARENT
Definition: SkColor.h:99
void allocPixels(const SkImageInfo &info, size_t rowBytes)
Definition: SkBitmap.cpp:258
void eraseColor(SkColor4f) const
Definition: SkBitmap.cpp:442
double duration
Definition: examples.cpp:30

◆ DEF_TEST() [3/6]

DEF_TEST ( AnimatedImage_invalidCrop  ,
 
)

Definition at line 89 of file AnimatedImageTest.cpp.

89 {
90 if (GetResourcePath().isEmpty()) {
91 return;
92 }
93
94 const char* file = "images/alphabetAnim.gif";
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) {
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}
static std::unique_ptr< SkAndroidCodec > MakeFromData(sk_sp< SkData >, SkPngChunkReader *=nullptr)
T * get() const
Definition: SkRefCnt.h:303
sk_sp< const SkImage > image
Definition: SkRecords.h:269
Definition: SkRect.h:32
Definition: SkSize.h:16
static constexpr SkISize Make(int32_t w, int32_t h)
Definition: SkSize.h:20

◆ DEF_TEST() [4/6]

DEF_TEST ( AnimatedImage_rotation  ,
 
)

Definition at line 54 of file AnimatedImageTest.cpp.

54 {
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();
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}
Optional< SkRect > bounds
Definition: SkRecords.h:189
DEF_SWITCHES_START aot vmservice shared library name
Definition: switches.h:32
static SkRect MakeIWH(int w, int h)
Definition: SkRect.h:623

◆ DEF_TEST() [5/6]

DEF_TEST ( AnimatedImage_scaled  ,
 
)

Definition at line 149 of file AnimatedImageTest.cpp.

149 {
150 if (GetResourcePath().isEmpty()) {
151 return;
152 }
153
154 const char* file = "images/alphabetAnim.gif";
156 if (!data) {
157 ERRORF(r, "Could not get %s", file);
158 return;
159 }
160
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()));
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}
constexpr SkColor SK_ColorBLUE
Definition: SkColor.h:135
sk_sp< SkBlender > blender SkRect rect
Definition: SkRecords.h:350
static SkImageInfo MakeN32Premul(int width, int height)

◆ DEF_TEST() [6/6]

DEF_TEST ( AnimatedImage_simple  ,
 
)

Definition at line 35 of file AnimatedImageTest.cpp.

35 {
36 if (GetResourcePath().isEmpty()) {
37 return;
38 }
39
40 const char* file = "images/stoplight_h.webp";
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}