Skip to content

Commit cc2c3c7

Browse files
committed
Add touchingColor() method
1 parent 7b3d3a3 commit cc2c3c7

File tree

5 files changed

+332
-0
lines changed

5 files changed

+332
-0
lines changed

src/irenderedtarget.h

+1
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ class IRenderedTarget : public QNanoQuickItem
8989
virtual QRgb colorAtScratchPoint(double x, double y) const = 0;
9090

9191
virtual bool touchingClones(const std::vector<libscratchcpp::Sprite *> &clones) const = 0;
92+
virtual bool touchingColor(const libscratchcpp::Value &color) const = 0;
9293
};
9394

9495
} // namespace scratchcpprender

src/renderedtarget.cpp

+132
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include <scratchcpp/iengine.h>
44
#include <scratchcpp/costume.h>
55
#include <scratchcpp/rect.h>
6+
#include <scratchcpp/value.h>
67
#include <QtSvg/QSvgRenderer>
78
#include <qnanopainter.h>
89

@@ -584,6 +585,7 @@ bool RenderedTarget::containsScratchPoint(double x, double y) const
584585

585586
QRgb RenderedTarget::colorAtScratchPoint(double x, double y) const
586587
{
588+
// NOTE: Only this target is processed! Use sampleColor3b() to get the final color.
587589
if (!m_engine || !m_cpuTexture.isValid())
588590
return qRgba(0, 0, 0, 0);
589591

@@ -638,6 +640,48 @@ bool RenderedTarget::touchingClones(const std::vector<libscratchcpp::Sprite *> &
638640
return false;
639641
}
640642

643+
bool RenderedTarget::touchingColor(const Value &color) const
644+
{
645+
// https://github.com/scratchfoundation/scratch-render/blob/0a04c2fb165f5c20406ec34ab2ea5682ae45d6e0/src/RenderWebGL.js#L775-L841
646+
QRgb rgb = convertColor(color);
647+
648+
std::vector<Target *> targets;
649+
getVisibleTargets(targets);
650+
651+
QRectF myRect = touchingBounds();
652+
std::vector<IRenderedTarget *> candidates;
653+
QRectF bounds = candidatesBounds(myRect, targets, candidates);
654+
655+
if (colorMatches(rgb, qRgb(255, 255, 255))) {
656+
// The color we're checking for is the background color which spans the entire stage
657+
bounds = myRect;
658+
659+
if (bounds.isEmpty())
660+
return false;
661+
} else if (candidates.empty()) {
662+
// If not checking for the background color, we can return early if there are no candidate drawables
663+
return false;
664+
}
665+
666+
QPointF point;
667+
668+
// Loop through the points of the union
669+
for (int y = bounds.top(); y <= bounds.bottom(); y++) {
670+
for (int x = bounds.left(); x <= bounds.right(); x++) {
671+
if (this->containsScratchPoint(x, y)) {
672+
point.setX(x);
673+
point.setY(y);
674+
QRgb pixelColor = sampleColor3b(point, candidates);
675+
676+
if (colorMatches(rgb, pixelColor))
677+
return true;
678+
}
679+
}
680+
}
681+
682+
return false;
683+
}
684+
641685
void RenderedTarget::calculatePos()
642686
{
643687
if (!m_skin || !m_costume || !m_engine)
@@ -810,6 +854,38 @@ CpuTextureManager *RenderedTarget::textureManager() const
810854
return m_textureManager.get();
811855
}
812856

857+
void RenderedTarget::getVisibleTargets(std::vector<Target *> &dst) const
858+
{
859+
dst.clear();
860+
861+
if (!m_engine)
862+
return;
863+
864+
const auto &targets = m_engine->targets();
865+
866+
for (auto target : targets) {
867+
Q_ASSERT(target);
868+
869+
if (target->isStage())
870+
dst.push_back(target.get());
871+
else {
872+
Sprite *sprite = static_cast<Sprite *>(target.get());
873+
874+
if (sprite->visible())
875+
dst.push_back(target.get());
876+
877+
const auto &clones = sprite->clones();
878+
879+
for (auto clone : clones) {
880+
if (clone->visible())
881+
dst.push_back(clone.get());
882+
}
883+
}
884+
}
885+
886+
std::sort(dst.begin(), dst.end(), [](Target *t1, Target *t2) { return t1->layerOrder() > t2->layerOrder(); });
887+
}
888+
813889
QRectF RenderedTarget::touchingBounds() const
814890
{
815891
// https://github.com/scratchfoundation/scratch-render/blob/0a04c2fb165f5c20406ec34ab2ea5682ae45d6e0/src/RenderWebGL.js#L1330-L1350
@@ -926,6 +1002,62 @@ void RenderedTarget::clampRect(Rect &rect, double left, double right, double bot
9261002
rect.setTop(std::max(rect.top(), bottom));
9271003
}
9281004

1005+
QRgb RenderedTarget::convertColor(const libscratchcpp::Value &color)
1006+
{
1007+
// TODO: Remove this after libscratchcpp starts converting colors (it still needs to be converted to RGB here)
1008+
std::string stringValue;
1009+
1010+
if (color.isString())
1011+
stringValue = color.toString();
1012+
1013+
if (!stringValue.empty() && stringValue[0] == '#') {
1014+
bool valid = false;
1015+
QColor color;
1016+
1017+
if (stringValue.size() <= 7) // #RRGGBB
1018+
{
1019+
color = QColor::fromString(stringValue);
1020+
valid = color.isValid();
1021+
}
1022+
1023+
if (!valid)
1024+
color = Qt::black;
1025+
1026+
return color.rgb();
1027+
1028+
} else
1029+
return QColor::fromRgba(static_cast<QRgb>(color.toLong())).rgb();
1030+
}
1031+
1032+
bool RenderedTarget::colorMatches(QRgb a, QRgb b)
1033+
{
1034+
// https://github.com/scratchfoundation/scratch-render/blob/0a04c2fb165f5c20406ec34ab2ea5682ae45d6e0/src/RenderWebGL.js#L77-L81
1035+
return (qRed(a) & 0b11111000) == (qRed(b) & 0b11111000) && (qGreen(a) & 0b11111000) == (qGreen(b) & 0b11111000) && (qBlue(a) & 0b11110000) == (qBlue(b) & 0b11110000);
1036+
}
1037+
1038+
QRgb RenderedTarget::sampleColor3b(const QPointF &point, const std::vector<IRenderedTarget *> &targets)
1039+
{
1040+
// https://github.com/scratchfoundation/scratch-render/blob/0a04c2fb165f5c20406ec34ab2ea5682ae45d6e0/src/RenderWebGL.js#L1966-L1990
1041+
double blendAlpha = 1;
1042+
QRgb blendColor;
1043+
int r = 0, g = 0, b = 0;
1044+
1045+
for (int i = 0; blendAlpha != 0 && i < targets.size(); i++) {
1046+
Q_ASSERT(targets[i]);
1047+
blendColor = targets[i]->colorAtScratchPoint(point.x(), point.y());
1048+
1049+
r += qRed(blendColor) * blendAlpha;
1050+
g += qGreen(blendColor) * blendAlpha;
1051+
b += qBlue(blendColor) * blendAlpha;
1052+
blendAlpha *= (1.0 - (qAlpha(blendColor) / 255.0));
1053+
}
1054+
1055+
r += blendAlpha * 255;
1056+
g += blendAlpha * 255;
1057+
b += blendAlpha * 255;
1058+
return qRgb(r, g, b);
1059+
}
1060+
9291061
bool RenderedTarget::mirrorHorizontally() const
9301062
{
9311063
return m_mirrorHorizontally;

src/renderedtarget.h

+5
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ class RenderedTarget : public IRenderedTarget
9797
QRgb colorAtScratchPoint(double x, double y) const override;
9898

9999
bool touchingClones(const std::vector<libscratchcpp::Sprite *> &) const override;
100+
bool touchingColor(const libscratchcpp::Value &color) const override;
100101

101102
signals:
102103
void engineChanged();
@@ -125,11 +126,15 @@ class RenderedTarget : public IRenderedTarget
125126
QPointF mapFromStageWithOriginPoint(const QPointF &scenePoint) const;
126127
QPointF mapFromScratchToLocal(const QPointF &point) const;
127128
CpuTextureManager *textureManager() const;
129+
void getVisibleTargets(std::vector<libscratchcpp::Target *> &dst) const;
128130
QRectF touchingBounds() const;
129131
QRectF candidatesBounds(const QRectF &targetRect, const std::vector<libscratchcpp::Target *> &candidates, std::vector<IRenderedTarget *> &dst) const;
130132
QRectF candidatesBounds(const QRectF &targetRect, const std::vector<libscratchcpp::Sprite *> &candidates, std::vector<IRenderedTarget *> &dst) const;
131133
static QRectF candidateIntersection(const QRectF &targetRect, IRenderedTarget *target);
132134
static void clampRect(libscratchcpp::Rect &rect, double left, double right, double bottom, double top);
135+
static QRgb convertColor(const libscratchcpp::Value &color);
136+
static bool colorMatches(QRgb a, QRgb b);
137+
static QRgb sampleColor3b(const QPointF &point, const std::vector<IRenderedTarget *> &targets);
133138

134139
libscratchcpp::IEngine *m_engine = nullptr;
135140
libscratchcpp::Costume *m_costume = nullptr;

test/mocks/renderedtargetmock.h

+1
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ class RenderedTargetMock : public IRenderedTarget
7474
MOCK_METHOD(QRgb, colorAtScratchPoint, (double, double), (const, override));
7575

7676
MOCK_METHOD(bool, touchingClones, (const std::vector<libscratchcpp::Sprite *> &), (const, override));
77+
MOCK_METHOD(bool, touchingColor, (const libscratchcpp::Value &), (const, override));
7778

7879
MOCK_METHOD(QNanoQuickItemPainter *, createItemPainter, (), (const, override));
7980
MOCK_METHOD(void, hoverEnterEvent, (QHoverEvent *), (override));

0 commit comments

Comments
 (0)