[skotty] Add Json DM source

Generates a filmstrip with evenly distributed frames for a Skotty animation.

TBR=
Change-Id: Ia89e0d65d59fd5ab4ef221a231e9b3e0c033828a
Reviewed-on: https://skia-review.googlesource.com/90025
Reviewed-by: Florin Malita <fmalita@chromium.org>
Commit-Queue: Florin Malita <fmalita@chromium.org>
diff --git a/BUILD.gn b/BUILD.gn
index a61bff7..8344b54 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -1503,6 +1503,7 @@
       include_dirs = [ "tests" ]
       deps = [
         ":common_flags",
+        ":experimental_skotty",
         ":experimental_sksg",
         ":experimental_svg_model",
         ":flags",
diff --git a/dm/DM.cpp b/dm/DM.cpp
index 6cb9cb5..246615e 100644
--- a/dm/DM.cpp
+++ b/dm/DM.cpp
@@ -784,6 +784,7 @@
         gather_file_srcs<SKPSrc>(FLAGS_skps, "skp");
     }
     gather_file_srcs<MSKPSrc>(FLAGS_mskps, "mskp");
+    gather_file_srcs<SkottySrc>(FLAGS_jsons, "json");
 #if defined(SK_XML)
     gather_file_srcs<SVGSrc>(FLAGS_svgs, "svg");
 #endif
diff --git a/dm/DMSrcSink.cpp b/dm/DMSrcSink.cpp
index c8a4bc2..beb55d9 100644
--- a/dm/DMSrcSink.cpp
+++ b/dm/DMSrcSink.cpp
@@ -32,6 +32,7 @@
 #include "SkMultiPictureDocumentPriv.h"
 #include "SkMultiPictureDraw.h"
 #include "SkNullCanvas.h"
+#include "Skotty.h"
 #include "SkOSFile.h"
 #include "SkOSPath.h"
 #include "SkOpts.h"
@@ -1310,7 +1311,86 @@
 
 Name DDLSKPSrc::name() const { return SkOSPath::Basename(fPath.c_str()); }
 
-/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+
+SkottySrc::SkottySrc(Path path)
+    : fName(SkOSPath::Basename(path.c_str())) {
+
+    auto stream = SkStream::MakeFromFile(path.c_str());
+    fAnimation  = skotty::Animation::Make(stream.get());
+
+    if (!fAnimation) {
+        return;
+    }
+
+    // Fit kTileCount x kTileCount frames to a 1000x1000 film strip.
+    static constexpr SkScalar kTargetSize = 1000;
+    const auto scale = kTargetSize / (kTileCount * std::max(fAnimation->size().width(),
+                                                            fAnimation->size().height()));
+    fTileSize = SkSize::Make(scale * fAnimation->size().width(),
+                             scale * fAnimation->size().height()).toCeil();
+
+}
+
+Error SkottySrc::draw(SkCanvas* canvas) const {
+    if (!fAnimation) {
+        return SkStringPrintf("Unable to parse file: %s", fName.c_str());
+    }
+
+    canvas->drawColor(SK_ColorWHITE);
+
+    SkPaint paint;
+    paint.setColor(0xffa0a0a0);
+    paint.setStyle(SkPaint::kStroke_Style);
+    paint.setStrokeWidth(0);
+
+    const auto ip = fAnimation->inPoint() * 1000 / fAnimation->frameRate(),
+               op = fAnimation->outPoint() * 1000 / fAnimation->frameRate(),
+               fr = (op - ip) / (kTileCount * kTileCount - 1);
+
+    const auto canvas_size = this->size();
+    for (int i = 0; i < kTileCount; ++i) {
+        const SkScalar y = i * (fTileSize.height() + 1);
+        canvas->drawLine(0, .5f + y, canvas_size.width(), .5f + y, paint);
+
+        for (int j = 0; j < kTileCount; ++j) {
+            const SkScalar x = j * (fTileSize.width() + 1);
+            canvas->drawLine(x + .5f, 0, x + .5f, canvas_size.height(), paint);
+            SkRect dest = SkRect::MakeXYWH(x, y, fTileSize.width(), fTileSize.height());
+
+            SkAutoCanvasRestore acr(canvas, true);
+            canvas->clipRect(dest);
+            canvas->concat(SkMatrix::MakeRectToRect(SkRect::MakeSize(fAnimation->size()),
+                                                    dest,
+                                                    SkMatrix::kFill_ScaleToFit));
+
+            const auto t = fr * (i * kTileCount + j);
+            fAnimation->animationTick(t);
+            fAnimation->render(canvas);
+        }
+    }
+
+    return "";
+}
+
+SkISize SkottySrc::size() const {
+    // Padding for grid.
+    return SkISize::Make(kTileCount * (fTileSize.width()  + 1),
+                         kTileCount * (fTileSize.height() + 1));
+}
+
+Name SkottySrc::name() const { return fName; }
+
+bool SkottySrc::veto(SinkFlags flags) const {
+    // No need to test to non-(raster||gpu||vector) or indirect backends.
+    bool type_ok = flags.type == SinkFlags::kRaster
+                || flags.type == SinkFlags::kGPU
+                || flags.type == SinkFlags::kVector;
+
+    return !type_ok || flags.approach != SinkFlags::kDirect;
+}
+
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
 #if defined(SK_XML)
 // Used when the image doesn't have an intrinsic size.
 static const SkSize kDefaultSVGSize = {1000, 1000};
diff --git a/dm/DMSrcSink.h b/dm/DMSrcSink.h
index 3f3c8b2..67128ea 100644
--- a/dm/DMSrcSink.h
+++ b/dm/DMSrcSink.h
@@ -21,6 +21,8 @@
 
 //#define TEST_VIA_SVG
 
+namespace skotty { class Animation; }
+
 namespace DM {
 
 // This is just convenience.  It lets you use either return "foo" or return SkStringPrintf(...).
@@ -260,6 +262,24 @@
     Path fPath;
 };
 
+class SkottySrc final : public Src {
+public:
+    explicit SkottySrc(Path path);
+
+    Error draw(SkCanvas*) const override;
+    SkISize size() const override;
+    Name name() const override;
+    bool veto(SinkFlags) const override;
+
+private:
+    // Generates a kTileCount x kTileCount filmstrip with evenly distributed frames.
+    static constexpr int               kTileCount = 5;
+
+    Name                               fName;
+    SkISize                            fTileSize = SkISize::MakeEmpty();
+    std::unique_ptr<skotty::Animation> fAnimation;
+};
+
 #if defined(SK_XML)
 } // namespace DM
 
diff --git a/experimental/skotty/Skotty.h b/experimental/skotty/Skotty.h
index 5cdffb8..c20feeb 100644
--- a/experimental/skotty/Skotty.h
+++ b/experimental/skotty/Skotty.h
@@ -38,9 +38,11 @@
 
     void animationTick(SkMSec);
 
-    const SkString& version() const { return fVersion; }
-    const SkSize& size() const { return fSize; }
-    SkScalar frameRate() const { return fFrameRate; }
+    const SkString& version() const { return fVersion;   }
+    const SkSize&      size() const { return fSize;      }
+         SkScalar frameRate() const { return fFrameRate; }
+         SkScalar   inPoint() const { return fInPoint;   }
+         SkScalar  outPoint() const { return fOutPoint;  }
 
     void setShowInval(bool show) { fShowInval = show; }
 
diff --git a/tools/flags/SkCommonFlags.cpp b/tools/flags/SkCommonFlags.cpp
index cd1e47f..dd3a855 100644
--- a/tools/flags/SkCommonFlags.cpp
+++ b/tools/flags/SkCommonFlags.cpp
@@ -52,6 +52,8 @@
 
 DEFINE_string(skps, "skps", "Directory to read skps from.");
 
+DEFINE_string(jsons, "", "Directory to read Bodymovin JSONs from, or a single JSON file.");
+
 DEFINE_string(svgs, "", "Directory to read SVGs from, or a single SVG file.");
 
 DEFINE_int32_2(threads, j, -1, "Run threadsafe tests on a threadpool with this many extra threads, "
diff --git a/tools/flags/SkCommonFlags.h b/tools/flags/SkCommonFlags.h
index 62d0477..a3eb001 100644
--- a/tools/flags/SkCommonFlags.h
+++ b/tools/flags/SkCommonFlags.h
@@ -25,6 +25,7 @@
 DECLARE_bool(releaseAndAbandonGpuContext);
 DECLARE_string(skps);
 DECLARE_bool(ddl);
+DECLARE_string(jsons);
 DECLARE_string(svgs);
 DECLARE_int32(threads);
 DECLARE_string(resourcePath);