1*c8dee2aaSAndroid Build Coastguard Worker /* 2*c8dee2aaSAndroid Build Coastguard Worker * Copyright 2016 Google Inc. 3*c8dee2aaSAndroid Build Coastguard Worker * 4*c8dee2aaSAndroid Build Coastguard Worker * Use of this source code is governed by a BSD-style license that can be 5*c8dee2aaSAndroid Build Coastguard Worker * found in the LICENSE file. 6*c8dee2aaSAndroid Build Coastguard Worker */ 7*c8dee2aaSAndroid Build Coastguard Worker 8*c8dee2aaSAndroid Build Coastguard Worker #include "gm/gm.h" 9*c8dee2aaSAndroid Build Coastguard Worker #include "include/codec/SkCodec.h" 10*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkBitmap.h" 11*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkCanvas.h" 12*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkData.h" 13*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkImageInfo.h" 14*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkScalar.h" 15*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkSize.h" 16*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkStream.h" 17*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkString.h" 18*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkTypes.h" 19*c8dee2aaSAndroid Build Coastguard Worker #include "modules/skresources/src/SkAnimCodecPlayer.h" 20*c8dee2aaSAndroid Build Coastguard Worker #include "src/core/SkOSFile.h" 21*c8dee2aaSAndroid Build Coastguard Worker #include "tools/Resources.h" 22*c8dee2aaSAndroid Build Coastguard Worker #include "tools/ToolUtils.h" 23*c8dee2aaSAndroid Build Coastguard Worker #include "tools/flags/CommandLineFlags.h" 24*c8dee2aaSAndroid Build Coastguard Worker #include "tools/timer/TimeUtils.h" 25*c8dee2aaSAndroid Build Coastguard Worker 26*c8dee2aaSAndroid Build Coastguard Worker #include <memory> 27*c8dee2aaSAndroid Build Coastguard Worker #include <utility> 28*c8dee2aaSAndroid Build Coastguard Worker #include <vector> 29*c8dee2aaSAndroid Build Coastguard Worker 30*c8dee2aaSAndroid Build Coastguard Worker #if defined(SK_ENABLE_SKOTTIE) 31*c8dee2aaSAndroid Build Coastguard Worker 32*c8dee2aaSAndroid Build Coastguard Worker static DEFINE_string(animatedGif, "images/test640x479.gif", "Animated gif in resources folder"); 33*c8dee2aaSAndroid Build Coastguard Worker 34*c8dee2aaSAndroid Build Coastguard Worker class AnimatedGifGM : public skiagm::GM { 35*c8dee2aaSAndroid Build Coastguard Worker private: 36*c8dee2aaSAndroid Build Coastguard Worker std::unique_ptr<SkCodec> fCodec; 37*c8dee2aaSAndroid Build Coastguard Worker int fFrame; 38*c8dee2aaSAndroid Build Coastguard Worker double fNextUpdate; 39*c8dee2aaSAndroid Build Coastguard Worker int fTotalFrames; 40*c8dee2aaSAndroid Build Coastguard Worker std::vector<SkCodec::FrameInfo> fFrameInfos; 41*c8dee2aaSAndroid Build Coastguard Worker std::vector<SkBitmap> fFrames; 42*c8dee2aaSAndroid Build Coastguard Worker drawFrame(SkCanvas * canvas,int frameIndex)43*c8dee2aaSAndroid Build Coastguard Worker void drawFrame(SkCanvas* canvas, int frameIndex) { 44*c8dee2aaSAndroid Build Coastguard Worker // FIXME: Create from an Image/ImageGenerator? 45*c8dee2aaSAndroid Build Coastguard Worker if (frameIndex >= (int) fFrames.size()) { 46*c8dee2aaSAndroid Build Coastguard Worker fFrames.resize(frameIndex + 1); 47*c8dee2aaSAndroid Build Coastguard Worker } 48*c8dee2aaSAndroid Build Coastguard Worker SkBitmap& bm = fFrames[frameIndex]; 49*c8dee2aaSAndroid Build Coastguard Worker if (!bm.getPixels()) { 50*c8dee2aaSAndroid Build Coastguard Worker const SkImageInfo info = fCodec->getInfo().makeColorType(kN32_SkColorType); 51*c8dee2aaSAndroid Build Coastguard Worker bm.allocPixels(info); 52*c8dee2aaSAndroid Build Coastguard Worker 53*c8dee2aaSAndroid Build Coastguard Worker SkCodec::Options opts; 54*c8dee2aaSAndroid Build Coastguard Worker opts.fFrameIndex = frameIndex; 55*c8dee2aaSAndroid Build Coastguard Worker const int requiredFrame = fFrameInfos[frameIndex].fRequiredFrame; 56*c8dee2aaSAndroid Build Coastguard Worker if (requiredFrame != SkCodec::kNoFrame) { 57*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(requiredFrame >= 0 58*c8dee2aaSAndroid Build Coastguard Worker && static_cast<size_t>(requiredFrame) < fFrames.size()); 59*c8dee2aaSAndroid Build Coastguard Worker SkBitmap& requiredBitmap = fFrames[requiredFrame]; 60*c8dee2aaSAndroid Build Coastguard Worker // For simplicity, do not try to cache old frames 61*c8dee2aaSAndroid Build Coastguard Worker if (requiredBitmap.getPixels() && 62*c8dee2aaSAndroid Build Coastguard Worker ToolUtils::copy_to(&bm, requiredBitmap.colorType(), requiredBitmap)) { 63*c8dee2aaSAndroid Build Coastguard Worker opts.fPriorFrame = requiredFrame; 64*c8dee2aaSAndroid Build Coastguard Worker } 65*c8dee2aaSAndroid Build Coastguard Worker } 66*c8dee2aaSAndroid Build Coastguard Worker 67*c8dee2aaSAndroid Build Coastguard Worker if (SkCodec::kSuccess != fCodec->getPixels(info, bm.getPixels(), 68*c8dee2aaSAndroid Build Coastguard Worker bm.rowBytes(), &opts)) { 69*c8dee2aaSAndroid Build Coastguard Worker SkDebugf("Could not getPixels for frame %i: %s", frameIndex, FLAGS_animatedGif[0]); 70*c8dee2aaSAndroid Build Coastguard Worker return; 71*c8dee2aaSAndroid Build Coastguard Worker } 72*c8dee2aaSAndroid Build Coastguard Worker } 73*c8dee2aaSAndroid Build Coastguard Worker 74*c8dee2aaSAndroid Build Coastguard Worker canvas->drawImage(bm.asImage(), 0, 0); 75*c8dee2aaSAndroid Build Coastguard Worker } 76*c8dee2aaSAndroid Build Coastguard Worker 77*c8dee2aaSAndroid Build Coastguard Worker public: AnimatedGifGM()78*c8dee2aaSAndroid Build Coastguard Worker AnimatedGifGM() 79*c8dee2aaSAndroid Build Coastguard Worker : fFrame(0) 80*c8dee2aaSAndroid Build Coastguard Worker , fNextUpdate (-1) 81*c8dee2aaSAndroid Build Coastguard Worker , fTotalFrames (-1) {} 82*c8dee2aaSAndroid Build Coastguard Worker 83*c8dee2aaSAndroid Build Coastguard Worker private: getName() const84*c8dee2aaSAndroid Build Coastguard Worker SkString getName() const override { return SkString("animatedGif"); } 85*c8dee2aaSAndroid Build Coastguard Worker getISize()86*c8dee2aaSAndroid Build Coastguard Worker SkISize getISize() override { 87*c8dee2aaSAndroid Build Coastguard Worker if (this->initCodec()) { 88*c8dee2aaSAndroid Build Coastguard Worker SkISize dim = fCodec->getInfo().dimensions(); 89*c8dee2aaSAndroid Build Coastguard Worker // Wide enough to display all the frames. 90*c8dee2aaSAndroid Build Coastguard Worker dim.fWidth *= fTotalFrames; 91*c8dee2aaSAndroid Build Coastguard Worker // Tall enough to show the row of frames plus an animating version. 92*c8dee2aaSAndroid Build Coastguard Worker dim.fHeight *= 2; 93*c8dee2aaSAndroid Build Coastguard Worker return dim; 94*c8dee2aaSAndroid Build Coastguard Worker } 95*c8dee2aaSAndroid Build Coastguard Worker return SkISize::Make(640, 480); 96*c8dee2aaSAndroid Build Coastguard Worker } 97*c8dee2aaSAndroid Build Coastguard Worker initCodec()98*c8dee2aaSAndroid Build Coastguard Worker bool initCodec() { 99*c8dee2aaSAndroid Build Coastguard Worker if (fCodec) { 100*c8dee2aaSAndroid Build Coastguard Worker return true; 101*c8dee2aaSAndroid Build Coastguard Worker } 102*c8dee2aaSAndroid Build Coastguard Worker if (FLAGS_animatedGif.isEmpty()) { 103*c8dee2aaSAndroid Build Coastguard Worker SkDebugf("Nothing specified for --animatedGif!"); 104*c8dee2aaSAndroid Build Coastguard Worker return false; 105*c8dee2aaSAndroid Build Coastguard Worker } 106*c8dee2aaSAndroid Build Coastguard Worker 107*c8dee2aaSAndroid Build Coastguard Worker std::unique_ptr<SkStream> stream(GetResourceAsStream(FLAGS_animatedGif[0])); 108*c8dee2aaSAndroid Build Coastguard Worker if (!stream) { 109*c8dee2aaSAndroid Build Coastguard Worker return false; 110*c8dee2aaSAndroid Build Coastguard Worker } 111*c8dee2aaSAndroid Build Coastguard Worker 112*c8dee2aaSAndroid Build Coastguard Worker fCodec = SkCodec::MakeFromStream(std::move(stream)); 113*c8dee2aaSAndroid Build Coastguard Worker if (!fCodec) { 114*c8dee2aaSAndroid Build Coastguard Worker return false; 115*c8dee2aaSAndroid Build Coastguard Worker } 116*c8dee2aaSAndroid Build Coastguard Worker 117*c8dee2aaSAndroid Build Coastguard Worker fFrame = 0; 118*c8dee2aaSAndroid Build Coastguard Worker fFrameInfos = fCodec->getFrameInfo(); 119*c8dee2aaSAndroid Build Coastguard Worker fTotalFrames = fFrameInfos.size(); 120*c8dee2aaSAndroid Build Coastguard Worker return true; 121*c8dee2aaSAndroid Build Coastguard Worker } 122*c8dee2aaSAndroid Build Coastguard Worker onDraw(SkCanvas * canvas,SkString * errorMsg)123*c8dee2aaSAndroid Build Coastguard Worker DrawResult onDraw(SkCanvas* canvas, SkString* errorMsg) override { 124*c8dee2aaSAndroid Build Coastguard Worker if (!this->initCodec()) { 125*c8dee2aaSAndroid Build Coastguard Worker errorMsg->printf("Could not create codec from %s", FLAGS_animatedGif[0]); 126*c8dee2aaSAndroid Build Coastguard Worker return DrawResult::kFail; 127*c8dee2aaSAndroid Build Coastguard Worker } 128*c8dee2aaSAndroid Build Coastguard Worker 129*c8dee2aaSAndroid Build Coastguard Worker canvas->save(); 130*c8dee2aaSAndroid Build Coastguard Worker for (int frameIndex = 0; frameIndex < fTotalFrames; frameIndex++) { 131*c8dee2aaSAndroid Build Coastguard Worker this->drawFrame(canvas, frameIndex); 132*c8dee2aaSAndroid Build Coastguard Worker canvas->translate(SkIntToScalar(fCodec->getInfo().width()), 0); 133*c8dee2aaSAndroid Build Coastguard Worker } 134*c8dee2aaSAndroid Build Coastguard Worker canvas->restore(); 135*c8dee2aaSAndroid Build Coastguard Worker 136*c8dee2aaSAndroid Build Coastguard Worker SkAutoCanvasRestore acr(canvas, true); 137*c8dee2aaSAndroid Build Coastguard Worker canvas->translate(0, SkIntToScalar(fCodec->getInfo().height())); 138*c8dee2aaSAndroid Build Coastguard Worker this->drawFrame(canvas, fFrame); 139*c8dee2aaSAndroid Build Coastguard Worker return DrawResult::kOk; 140*c8dee2aaSAndroid Build Coastguard Worker } 141*c8dee2aaSAndroid Build Coastguard Worker onAnimate(double nanos)142*c8dee2aaSAndroid Build Coastguard Worker bool onAnimate(double nanos) override { 143*c8dee2aaSAndroid Build Coastguard Worker if (!fCodec || fTotalFrames == 1) { 144*c8dee2aaSAndroid Build Coastguard Worker return false; 145*c8dee2aaSAndroid Build Coastguard Worker } 146*c8dee2aaSAndroid Build Coastguard Worker 147*c8dee2aaSAndroid Build Coastguard Worker double secs = TimeUtils::NanosToMSec(nanos) * .1; 148*c8dee2aaSAndroid Build Coastguard Worker if (fNextUpdate < double(0)) { 149*c8dee2aaSAndroid Build Coastguard Worker // This is a sentinel that we have not done any updates yet. 150*c8dee2aaSAndroid Build Coastguard Worker // I'm assuming this gets called *after* onOnceBeforeDraw, so our first frame should 151*c8dee2aaSAndroid Build Coastguard Worker // already have been retrieved. 152*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(fFrame == 0); 153*c8dee2aaSAndroid Build Coastguard Worker fNextUpdate = secs + fFrameInfos[fFrame].fDuration; 154*c8dee2aaSAndroid Build Coastguard Worker 155*c8dee2aaSAndroid Build Coastguard Worker return true; 156*c8dee2aaSAndroid Build Coastguard Worker } 157*c8dee2aaSAndroid Build Coastguard Worker 158*c8dee2aaSAndroid Build Coastguard Worker if (secs < fNextUpdate) { 159*c8dee2aaSAndroid Build Coastguard Worker return true; 160*c8dee2aaSAndroid Build Coastguard Worker } 161*c8dee2aaSAndroid Build Coastguard Worker 162*c8dee2aaSAndroid Build Coastguard Worker while (secs >= fNextUpdate) { 163*c8dee2aaSAndroid Build Coastguard Worker // Retrieve the next frame. 164*c8dee2aaSAndroid Build Coastguard Worker fFrame++; 165*c8dee2aaSAndroid Build Coastguard Worker if (fFrame == fTotalFrames) { 166*c8dee2aaSAndroid Build Coastguard Worker fFrame = 0; 167*c8dee2aaSAndroid Build Coastguard Worker } 168*c8dee2aaSAndroid Build Coastguard Worker 169*c8dee2aaSAndroid Build Coastguard Worker // Note that we loop here. This is not safe if we need to draw the intermediate frame 170*c8dee2aaSAndroid Build Coastguard Worker // in order to draw correctly. 171*c8dee2aaSAndroid Build Coastguard Worker fNextUpdate += fFrameInfos[fFrame].fDuration; 172*c8dee2aaSAndroid Build Coastguard Worker } 173*c8dee2aaSAndroid Build Coastguard Worker 174*c8dee2aaSAndroid Build Coastguard Worker return true; 175*c8dee2aaSAndroid Build Coastguard Worker } 176*c8dee2aaSAndroid Build Coastguard Worker }; 177*c8dee2aaSAndroid Build Coastguard Worker DEF_GM(return new AnimatedGifGM;) 178*c8dee2aaSAndroid Build Coastguard Worker 179*c8dee2aaSAndroid Build Coastguard Worker class AnimCodecPlayerExifGM : public skiagm::GM { 180*c8dee2aaSAndroid Build Coastguard Worker const char* fPath; 181*c8dee2aaSAndroid Build Coastguard Worker SkISize fSize = SkISize::MakeEmpty(); 182*c8dee2aaSAndroid Build Coastguard Worker std::unique_ptr<SkAnimCodecPlayer> fPlayer; 183*c8dee2aaSAndroid Build Coastguard Worker std::vector<SkCodec::FrameInfo> fFrameInfos; 184*c8dee2aaSAndroid Build Coastguard Worker init()185*c8dee2aaSAndroid Build Coastguard Worker void init() { 186*c8dee2aaSAndroid Build Coastguard Worker if (!fPlayer) { 187*c8dee2aaSAndroid Build Coastguard Worker auto data = GetResourceAsData(fPath); 188*c8dee2aaSAndroid Build Coastguard Worker if (!data) return; 189*c8dee2aaSAndroid Build Coastguard Worker 190*c8dee2aaSAndroid Build Coastguard Worker auto codec = SkCodec::MakeFromData(std::move(data)); 191*c8dee2aaSAndroid Build Coastguard Worker fFrameInfos = codec->getFrameInfo(); 192*c8dee2aaSAndroid Build Coastguard Worker fPlayer = std::make_unique<SkAnimCodecPlayer>(std::move(codec)); 193*c8dee2aaSAndroid Build Coastguard Worker if (!fPlayer) return; 194*c8dee2aaSAndroid Build Coastguard Worker 195*c8dee2aaSAndroid Build Coastguard Worker // We'll draw one of each frame, so make it big enough to hold them all 196*c8dee2aaSAndroid Build Coastguard Worker // in a grid. The grid will be roughly square, with "factor" frames per 197*c8dee2aaSAndroid Build Coastguard Worker // row and up to "factor" rows. 198*c8dee2aaSAndroid Build Coastguard Worker const size_t count = fFrameInfos.size(); 199*c8dee2aaSAndroid Build Coastguard Worker const float root = sqrt((float) count); 200*c8dee2aaSAndroid Build Coastguard Worker const int factor = sk_float_ceil2int(root); 201*c8dee2aaSAndroid Build Coastguard Worker 202*c8dee2aaSAndroid Build Coastguard Worker auto imageSize = fPlayer->dimensions(); 203*c8dee2aaSAndroid Build Coastguard Worker fSize.fWidth = imageSize.fWidth * factor; 204*c8dee2aaSAndroid Build Coastguard Worker fSize.fHeight = imageSize.fHeight * sk_float_ceil2int((float) count / (float) factor); 205*c8dee2aaSAndroid Build Coastguard Worker } 206*c8dee2aaSAndroid Build Coastguard Worker } 207*c8dee2aaSAndroid Build Coastguard Worker getName() const208*c8dee2aaSAndroid Build Coastguard Worker SkString getName() const override { 209*c8dee2aaSAndroid Build Coastguard Worker return SkStringPrintf("AnimCodecPlayerExif_%s", strrchr(fPath, '/') + 1); 210*c8dee2aaSAndroid Build Coastguard Worker } 211*c8dee2aaSAndroid Build Coastguard Worker getISize()212*c8dee2aaSAndroid Build Coastguard Worker SkISize getISize() override { 213*c8dee2aaSAndroid Build Coastguard Worker this->init(); 214*c8dee2aaSAndroid Build Coastguard Worker return fSize; 215*c8dee2aaSAndroid Build Coastguard Worker } 216*c8dee2aaSAndroid Build Coastguard Worker onDraw(SkCanvas * canvas)217*c8dee2aaSAndroid Build Coastguard Worker void onDraw(SkCanvas* canvas) override { 218*c8dee2aaSAndroid Build Coastguard Worker this->init(); 219*c8dee2aaSAndroid Build Coastguard Worker if (!fPlayer) return; 220*c8dee2aaSAndroid Build Coastguard Worker 221*c8dee2aaSAndroid Build Coastguard Worker const float root = sqrt((float) fFrameInfos.size()); 222*c8dee2aaSAndroid Build Coastguard Worker const int factor = sk_float_ceil2int(root); 223*c8dee2aaSAndroid Build Coastguard Worker auto dimensions = fPlayer->dimensions(); 224*c8dee2aaSAndroid Build Coastguard Worker 225*c8dee2aaSAndroid Build Coastguard Worker uint32_t duration = 0; 226*c8dee2aaSAndroid Build Coastguard Worker for (int frame = 0; duration < fPlayer->duration(); frame++) { 227*c8dee2aaSAndroid Build Coastguard Worker SkAutoCanvasRestore acr(canvas, true); 228*c8dee2aaSAndroid Build Coastguard Worker const int xTranslate = (frame % factor) * dimensions.width(); 229*c8dee2aaSAndroid Build Coastguard Worker const int yTranslate = (frame / factor) * dimensions.height(); 230*c8dee2aaSAndroid Build Coastguard Worker canvas->translate(SkIntToScalar(xTranslate), SkIntToScalar(yTranslate)); 231*c8dee2aaSAndroid Build Coastguard Worker 232*c8dee2aaSAndroid Build Coastguard Worker 233*c8dee2aaSAndroid Build Coastguard Worker auto image = fPlayer->getFrame(); 234*c8dee2aaSAndroid Build Coastguard Worker canvas->drawImage(image, 0, 0); 235*c8dee2aaSAndroid Build Coastguard Worker duration += fFrameInfos[frame].fDuration; 236*c8dee2aaSAndroid Build Coastguard Worker fPlayer->seek(duration); 237*c8dee2aaSAndroid Build Coastguard Worker } 238*c8dee2aaSAndroid Build Coastguard Worker } 239*c8dee2aaSAndroid Build Coastguard Worker public: AnimCodecPlayerExifGM(const char * path)240*c8dee2aaSAndroid Build Coastguard Worker AnimCodecPlayerExifGM(const char* path) 241*c8dee2aaSAndroid Build Coastguard Worker : fPath(path) 242*c8dee2aaSAndroid Build Coastguard Worker {} 243*c8dee2aaSAndroid Build Coastguard Worker 244*c8dee2aaSAndroid Build Coastguard Worker ~AnimCodecPlayerExifGM() override = default; 245*c8dee2aaSAndroid Build Coastguard Worker }; 246*c8dee2aaSAndroid Build Coastguard Worker 247*c8dee2aaSAndroid Build Coastguard Worker DEF_GM(return new AnimCodecPlayerExifGM("images/required.webp");) 248*c8dee2aaSAndroid Build Coastguard Worker DEF_GM(return new AnimCodecPlayerExifGM("images/required.gif");) 249*c8dee2aaSAndroid Build Coastguard Worker DEF_GM(return new AnimCodecPlayerExifGM("images/stoplight_h.webp");) 250*c8dee2aaSAndroid Build Coastguard Worker 251*c8dee2aaSAndroid Build Coastguard Worker #endif 252