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
32DEF_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
51DEF_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
86DEF_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
146DEF_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
190static 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",
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
286DEF_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