1 /*
2  * Copyright 2020 Google LLC
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 "gm/gm.h"
9 #include "include/android/SkAnimatedImage.h"
10 #include "include/codec/SkAndroidCodec.h"
11 #include "include/codec/SkCodec.h"
12 #include "include/core/SkBlendMode.h"
13 #include "include/core/SkCanvas.h"
14 #include "include/core/SkData.h"
15 #include "include/core/SkPathBuilder.h"
16 #include "include/core/SkPathTypes.h"
17 #include "include/core/SkPicture.h"
18 #include "include/core/SkPictureRecorder.h"
19 #include "include/core/SkRRect.h"
20 #include "tools/Resources.h"
21 
post_processor(const SkRect & bounds)22 static sk_sp<SkPicture> post_processor(const SkRect& bounds) {
23     int radius = (bounds.width() + bounds.height()) / 6;
24     SkPathBuilder pathBuilder;
25     pathBuilder.setFillType(SkPathFillType::kInverseEvenOdd)
26                .addRRect(SkRRect::MakeRectXY(bounds, radius, radius));
27 
28     SkPaint paint;
29     paint.setAntiAlias(true);
30     paint.setColor(SK_ColorTRANSPARENT);
31     paint.setBlendMode(SkBlendMode::kSrc);
32 
33     SkPictureRecorder recorder;
34     auto* canvas = recorder.beginRecording(bounds);
35     canvas->drawPath(pathBuilder.detach(), paint);
36     return recorder.finishRecordingAsPicture();
37 }
38 
39 class AnimatedImageGM : public skiagm::GM {
40     const char*   fPath;
41     const char*   fName;
42     const int     fStep;
43     const SkIRect fCropRect;
44     SkISize       fSize;
45     int           fTranslate;
46     sk_sp<SkData> fData;
47 
48     static const int kMaxFrames = 2;
49 
init()50     void init() {
51         if (!fData) {
52             fData = GetResourceAsData(fPath);
53             auto codec = SkCodec::MakeFromData(fData);
54             auto dimensions = codec->dimensions();
55 
56             fTranslate = std::max(dimensions.width(), dimensions.height()) // may be rotated
57                          * 1.25f    // will be scaled up
58                          + 2;       // padding
59 
60             fSize = { fTranslate * kMaxFrames
61                                  * 2    // crop and no-crop
62                                  * 2,   // post-process and no post-process
63                       fTranslate * 4    // 4 scales
64                                  * 2 }; // newPictureSnapshot and getCurrentFrame
65         }
66     }
67 public:
AnimatedImageGM(const char * path,const char * name,int step,SkIRect cropRect)68     AnimatedImageGM(const char* path, const char* name, int step, SkIRect cropRect)
69         : fPath(path)
70         , fName(name)
71         , fStep(step)
72         , fCropRect(cropRect)
73         , fSize{0, 0}
74         , fTranslate(0)
75     {}
76     ~AnimatedImageGM() override = default;
77 
onShortName()78     SkString onShortName() override {
79         return SkStringPrintf("%s_animated_image", fName);
80     }
81 
onISize()82     SkISize onISize() override {
83         this->init();
84         return fSize;
85     }
86 
onDraw(SkCanvas * canvas)87     void onDraw(SkCanvas* canvas) override {
88         this->init();
89         for (bool usePic : { true, false }) {
90             auto drawProc = [canvas, usePic](const sk_sp<SkAnimatedImage>& animatedImage) {
91                 if (usePic) {
92                     sk_sp<SkPicture> pic(animatedImage->newPictureSnapshot());
93                     canvas->drawPicture(pic);
94                 } else {
95                     auto image = animatedImage->getCurrentFrame();
96                     canvas->drawImage(image, 0, 0);
97                 }
98             };
99             for (float scale : { 1.25f, 1.0f, .75f, .5f }) {
100                 canvas->save();
101                 for (bool doCrop : { false, true }) {
102                     for (bool doPostProcess : { false, true }) {
103                         auto codec = SkCodec::MakeFromData(fData);
104                         const auto origin = codec->getOrigin();
105                         auto androidCodec = SkAndroidCodec::MakeFromCodec(std::move(codec));
106                         auto info = androidCodec->getInfo();
107                         const auto unscaledSize = SkEncodedOriginSwapsWidthHeight(origin)
108                                 ? SkISize{ info.height(), info.width() } :  info.dimensions();
109 
110                         SkISize scaledSize = { SkScalarFloorToInt(unscaledSize.width()  * scale) ,
111                                                SkScalarFloorToInt(unscaledSize.height() * scale) };
112                         info = info.makeDimensions(scaledSize);
113 
114                         auto cropRect = SkIRect::MakeSize(scaledSize);
115                         if (doCrop) {
116                             auto matrix = SkMatrix::RectToRect(SkRect::Make(unscaledSize),
117                                                                SkRect::Make(scaledSize));
118                             matrix.preConcat(SkEncodedOriginToMatrix(origin,
119                                     unscaledSize.width(), unscaledSize.height()));
120                             SkRect cropRectFloat = SkRect::Make(fCropRect);
121                             matrix.mapRect(&cropRectFloat);
122                             cropRectFloat.roundOut(&cropRect);
123                         }
124 
125                         sk_sp<SkPicture> postProcessor = doPostProcess
126                                 ? post_processor(SkRect::Make(cropRect.size())) : nullptr;
127                         auto animatedImage = SkAnimatedImage::Make(std::move(androidCodec),
128                                 info, cropRect, std::move(postProcessor));
129                         animatedImage->setRepetitionCount(0);
130 
131                         for (int frame = 0; frame < kMaxFrames; frame++) {
132                             {
133                                 SkAutoCanvasRestore acr(canvas, doCrop);
134                                 if (doCrop) {
135                                     canvas->translate(cropRect.left(), cropRect.top());
136                                 }
137                                 drawProc(animatedImage);
138                             }
139 
140                             canvas->translate(fTranslate, 0);
141                             const auto duration = animatedImage->currentFrameDuration();
142                             if (duration == SkAnimatedImage::kFinished) {
143                                 break;
144                             }
145                             for (int i = 0; i < fStep; i++) {
146                                 animatedImage->decodeNextFrame();
147                             }
148                         }
149                     }
150                 }
151                 canvas->restore();
152                 canvas->translate(0, fTranslate);
153             }
154         }
155     }
156 };
157 
158 DEF_GM( return new AnimatedImageGM("images/stoplight_h.webp", "stoplight", 2,
159                                    // Deliberately not centered in X or Y, and shows all three
160                                    // lights, but otherwise arbitrary.
161                                    SkIRect::MakeLTRB(5, 6, 11, 29)); )
162 DEF_GM( return new AnimatedImageGM("images/flightAnim.gif", "flight", 20,
163                                    // Deliberately starts in the upper left corner to exercise
164                                    // a special case, but otherwise arbitrary.
165                                    SkIRect::MakeLTRB(0, 0, 300, 200)); )
166