[sksg] Hit-testing API
Introduce RenderNode::nodeAt(const SkPoint&) as the entry point for the hit-testing API.
This is backed by a onNodeAt() virtual, which gets dispatched throughout the render DAG,
and normally stops at the first leaf Draw node in encounters.
To support the implementation, introduce a GeometryNode::contains(const SkPoint&) API.
This is backed by a onContains() virtual, overridden in each concrete geometry class.
Expose nodeAt() on sksg::Scene, and add some basic unit tests.
Change-Id: I0c8abd9d1e51ecf2d8b4dd699f325cd636e21084
Reviewed-on: https://skia-review.googlesource.com/c/191296
Commit-Queue: Florin Malita <fmalita@chromium.org>
Reviewed-by: Mike Reed <reed@google.com>
diff --git a/modules/skottie/src/SkottieLayer.cpp b/modules/skottie/src/SkottieLayer.cpp
index 3cc4122..24d64c8 100644
--- a/modules/skottie/src/SkottieLayer.cpp
+++ b/modules/skottie/src/SkottieLayer.cpp
@@ -187,6 +187,8 @@
return SkRect::MakeSize(fAnimation->size());
}
+ const RenderNode* onNodeAt(const SkPoint&) const override { return nullptr; }
+
void onRender(SkCanvas* canvas, const RenderContext* ctx) const override {
const auto local_scope =
ScopedRenderContext(canvas, ctx).setIsolation(this->bounds(), true);
diff --git a/modules/sksg/include/SkSGClipEffect.h b/modules/sksg/include/SkSGClipEffect.h
index 52ba7c7..309127f 100644
--- a/modules/sksg/include/SkSGClipEffect.h
+++ b/modules/sksg/include/SkSGClipEffect.h
@@ -33,6 +33,7 @@
ClipEffect(sk_sp<RenderNode>, sk_sp<GeometryNode>, bool aa);
void onRender(SkCanvas*, const RenderContext*) const override;
+ const RenderNode* onNodeAt(const SkPoint&) const override;
SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;
diff --git a/modules/sksg/include/SkSGColorFilter.h b/modules/sksg/include/SkSGColorFilter.h
index a9e12e7..db1a800 100644
--- a/modules/sksg/include/SkSGColorFilter.h
+++ b/modules/sksg/include/SkSGColorFilter.h
@@ -27,6 +27,7 @@
explicit ColorFilter(sk_sp<RenderNode>);
void onRender(SkCanvas*, const RenderContext*) const final;
+ const RenderNode* onNodeAt(const SkPoint&) const final;
sk_sp<SkColorFilter> fColorFilter;
diff --git a/modules/sksg/include/SkSGDraw.h b/modules/sksg/include/SkSGDraw.h
index 018dd1a..570ba72 100644
--- a/modules/sksg/include/SkSGDraw.h
+++ b/modules/sksg/include/SkSGDraw.h
@@ -33,6 +33,7 @@
~Draw() override;
void onRender(SkCanvas*, const RenderContext*) const override;
+ const RenderNode* onNodeAt(const SkPoint&) const override;
SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;
diff --git a/modules/sksg/include/SkSGEffectNode.h b/modules/sksg/include/SkSGEffectNode.h
index 9f0d940..1fe9468 100644
--- a/modules/sksg/include/SkSGEffectNode.h
+++ b/modules/sksg/include/SkSGEffectNode.h
@@ -24,6 +24,7 @@
~EffectNode() override;
void onRender(SkCanvas*, const RenderContext*) const override;
+ const RenderNode* onNodeAt(const SkPoint&) const override;
SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;
diff --git a/modules/sksg/include/SkSGGeometryNode.h b/modules/sksg/include/SkSGGeometryNode.h
index 7ce3aa9..66be09d 100644
--- a/modules/sksg/include/SkSGGeometryNode.h
+++ b/modules/sksg/include/SkSGGeometryNode.h
@@ -27,6 +27,8 @@
void clip(SkCanvas*, bool antiAlias) const;
void draw(SkCanvas*, const SkPaint&) const;
+ bool contains(const SkPoint&) const;
+
SkPath asPath() const;
protected:
@@ -36,6 +38,8 @@
virtual void onDraw(SkCanvas*, const SkPaint&) const = 0;
+ virtual bool onContains(const SkPoint&) const = 0;
+
virtual SkPath onAsPath() const = 0;
private:
diff --git a/modules/sksg/include/SkSGGeometryTransform.h b/modules/sksg/include/SkSGGeometryTransform.h
index bf297eb..f9907ed 100644
--- a/modules/sksg/include/SkSGGeometryTransform.h
+++ b/modules/sksg/include/SkSGGeometryTransform.h
@@ -37,6 +37,7 @@
protected:
void onClip(SkCanvas*, bool antiAlias) const override;
void onDraw(SkCanvas*, const SkPaint&) const override;
+ bool onContains(const SkPoint&) const override;
SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;
SkPath onAsPath() const override;
diff --git a/modules/sksg/include/SkSGGroup.h b/modules/sksg/include/SkSGGroup.h
index bb53555..ccb9cf8 100644
--- a/modules/sksg/include/SkSGGroup.h
+++ b/modules/sksg/include/SkSGGroup.h
@@ -39,6 +39,8 @@
~Group() override;
void onRender(SkCanvas*, const RenderContext*) const override;
+ const RenderNode* onNodeAt(const SkPoint&) const override;
+
SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;
private:
diff --git a/modules/sksg/include/SkSGImage.h b/modules/sksg/include/SkSGImage.h
index b6aef47..f3e3151 100644
--- a/modules/sksg/include/SkSGImage.h
+++ b/modules/sksg/include/SkSGImage.h
@@ -34,6 +34,7 @@
explicit Image(sk_sp<SkImage>);
void onRender(SkCanvas*, const RenderContext*) const override;
+ const RenderNode* onNodeAt(const SkPoint&) const override;
SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;
diff --git a/modules/sksg/include/SkSGMaskEffect.h b/modules/sksg/include/SkSGMaskEffect.h
index f668777..9837d0e 100644
--- a/modules/sksg/include/SkSGMaskEffect.h
+++ b/modules/sksg/include/SkSGMaskEffect.h
@@ -36,6 +36,7 @@
MaskEffect(sk_sp<RenderNode>, sk_sp<RenderNode> mask, Mode);
void onRender(SkCanvas*, const RenderContext*) const override;
+ const RenderNode* onNodeAt(const SkPoint&) const override;
SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;
diff --git a/modules/sksg/include/SkSGMerge.h b/modules/sksg/include/SkSGMerge.h
index c4957f4..b37530a 100644
--- a/modules/sksg/include/SkSGMerge.h
+++ b/modules/sksg/include/SkSGMerge.h
@@ -50,6 +50,7 @@
protected:
void onClip(SkCanvas*, bool antiAlias) const override;
void onDraw(SkCanvas*, const SkPaint&) const override;
+ bool onContains(const SkPoint&) const override;
SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;
SkPath onAsPath() const override;
diff --git a/modules/sksg/include/SkSGOpacityEffect.h b/modules/sksg/include/SkSGOpacityEffect.h
index a982304..8e1105c 100644
--- a/modules/sksg/include/SkSGOpacityEffect.h
+++ b/modules/sksg/include/SkSGOpacityEffect.h
@@ -28,6 +28,7 @@
OpacityEffect(sk_sp<RenderNode>, float);
void onRender(SkCanvas*, const RenderContext*) const override;
+ const RenderNode* onNodeAt(const SkPoint&) const override;
SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;
diff --git a/modules/sksg/include/SkSGPath.h b/modules/sksg/include/SkSGPath.h
index 1a87188..55d16fa 100644
--- a/modules/sksg/include/SkSGPath.h
+++ b/modules/sksg/include/SkSGPath.h
@@ -31,6 +31,7 @@
protected:
void onClip(SkCanvas*, bool antiAlias) const override;
void onDraw(SkCanvas*, const SkPaint&) const override;
+ bool onContains(const SkPoint&) const override;
SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;
SkPath onAsPath() const override;
diff --git a/modules/sksg/include/SkSGPlane.h b/modules/sksg/include/SkSGPlane.h
index c0a2637..f8338a1 100644
--- a/modules/sksg/include/SkSGPlane.h
+++ b/modules/sksg/include/SkSGPlane.h
@@ -25,6 +25,7 @@
protected:
void onClip(SkCanvas*, bool antiAlias) const override;
void onDraw(SkCanvas*, const SkPaint&) const override;
+ bool onContains(const SkPoint&) const override;
SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;
SkPath onAsPath() const override;
diff --git a/modules/sksg/include/SkSGRect.h b/modules/sksg/include/SkSGRect.h
index 72133cf..a4666b4 100644
--- a/modules/sksg/include/SkSGRect.h
+++ b/modules/sksg/include/SkSGRect.h
@@ -38,6 +38,7 @@
protected:
void onClip(SkCanvas*, bool antiAlias) const override;
void onDraw(SkCanvas*, const SkPaint&) const override;
+ bool onContains(const SkPoint&) const override;
SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;
SkPath onAsPath() const override;
@@ -80,6 +81,7 @@
protected:
void onClip(SkCanvas*, bool antiAlias) const override;
void onDraw(SkCanvas*, const SkPaint&) const override;
+ bool onContains(const SkPoint&) const override;
SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;
SkPath onAsPath() const override;
diff --git a/modules/sksg/include/SkSGRenderEffect.h b/modules/sksg/include/SkSGRenderEffect.h
index 29b4d1c..08eb87d 100644
--- a/modules/sksg/include/SkSGRenderEffect.h
+++ b/modules/sksg/include/SkSGRenderEffect.h
@@ -64,6 +64,7 @@
protected:
void onRender(SkCanvas*, const RenderContext*) const override;
+ const RenderNode* onNodeAt(const SkPoint&) const override;
SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;
diff --git a/modules/sksg/include/SkSGRenderNode.h b/modules/sksg/include/SkSGRenderNode.h
index 13d86fb..6e44b5b 100644
--- a/modules/sksg/include/SkSGRenderNode.h
+++ b/modules/sksg/include/SkSGRenderNode.h
@@ -29,10 +29,15 @@
// Render the node and its descendants to the canvas.
void render(SkCanvas*, const RenderContext* = nullptr) const;
+ // Perform a front-to-back hit-test, and return the RenderNode located at |point|.
+ // Normally, hit-testing stops at leaf Draw nodes.
+ const RenderNode* nodeAt(const SkPoint& point) const;
+
protected:
explicit RenderNode(uint32_t inval_traits = 0);
virtual void onRender(SkCanvas*, const RenderContext*) const = 0;
+ virtual const RenderNode* onNodeAt(const SkPoint& p) const = 0;
// Paint property overrides.
// These are deferred until we can determine whether they can be applied to the individual
diff --git a/modules/sksg/include/SkSGRoundEffect.h b/modules/sksg/include/SkSGRoundEffect.h
index 67124ca..4787a8c 100644
--- a/modules/sksg/include/SkSGRoundEffect.h
+++ b/modules/sksg/include/SkSGRoundEffect.h
@@ -30,6 +30,7 @@
protected:
void onClip(SkCanvas*, bool antiAlias) const override;
void onDraw(SkCanvas*, const SkPaint&) const override;
+ bool onContains(const SkPoint&) const override;
SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;
SkPath onAsPath() const override;
diff --git a/modules/sksg/include/SkSGScene.h b/modules/sksg/include/SkSGScene.h
index 95bee30..9261cf4 100644
--- a/modules/sksg/include/SkSGScene.h
+++ b/modules/sksg/include/SkSGScene.h
@@ -15,6 +15,7 @@
#include <vector>
class SkCanvas;
+struct SkPoint;
namespace sksg {
@@ -67,6 +68,7 @@
void render(SkCanvas*) const;
void animate(float t);
+ const RenderNode* nodeAt(const SkPoint&) const;
void setShowInval(bool show) { fShowInval = show; }
diff --git a/modules/sksg/include/SkSGText.h b/modules/sksg/include/SkSGText.h
index 64ec1b2..e2cf788 100644
--- a/modules/sksg/include/SkSGText.h
+++ b/modules/sksg/include/SkSGText.h
@@ -45,6 +45,7 @@
protected:
void onClip(SkCanvas*, bool antiAlias) const override;
void onDraw(SkCanvas*, const SkPaint&) const override;
+ bool onContains(const SkPoint&) const override;
SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;
SkPath onAsPath() const override;
@@ -83,6 +84,7 @@
protected:
void onClip(SkCanvas*, bool antiAlias) const override;
void onDraw(SkCanvas*, const SkPaint&) const override;
+ bool onContains(const SkPoint&) const override;
SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;
SkPath onAsPath() const override;
diff --git a/modules/sksg/include/SkSGTransform.h b/modules/sksg/include/SkSGTransform.h
index 3153a7e..d8408da 100644
--- a/modules/sksg/include/SkSGTransform.h
+++ b/modules/sksg/include/SkSGTransform.h
@@ -97,6 +97,7 @@
protected:
void onRender(SkCanvas*, const RenderContext*) const override;
+ const RenderNode* onNodeAt(const SkPoint&) const override;
SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;
diff --git a/modules/sksg/include/SkSGTrimEffect.h b/modules/sksg/include/SkSGTrimEffect.h
index 18f1592..ea2fd253 100644
--- a/modules/sksg/include/SkSGTrimEffect.h
+++ b/modules/sksg/include/SkSGTrimEffect.h
@@ -36,6 +36,7 @@
protected:
void onClip(SkCanvas*, bool antiAlias) const override;
void onDraw(SkCanvas*, const SkPaint&) const override;
+ bool onContains(const SkPoint&) const override;
SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;
SkPath onAsPath() const override;
diff --git a/modules/sksg/src/SkSGClipEffect.cpp b/modules/sksg/src/SkSGClipEffect.cpp
index 045605d..11a9be6 100644
--- a/modules/sksg/src/SkSGClipEffect.cpp
+++ b/modules/sksg/src/SkSGClipEffect.cpp
@@ -36,6 +36,10 @@
this->INHERITED::onRender(canvas, ctx);
}
+const RenderNode* ClipEffect::onNodeAt(const SkPoint& p) const {
+ return fClipNode->contains(p) ? this->INHERITED::onNodeAt(p) : nullptr;
+}
+
SkRect ClipEffect::onRevalidate(InvalidationController* ic, const SkMatrix& ctm) {
SkASSERT(this->hasInval());
diff --git a/modules/sksg/src/SkSGColorFilter.cpp b/modules/sksg/src/SkSGColorFilter.cpp
index 3ad2287..ae4a01a 100644
--- a/modules/sksg/src/SkSGColorFilter.cpp
+++ b/modules/sksg/src/SkSGColorFilter.cpp
@@ -24,6 +24,11 @@
this->INHERITED::onRender(canvas, local_ctx);
}
+const RenderNode* ColorFilter::onNodeAt(const SkPoint& p) const {
+ // TODO: we likely need to do something more sophisticated than delegate to descendants here.
+ return this->INHERITED::onNodeAt(p);
+}
+
ColorModeFilter::ColorModeFilter(sk_sp<RenderNode> child, sk_sp<Color> color, SkBlendMode mode)
: INHERITED(std::move(child))
, fColor(std::move(color))
diff --git a/modules/sksg/src/SkSGDraw.cpp b/modules/sksg/src/SkSGDraw.cpp
index 24b358e..10def5b 100644
--- a/modules/sksg/src/SkSGDraw.cpp
+++ b/modules/sksg/src/SkSGDraw.cpp
@@ -7,6 +7,7 @@
#include "SkSGDraw.h"
+#include "SkPath.h"
#include "SkSGGeometryNode.h"
#include "SkSGInvalidationController.h"
#include "SkSGPaintNode.h"
@@ -40,6 +41,25 @@
}
}
+const RenderNode* Draw::onNodeAt(const SkPoint& p) const {
+ const auto paint = fPaint->makePaint();
+
+ if (!paint.getAlpha()) {
+ return nullptr;
+ }
+
+ if (paint.getStyle() == SkPaint::Style::kFill_Style && fGeometry->contains(p)) {
+ return this;
+ }
+
+ SkPath stroke_path;
+ if (!paint.getFillPath(fGeometry->asPath(), &stroke_path)) {
+ return nullptr;
+ }
+
+ return stroke_path.contains(p.x(), p.y()) ? this : nullptr;
+}
+
SkRect Draw::onRevalidate(InvalidationController* ic, const SkMatrix& ctm) {
SkASSERT(this->hasInval());
diff --git a/modules/sksg/src/SkSGEffectNode.cpp b/modules/sksg/src/SkSGEffectNode.cpp
index d3ce7a6..8ea2eea 100644
--- a/modules/sksg/src/SkSGEffectNode.cpp
+++ b/modules/sksg/src/SkSGEffectNode.cpp
@@ -23,6 +23,10 @@
fChild->render(canvas, ctx);
}
+const RenderNode* EffectNode::onNodeAt(const SkPoint& p) const {
+ return fChild->nodeAt(p);
+}
+
SkRect EffectNode::onRevalidate(InvalidationController* ic, const SkMatrix& ctm) {
SkASSERT(this->hasInval());
diff --git a/modules/sksg/src/SkSGGeometryNode.cpp b/modules/sksg/src/SkSGGeometryNode.cpp
index 6b78c48..78ef6e6 100644
--- a/modules/sksg/src/SkSGGeometryNode.cpp
+++ b/modules/sksg/src/SkSGGeometryNode.cpp
@@ -24,6 +24,11 @@
this->onDraw(canvas, paint);
}
+bool GeometryNode::contains(const SkPoint& p) const {
+ SkASSERT(!this->hasInval());
+ return this->bounds().contains(p.x(), p.y()) ? this->onContains(p) : false;
+}
+
SkPath GeometryNode::asPath() const {
SkASSERT(!this->hasInval());
return this->onAsPath();
diff --git a/modules/sksg/src/SkSGGeometryTransform.cpp b/modules/sksg/src/SkSGGeometryTransform.cpp
index 6e71dc6..224d46b 100644
--- a/modules/sksg/src/SkSGGeometryTransform.cpp
+++ b/modules/sksg/src/SkSGGeometryTransform.cpp
@@ -33,6 +33,10 @@
canvas->drawPath(fTransformedPath, paint);
}
+bool GeometryTransform::onContains(const SkPoint& p) const {
+ return fTransformedPath.contains(p.x(), p.y());
+}
+
SkRect GeometryTransform::onRevalidate(InvalidationController* ic, const SkMatrix& ctm) {
SkASSERT(this->hasInval());
diff --git a/modules/sksg/src/SkSGGroup.cpp b/modules/sksg/src/SkSGGroup.cpp
index fff7436..cc44b31 100644
--- a/modules/sksg/src/SkSGGroup.cpp
+++ b/modules/sksg/src/SkSGGroup.cpp
@@ -66,6 +66,16 @@
}
}
+const RenderNode* Group::onNodeAt(const SkPoint& p) const {
+ for (const auto& child : fChildren) {
+ if (const auto* node = child->nodeAt(p)) {
+ return node;
+ }
+ }
+
+ return nullptr;
+}
+
SkRect Group::onRevalidate(InvalidationController* ic, const SkMatrix& ctm) {
SkASSERT(this->hasInval());
diff --git a/modules/sksg/src/SkSGImage.cpp b/modules/sksg/src/SkSGImage.cpp
index 59da5d7..64bfd33 100644
--- a/modules/sksg/src/SkSGImage.cpp
+++ b/modules/sksg/src/SkSGImage.cpp
@@ -30,6 +30,11 @@
canvas->drawImage(fImage, 0, 0, &paint);
}
+const RenderNode* Image::onNodeAt(const SkPoint& p) const {
+ SkASSERT(this->bounds().contains(p.x(), p.y()));
+ return this;
+}
+
SkRect Image::onRevalidate(InvalidationController*, const SkMatrix& ctm) {
return fImage ? SkRect::Make(fImage->bounds()) : SkRect::MakeEmpty();
}
diff --git a/modules/sksg/src/SkSGMaskEffect.cpp b/modules/sksg/src/SkSGMaskEffect.cpp
index ff869fd..2086d3f 100644
--- a/modules/sksg/src/SkSGMaskEffect.cpp
+++ b/modules/sksg/src/SkSGMaskEffect.cpp
@@ -32,7 +32,6 @@
// Note: the paint overrides in ctx don't apply to the mask.
fMaskNode->render(canvas);
-
SkPaint p;
p.setBlendMode(fMaskMode == Mode::kNormal ? SkBlendMode::kSrcIn : SkBlendMode::kSrcOut);
canvas->saveLayer(this->bounds(), &p);
@@ -40,6 +39,11 @@
this->INHERITED::onRender(canvas, ctx);
}
+const RenderNode* MaskEffect::onNodeAt(const SkPoint& p) const {
+ const auto mask_hit = (!!fMaskNode->nodeAt(p) == (fMaskMode == Mode::kNormal));
+
+ return mask_hit ? this->INHERITED::onNodeAt(p) : nullptr;
+}
SkRect MaskEffect::onRevalidate(InvalidationController* ic, const SkMatrix& ctm) {
SkASSERT(this->hasInval());
diff --git a/modules/sksg/src/SkSGMerge.cpp b/modules/sksg/src/SkSGMerge.cpp
index 48e7b9b..a882647 100644
--- a/modules/sksg/src/SkSGMerge.cpp
+++ b/modules/sksg/src/SkSGMerge.cpp
@@ -33,6 +33,10 @@
canvas->drawPath(fMerged, paint);
}
+bool Merge::onContains(const SkPoint& p) const {
+ return fMerged.contains(p.x(), p.y());
+}
+
SkPath Merge::onAsPath() const {
return fMerged;
}
diff --git a/modules/sksg/src/SkSGOpacityEffect.cpp b/modules/sksg/src/SkSGOpacityEffect.cpp
index cc72c14..d6abfa2 100644
--- a/modules/sksg/src/SkSGOpacityEffect.cpp
+++ b/modules/sksg/src/SkSGOpacityEffect.cpp
@@ -23,6 +23,10 @@
this->INHERITED::onRender(canvas, local_context);
}
+const RenderNode* OpacityEffect::onNodeAt(const SkPoint& p) const {
+ return (fOpacity > 0) ? this->INHERITED::onNodeAt(p) : nullptr;
+}
+
SkRect OpacityEffect::onRevalidate(InvalidationController* ic, const SkMatrix& ctm) {
SkASSERT(this->hasInval());
diff --git a/modules/sksg/src/SkSGPath.cpp b/modules/sksg/src/SkSGPath.cpp
index 230442d..3b63182 100644
--- a/modules/sksg/src/SkSGPath.cpp
+++ b/modules/sksg/src/SkSGPath.cpp
@@ -23,6 +23,10 @@
canvas->drawPath(fPath, paint);
}
+bool Path::onContains(const SkPoint& p) const {
+ return fPath.contains(p.x(), p.y());
+}
+
SkRect Path::onRevalidate(InvalidationController*, const SkMatrix&) {
SkASSERT(this->hasInval());
diff --git a/modules/sksg/src/SkSGPlane.cpp b/modules/sksg/src/SkSGPlane.cpp
index 806fcc7..989f40c 100644
--- a/modules/sksg/src/SkSGPlane.cpp
+++ b/modules/sksg/src/SkSGPlane.cpp
@@ -20,6 +20,8 @@
canvas->drawPaint(paint);
}
+bool Plane::onContains(const SkPoint&) const { return true; }
+
SkRect Plane::onRevalidate(InvalidationController*, const SkMatrix&) {
SkASSERT(this->hasInval());
diff --git a/modules/sksg/src/SkSGRect.cpp b/modules/sksg/src/SkSGRect.cpp
index 64739d9..091751b 100644
--- a/modules/sksg/src/SkSGRect.cpp
+++ b/modules/sksg/src/SkSGRect.cpp
@@ -23,6 +23,10 @@
canvas->drawRect(fRect, paint);
}
+bool Rect::onContains(const SkPoint& p) const {
+ return fRect.contains(p.x(), p.y());
+}
+
SkRect Rect::onRevalidate(InvalidationController*, const SkMatrix&) {
SkASSERT(this->hasInval());
@@ -45,6 +49,22 @@
canvas->drawRRect(fRRect, paint);
}
+bool RRect::onContains(const SkPoint& p) const {
+ if (!fRRect.rect().contains(p.x(), p.y())) {
+ return false;
+ }
+
+ if (fRRect.isRect()) {
+ return true;
+ }
+
+ // TODO: no SkRRect::contains(x, y)
+ return fRRect.contains(SkRect::MakeLTRB(p.x() - SK_ScalarNearlyZero,
+ p.y() - SK_ScalarNearlyZero,
+ p.x() + SK_ScalarNearlyZero,
+ p.y() + SK_ScalarNearlyZero));
+}
+
SkRect RRect::onRevalidate(InvalidationController*, const SkMatrix&) {
SkASSERT(this->hasInval());
diff --git a/modules/sksg/src/SkSGRenderEffect.cpp b/modules/sksg/src/SkSGRenderEffect.cpp
index fdde23a..dcfdbe4 100644
--- a/modules/sksg/src/SkSGRenderEffect.cpp
+++ b/modules/sksg/src/SkSGRenderEffect.cpp
@@ -39,6 +39,13 @@
return filter->computeFastBounds(this->INHERITED::onRevalidate(ic, ctm));
}
+const RenderNode* ImageFilterEffect::onNodeAt(const SkPoint& p) const {
+ // TODO: map p through the filter DAG and dispatch to descendants?
+ // For now, image filters occlude hit-testing.
+ SkASSERT(this->bounds().contains(p.x(), p.y()));
+ return this;
+}
+
void ImageFilterEffect::onRender(SkCanvas* canvas, const RenderContext* ctx) const {
// TODO: hoist these checks to RenderNode?
if (this->bounds().isEmpty())
diff --git a/modules/sksg/src/SkSGRenderNode.cpp b/modules/sksg/src/SkSGRenderNode.cpp
index e62ce00..fab3105 100644
--- a/modules/sksg/src/SkSGRenderNode.cpp
+++ b/modules/sksg/src/SkSGRenderNode.cpp
@@ -20,6 +20,10 @@
this->onRender(canvas, ctx);
}
+const RenderNode* RenderNode::nodeAt(const SkPoint& p) const {
+ return this->bounds().contains(p.x(), p.y()) ? this->onNodeAt(p) : nullptr;
+}
+
bool RenderNode::RenderContext::modulatePaint(SkPaint* paint) const {
const auto initial_alpha = paint->getAlpha(),
alpha = SkToU8(sk_float_round2int(initial_alpha * fOpacity));
diff --git a/modules/sksg/src/SkSGRoundEffect.cpp b/modules/sksg/src/SkSGRoundEffect.cpp
index e47c361..14b8e1d 100644
--- a/modules/sksg/src/SkSGRoundEffect.cpp
+++ b/modules/sksg/src/SkSGRoundEffect.cpp
@@ -32,6 +32,10 @@
canvas->drawPath(fRoundedPath, paint);
}
+bool RoundEffect::onContains(const SkPoint& p) const {
+ return fRoundedPath.contains(p.x(), p.y());
+}
+
SkPath RoundEffect::onAsPath() const {
return fRoundedPath;
}
diff --git a/modules/sksg/src/SkSGScene.cpp b/modules/sksg/src/SkSGScene.cpp
index e916540..7d27bb2 100644
--- a/modules/sksg/src/SkSGScene.cpp
+++ b/modules/sksg/src/SkSGScene.cpp
@@ -69,4 +69,8 @@
}
}
+const RenderNode* Scene::nodeAt(const SkPoint& p) const {
+ return fRoot->nodeAt(p);
+}
+
} // namespace sksg
diff --git a/modules/sksg/src/SkSGText.cpp b/modules/sksg/src/SkSGText.cpp
index b70b192..9aa7211 100644
--- a/modules/sksg/src/SkSGText.cpp
+++ b/modules/sksg/src/SkSGText.cpp
@@ -73,6 +73,10 @@
canvas->drawTextBlob(fBlob, aligned_pos.x(), aligned_pos.y(), paint);
}
+bool Text::onContains(const SkPoint& p) const {
+ return this->asPath().contains(p.x(), p.y());
+}
+
SkPath Text::onAsPath() const {
// TODO
return SkPath();
@@ -99,6 +103,10 @@
canvas->drawTextBlob(fBlob, fPosition.x(), fPosition.y(), paint);
}
+bool TextBlob::onContains(const SkPoint& p) const {
+ return this->asPath().contains(p.x(), p.y());
+}
+
SkPath TextBlob::onAsPath() const {
// TODO
return SkPath();
diff --git a/modules/sksg/src/SkSGTransform.cpp b/modules/sksg/src/SkSGTransform.cpp
index 6834fc9..fff1ba4 100644
--- a/modules/sksg/src/SkSGTransform.cpp
+++ b/modules/sksg/src/SkSGTransform.cpp
@@ -96,6 +96,15 @@
this->INHERITED::onRender(canvas, ctx);
}
+const RenderNode* TransformEffect::onNodeAt(const SkPoint& p) const {
+ const auto m = TransformPriv::As<SkMatrix>(fTransform);
+
+ SkPoint mapped_p;
+ m.mapPoints(&mapped_p, &p, 1);
+
+ return this->INHERITED::onNodeAt(mapped_p);
+}
+
SkRect TransformEffect::onRevalidate(InvalidationController* ic, const SkMatrix& ctm) {
SkASSERT(this->hasInval());
diff --git a/modules/sksg/src/SkSGTrimEffect.cpp b/modules/sksg/src/SkSGTrimEffect.cpp
index a2ec3c6..53f8f28 100644
--- a/modules/sksg/src/SkSGTrimEffect.cpp
+++ b/modules/sksg/src/SkSGTrimEffect.cpp
@@ -32,6 +32,10 @@
canvas->drawPath(fTrimmedPath, paint);
}
+bool TrimEffect::onContains(const SkPoint& p) const {
+ return fTrimmedPath.contains(p.x(), p.y());
+}
+
SkPath TrimEffect::onAsPath() const {
return fTrimmedPath;
}
diff --git a/modules/sksg/tests/SGTest.cpp b/modules/sksg/tests/SGTest.cpp
index 873cd9a..d3509ce 100644
--- a/modules/sksg/tests/SGTest.cpp
+++ b/modules/sksg/tests/SGTest.cpp
@@ -56,6 +56,23 @@
}
}
+struct HitTest {
+ const SkPoint pt;
+ sk_sp<sksg::RenderNode> node;
+};
+
+static void check_hittest(skiatest::Reporter* reporter, const sk_sp<sksg::RenderNode>& root,
+ const std::vector<HitTest>& tests) {
+ for (const auto& tst : tests) {
+ const auto* node = root->nodeAt(tst.pt);
+ if (node != tst.node.get()) {
+ SkDebugf("*** nodeAt(%f, %f) - expected %p, got %p\n",
+ tst.pt.x(), tst.pt.y(), tst.node.get(), node);
+ }
+ REPORTER_ASSERT(reporter, tst.node.get() == node);
+ }
+}
+
static void inval_test1(skiatest::Reporter* reporter) {
auto color = sksg::Color::Make(0xff000000);
auto r1 = sksg::Rect::Make(SkRect::MakeWH(100, 100)),
@@ -63,9 +80,11 @@
auto grp = sksg::Group::Make();
auto matrix = sksg::Matrix<SkMatrix>::Make(SkMatrix::I());
auto root = sksg::TransformEffect::Make(grp, matrix);
+ auto d1 = sksg::Draw::Make(r1, color),
+ d2 = sksg::Draw::Make(r2, color);
- grp->addChild(sksg::Draw::Make(r1, color));
- grp->addChild(sksg::Draw::Make(r2, color));
+ grp->addChild(d1);
+ grp->addChild(d2);
{
// Initial revalidation.
@@ -73,6 +92,15 @@
SkRect::MakeWH(100, 100),
SkRectPriv::MakeLargeS32(),
nullptr);
+
+ check_hittest(reporter, root, {
+ {{ -1, 0 }, nullptr },
+ {{ 0, -1 }, nullptr },
+ {{ 100, 0 }, nullptr },
+ {{ 0, 100 }, nullptr },
+ {{ 0, 0 }, d1 },
+ {{ 99, 99 }, d1 },
+ });
}
{
@@ -83,6 +111,22 @@
SkRect::MakeWH(300, 200),
SkRect::MakeWH(300, 200),
&damage);
+
+ check_hittest(reporter, root, {
+ {{ -1, 0 }, nullptr },
+ {{ 0, -1 }, nullptr },
+ {{ 100, 0 }, nullptr },
+ {{ 0, 100 }, nullptr },
+ {{ 0, 0 }, d1 },
+ {{ 99, 99 }, d1 },
+
+ {{ 199, 100 }, nullptr },
+ {{ 200, 99 }, nullptr },
+ {{ 300, 100 }, nullptr },
+ {{ 200, 200 }, nullptr },
+ {{ 200, 100 }, d2 },
+ {{ 299, 199 }, d2 },
+ });
}
{
@@ -103,6 +147,22 @@
SkRect::MakeWH(300, 200),
SkRect::MakeWH(100, 100),
&damage);
+
+ check_hittest(reporter, root, {
+ {{ -1, 0 }, nullptr },
+ {{ 0, -1 }, nullptr },
+ {{ 50, 0 }, nullptr },
+ {{ 0, 100 }, nullptr },
+ {{ 0, 0 }, d1 },
+ {{ 49, 99 }, d1 },
+
+ {{ 199, 100 }, nullptr },
+ {{ 200, 99 }, nullptr },
+ {{ 300, 100 }, nullptr },
+ {{ 200, 200 }, nullptr },
+ {{ 200, 100 }, d2 },
+ {{ 299, 199 }, d2 },
+ });
}
{
@@ -113,6 +173,22 @@
SkRect::MakeWH(600, 400),
SkRect::MakeWH(600, 400),
&damage);
+
+ check_hittest(reporter, root, {
+ {{ -1, 0 }, nullptr },
+ {{ 0, -1 }, nullptr },
+ {{ 25, 0 }, nullptr },
+ {{ 0, 50 }, nullptr },
+ {{ 0, 0 }, d1 },
+ {{ 24, 49 }, d1 },
+
+ {{ 99, 50 }, nullptr },
+ {{ 100, 49 }, nullptr },
+ {{ 150, 50 }, nullptr },
+ {{ 100, 100 }, nullptr },
+ {{ 100, 50 }, d2 },
+ {{ 149, 99 }, d2 },
+ });
}
{
@@ -123,6 +199,22 @@
SkRect::MakeWH(500, 400),
SkRect::MakeLTRB(400, 200, 600, 400),
&damage);
+
+ check_hittest(reporter, root, {
+ {{ -1, 0 }, nullptr },
+ {{ 0, -1 }, nullptr },
+ {{ 25, 0 }, nullptr },
+ {{ 0, 50 }, nullptr },
+ {{ 0, 0 }, d1 },
+ {{ 24, 49 }, d1 },
+
+ {{ 99, 50 }, nullptr },
+ {{ 100, 49 }, nullptr },
+ {{ 125, 50 }, nullptr },
+ {{ 100, 100 }, nullptr },
+ {{ 100, 50 }, d2 },
+ {{ 124, 99 }, d2 },
+ });
}
}
diff --git a/tools/viewer/SlideDir.cpp b/tools/viewer/SlideDir.cpp
index beb2ec2..a6b3b14 100644
--- a/tools/viewer/SlideDir.cpp
+++ b/tools/viewer/SlideDir.cpp
@@ -80,6 +80,8 @@
fSlide->draw(canvas);
}
+ const RenderNode* onNodeAt(const SkPoint&) const override { return nullptr; }
+
private:
void tick(SkMSec t) {
fSlide->animate(SkAnimTimer(t * 1e6));