[skotty,sksg] Initial gradient support

TBR=
Change-Id: I61e4d46ac14660f4c9ea757be2278e4098131a6b
Reviewed-on: https://skia-review.googlesource.com/94121
Reviewed-by: Florin Malita <fmalita@chromium.org>
Commit-Queue: Florin Malita <fmalita@chromium.org>
diff --git a/experimental/skotty/Skotty.cpp b/experimental/skotty/Skotty.cpp
index 284b377..ce2644e 100644
--- a/experimental/skotty/Skotty.cpp
+++ b/experimental/skotty/Skotty.cpp
@@ -21,6 +21,7 @@
 #include "SkPoint.h"
 #include "SkSGColor.h"
 #include "SkSGDraw.h"
+#include "SkSGGradient.h"
 #include "SkSGGroup.h"
 #include "SkSGImage.h"
 #include "SkSGInvalidationController.h"
@@ -293,13 +294,11 @@
     return path_node;
 }
 
-sk_sp<sksg::Color> AttachColorPaint(const Json::Value& obj, AttachContext* ctx) {
+sk_sp<sksg::Color> AttachColor(const Json::Value& obj, AttachContext* ctx) {
     SkASSERT(obj.isObject());
 
     auto color_node = sksg::Color::Make(SK_ColorBLACK);
-    color_node->setAntiAlias(true);
-
-    auto composite = sk_make_sp<CompositeColor>(color_node);
+    auto composite  = sk_make_sp<CompositeColor>(color_node);
     auto color_attached = BindProperty<VectorValue>(obj["c"], ctx, composite,
         [](CompositeColor* node, const VectorValue& c) {
             node->setColor(ValueTraits<VectorValue>::As<SkColor>(c));
@@ -312,29 +311,70 @@
     return (color_attached || opacity_attached) ? color_node : nullptr;
 }
 
-sk_sp<sksg::PaintNode> AttachFillPaint(const Json::Value& jfill, AttachContext* ctx) {
-    SkASSERT(jfill.isObject());
+sk_sp<sksg::Gradient> AttachGradient(const Json::Value& obj, AttachContext* ctx) {
+    SkASSERT(obj.isObject());
 
-    auto color = AttachColorPaint(jfill, ctx);
-    if (color) {
-        LOG("** Attached color fill: 0x%x\n", color->getColor());
-    }
-    return color;
-}
-
-sk_sp<sksg::PaintNode> AttachStrokePaint(const Json::Value& jstroke, AttachContext* ctx) {
-    SkASSERT(jstroke.isObject());
-
-    auto stroke_node = AttachColorPaint(jstroke, ctx);
-    if (!stroke_node)
+    const auto& stops = obj["g"];
+    if (!stops.isObject())
         return nullptr;
 
-    LOG("** Attached color stroke: 0x%x\n", stroke_node->getColor());
+    const auto stopCount = ParseInt(stops["p"], -1);
+    if (stopCount < 0)
+        return nullptr;
+
+    sk_sp<sksg::Gradient> gradient_node;
+    sk_sp<CompositeGradient> composite;
+
+    if (ParseInt(obj["t"], 1) == 1) {
+        auto linear_node = sksg::LinearGradient::Make();
+        composite = sk_make_sp<CompositeLinearGradient>(linear_node, stopCount);
+        gradient_node = std::move(linear_node);
+    } else {
+        auto radial_node = sksg::RadialGradient::Make();
+        composite = sk_make_sp<CompositeRadialGradient>(radial_node, stopCount);
+
+        // TODO: highlight, angle
+        gradient_node = std::move(radial_node);
+    }
+
+    BindProperty<VectorValue>(stops["k"], ctx, composite,
+        [](CompositeGradient* node, const VectorValue& stops) {
+            node->setColorStops(stops);
+        });
+    BindProperty<VectorValue>(obj["s"], ctx, composite,
+        [](CompositeGradient* node, const VectorValue& s) {
+            node->setStartPoint(ValueTraits<VectorValue>::As<SkPoint>(s));
+        });
+    BindProperty<VectorValue>(obj["e"], ctx, composite,
+        [](CompositeGradient* node, const VectorValue& e) {
+            node->setEndPoint(ValueTraits<VectorValue>::As<SkPoint>(e));
+        });
+
+    return gradient_node;
+}
+
+sk_sp<sksg::PaintNode> AttachPaint(const Json::Value& jfill, AttachContext* ctx,
+                                   sk_sp<sksg::PaintNode> paint_node) {
+    if (paint_node) {
+        paint_node->setAntiAlias(true);
+
+        // TODO: refactor opacity
+    }
+
+    return paint_node;
+}
+
+sk_sp<sksg::PaintNode> AttachStroke(const Json::Value& jstroke, AttachContext* ctx,
+                                    sk_sp<sksg::PaintNode> stroke_node) {
+    SkASSERT(jstroke.isObject());
+
+    if (!stroke_node)
+        return nullptr;
 
     stroke_node->setStyle(SkPaint::kStroke_Style);
 
     auto width_attached = BindProperty<ScalarValue>(jstroke["w"], ctx, stroke_node,
-        [](sksg::Color* node, const ScalarValue& w) {
+        [](sksg::PaintNode* node, const ScalarValue& w) {
             node->setStrokeWidth(w);
         });
     if (!width_attached)
@@ -361,6 +401,30 @@
     return stroke_node;
 }
 
+sk_sp<sksg::PaintNode> AttachColorFill(const Json::Value& jfill, AttachContext* ctx) {
+    SkASSERT(jfill.isObject());
+
+    return AttachPaint(jfill, ctx, AttachColor(jfill, ctx));
+}
+
+sk_sp<sksg::PaintNode> AttachGradientFill(const Json::Value& jfill, AttachContext* ctx) {
+    SkASSERT(jfill.isObject());
+
+    return AttachPaint(jfill, ctx, AttachGradient(jfill, ctx));
+}
+
+sk_sp<sksg::PaintNode> AttachColorStroke(const Json::Value& jstroke, AttachContext* ctx) {
+    SkASSERT(jstroke.isObject());
+
+    return AttachStroke(jstroke, ctx, AttachPaint(jstroke, ctx, AttachColor(jstroke, ctx)));
+}
+
+sk_sp<sksg::PaintNode> AttachGradientStroke(const Json::Value& jstroke, AttachContext* ctx) {
+    SkASSERT(jstroke.isObject());
+
+    return AttachStroke(jstroke, ctx, AttachPaint(jstroke, ctx, AttachGradient(jstroke, ctx)));
+}
+
 std::vector<sk_sp<sksg::GeometryNode>> AttachMergeGeometryEffect(
     const Json::Value& jmerge, AttachContext* ctx, std::vector<sk_sp<sksg::GeometryNode>>&& geos) {
     std::vector<sk_sp<sksg::GeometryNode>> merged;
@@ -433,8 +497,10 @@
 
 using PaintAttacherT = sk_sp<sksg::PaintNode> (*)(const Json::Value&, AttachContext*);
 static constexpr PaintAttacherT gPaintAttachers[] = {
-    AttachFillPaint,
-    AttachStrokePaint,
+    AttachColorFill,
+    AttachColorStroke,
+    AttachGradientFill,
+    AttachGradientStroke,
 };
 
 using GroupAttacherT = sk_sp<sksg::RenderNode> (*)(const Json::Value&, AttachContext*);
@@ -468,13 +534,15 @@
 const ShapeInfo* FindShapeInfo(const Json::Value& shape) {
     static constexpr ShapeInfo gShapeInfo[] = {
         { "el", ShapeType::kGeometry      , 2 }, // ellipse   -> AttachEllipseGeometry
-        { "fl", ShapeType::kPaint         , 0 }, // fill      -> AttachFillPaint
+        { "fl", ShapeType::kPaint         , 0 }, // fill      -> AttachColorFill
+        { "gf", ShapeType::kPaint         , 2 }, // gfill     -> AttachGradientFill
         { "gr", ShapeType::kGroup         , 0 }, // group     -> AttachShapeGroup
+        { "gs", ShapeType::kPaint         , 3 }, // gstroke   -> AttachGradientStroke
         { "mm", ShapeType::kGeometryEffect, 0 }, // merge     -> AttachMergeGeometryEffect
         { "rc", ShapeType::kGeometry      , 1 }, // rrect     -> AttachRRectGeometry
         { "sh", ShapeType::kGeometry      , 0 }, // shape     -> AttachPathGeometry
         { "sr", ShapeType::kGeometry      , 3 }, // polystar  -> AttachPolyStarGeometry
-        { "st", ShapeType::kPaint         , 1 }, // stroke    -> AttachStrokePaint
+        { "st", ShapeType::kPaint         , 1 }, // stroke    -> AttachColorStroke
         { "tm", ShapeType::kGeometryEffect, 1 }, // trim      -> AttachTrimGeometryEffect
         { "tr", ShapeType::kTransform     , 0 }, // transform -> In-place handler
     };