Skip to content

Commit 26db0eb

Browse files
authored
Merge pull request #53 from scratchcpp/contains_method
Implement the RenderedTarget::contains() method
2 parents 108fd32 + ed6a33b commit 26db0eb

File tree

7 files changed

+186
-2
lines changed

7 files changed

+186
-2
lines changed

ScratchCPPGui/irenderedtarget.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@ class IRenderedTarget : public QNanoQuickItem
6363

6464
virtual bool isSvg() const = 0;
6565
virtual void paintSvg(QNanoPainter *painter) = 0;
66+
67+
virtual void updateHullPoints(QOpenGLFramebufferObject *fbo) = 0;
68+
virtual const std::vector<QPointF> &hullPoints() const = 0;
6669
};
6770

6871
} // namespace scratchcppgui

ScratchCPPGui/renderedtarget.cpp

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,79 @@ void RenderedTarget::paintSvg(QNanoPainter *painter)
303303
context->makeCurrent(oldSurface);
304304
}
305305

306+
void RenderedTarget::updateHullPoints(QOpenGLFramebufferObject *fbo)
307+
{
308+
if (m_stageModel)
309+
return; // hull points are useless for the stage
310+
311+
Q_ASSERT(fbo);
312+
int width = fbo->width();
313+
int height = fbo->height();
314+
m_hullPoints.clear();
315+
m_hullPoints.reserve(width * height);
316+
317+
// Blit multisampled FBO to a custom FBO
318+
QOpenGLFramebufferObject customFbo(fbo->size());
319+
glBindFramebuffer(GL_READ_FRAMEBUFFER_EXT, fbo->handle());
320+
glBindFramebuffer(GL_DRAW_FRAMEBUFFER_EXT, customFbo.handle());
321+
glBlitFramebuffer(0, 0, width, height, 0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_NEAREST);
322+
glBindFramebuffer(GL_FRAMEBUFFER_EXT, customFbo.handle());
323+
324+
// Read pixels from framebuffer
325+
size_t size = width * height * 4;
326+
GLubyte *pixelData = new GLubyte[size];
327+
glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixelData);
328+
glBindFramebuffer(GL_FRAMEBUFFER_EXT, 0);
329+
fbo->bind();
330+
331+
// Flip vertically
332+
int rowSize = width * 4;
333+
GLubyte *tempRow = new GLubyte[rowSize];
334+
335+
for (size_t i = 0; i < height / 2; ++i) {
336+
size_t topRowIndex = i * rowSize;
337+
size_t bottomRowIndex = (height - 1 - i) * rowSize;
338+
339+
// Swap rows
340+
memcpy(tempRow, &pixelData[topRowIndex], rowSize);
341+
memcpy(&pixelData[topRowIndex], &pixelData[bottomRowIndex], rowSize);
342+
memcpy(&pixelData[bottomRowIndex], tempRow, rowSize);
343+
}
344+
345+
delete[] tempRow;
346+
347+
// Fill hull points vector
348+
for (int y = 0; y < height; y++) {
349+
for (int x = 0; x < width; x++) {
350+
int index = (y * width + x) * 4; // RGBA channels
351+
352+
// Check alpha channel
353+
if (pixelData[index + 3] > 0)
354+
m_hullPoints.push_back(QPointF(x, y));
355+
}
356+
}
357+
358+
delete[] pixelData;
359+
}
360+
361+
const std::vector<QPointF> &RenderedTarget::hullPoints() const
362+
{
363+
return m_hullPoints;
364+
}
365+
366+
bool RenderedTarget::contains(const QPointF &point) const
367+
{
368+
if (m_stageModel)
369+
return true; // the stage contains any point within the scene
370+
371+
for (const auto &hullPoint : m_hullPoints) {
372+
if (point.toPoint() == hullPoint.toPoint())
373+
return true;
374+
}
375+
376+
return false;
377+
}
378+
306379
void RenderedTarget::calculateSize(Target *target, double costumeWidth, double costumeHeight)
307380
{
308381
if (m_costume) {

ScratchCPPGui/renderedtarget.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,11 @@ class RenderedTarget : public IRenderedTarget
5959
bool isSvg() const override;
6060
void paintSvg(QNanoPainter *painter) override;
6161

62+
void updateHullPoints(QOpenGLFramebufferObject *fbo) override;
63+
const std::vector<QPointF> &hullPoints() const override;
64+
65+
Q_INVOKABLE bool contains(const QPointF &point) const override;
66+
6267
signals:
6368
void engineChanged();
6469
void stageModelChanged();
@@ -101,6 +106,7 @@ class RenderedTarget : public IRenderedTarget
101106
double m_originY = 0;
102107
qreal m_maximumWidth = std::numeric_limits<double>::infinity();
103108
qreal m_maximumHeight = std::numeric_limits<double>::infinity();
109+
std::vector<QPointF> m_hullPoints;
104110
};
105111

106112
} // namespace scratchcppgui

ScratchCPPGui/targetpainter.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,30 @@ void TargetPainter::paint(QNanoPainter *painter)
2626
if (m_target->isSvg())
2727
m_target->paintSvg(painter);
2828
else {
29+
QOpenGLContext *context = QOpenGLContext::currentContext();
30+
Q_ASSERT(context);
31+
32+
if (!context)
33+
return;
34+
35+
QOffscreenSurface surface;
36+
surface.setFormat(context->format());
37+
surface.create();
38+
Q_ASSERT(surface.isValid());
39+
40+
QSurface *oldSurface = context->surface();
41+
context->makeCurrent(&surface);
42+
43+
painter->beginFrame(width, height);
2944
QNanoImage image = QNanoImage::fromCache(painter, m_target->bitmapBuffer(), m_target->bitmapUniqueKey());
3045
painter->drawImage(image, 0, 0, width, height);
46+
painter->endFrame();
47+
48+
context->doneCurrent();
49+
context->makeCurrent(oldSurface);
3150
}
3251

52+
m_target->updateHullPoints(framebufferObject());
3353
m_target->unlockCostume();
3454
}
3555

test/mocks/renderedtargetmock.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ class RenderedTargetMock : public IRenderedTarget
4444
MOCK_METHOD(bool, isSvg, (), (const, override));
4545
MOCK_METHOD(void, paintSvg, (QNanoPainter *), (override));
4646

47+
MOCK_METHOD(void, updateHullPoints, (QOpenGLFramebufferObject *), (override));
48+
MOCK_METHOD(const std::vector<QPointF> &, hullPoints, (), (const, override));
49+
50+
MOCK_METHOD(bool, contains, (const QPointF &), (const, override));
4751
MOCK_METHOD(QNanoQuickItemPainter *, createItemPainter, (), (const, override));
4852
};
4953

test/renderedtarget/renderedtarget_test.cpp

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -468,6 +468,82 @@ TEST_F(RenderedTargetTest, PaintSvg)
468468
context.doneCurrent();
469469
}
470470

471+
TEST_F(RenderedTargetTest, HullPoints)
472+
{
473+
EngineMock engine;
474+
Sprite sprite;
475+
SpriteModel model;
476+
model.init(&sprite);
477+
478+
RenderedTarget target;
479+
target.setEngine(&engine);
480+
target.setSpriteModel(&model);
481+
482+
// Create OpenGL context
483+
QOpenGLContext context;
484+
QOffscreenSurface surface;
485+
createContextAndSurface(&context, &surface);
486+
487+
// Create a painter
488+
QNanoPainter painter;
489+
490+
QOpenGLFramebufferObjectFormat format;
491+
format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
492+
493+
// Begin painting
494+
QOpenGLFramebufferObject fbo(4, 6, format);
495+
fbo.bind();
496+
painter.beginFrame(fbo.width(), fbo.height());
497+
498+
// Paint
499+
QNanoImage image = QNanoImage::fromCache(&painter, "image.png");
500+
painter.drawImage(image, 0, 0);
501+
painter.endFrame();
502+
503+
// Test hull points
504+
target.updateHullPoints(&fbo);
505+
ASSERT_EQ(target.hullPoints(), std::vector<QPointF>({ { 1, 1 }, { 2, 1 }, { 3, 1 }, { 1, 2 }, { 3, 2 }, { 1, 3 }, { 2, 3 }, { 3, 3 } }));
506+
507+
// Begin painting (multisampled)
508+
format.setSamples(16);
509+
QOpenGLFramebufferObject fboMultiSampled(4, 6, format);
510+
fboMultiSampled.bind();
511+
painter.beginFrame(fboMultiSampled.width(), fboMultiSampled.height());
512+
513+
// Paint (multisampled)
514+
painter.drawImage(image, 0, 0);
515+
painter.endFrame();
516+
517+
// Test hull points (this is undefined with multisampling, so we just check if there are any hull points)
518+
ASSERT_FALSE(target.hullPoints().empty());
519+
520+
// Release
521+
fbo.release();
522+
context.doneCurrent();
523+
524+
// Test contains()
525+
ASSERT_FALSE(target.contains({ 0, 0 }));
526+
ASSERT_FALSE(target.contains({ 1, 0 }));
527+
ASSERT_FALSE(target.contains({ 2, 0 }));
528+
ASSERT_FALSE(target.contains({ 3, 0 }));
529+
530+
ASSERT_FALSE(target.contains({ 0, 1 }));
531+
ASSERT_TRUE(target.contains({ 1, 1 }));
532+
ASSERT_TRUE(target.contains({ 1.4, 1.25 }));
533+
ASSERT_TRUE(target.contains({ 2, 1 }));
534+
ASSERT_TRUE(target.contains({ 3, 1 }));
535+
536+
ASSERT_TRUE(target.contains({ 1, 2 }));
537+
ASSERT_FALSE(target.contains({ 2, 2 }));
538+
ASSERT_TRUE(target.contains({ 3, 2 }));
539+
ASSERT_FALSE(target.contains({ 3.5, 2.1 }));
540+
541+
ASSERT_TRUE(target.contains({ 1, 3 }));
542+
ASSERT_TRUE(target.contains({ 2, 3 }));
543+
ASSERT_TRUE(target.contains({ 3, 3 }));
544+
ASSERT_FALSE(target.contains({ 3.3, 3.5 }));
545+
}
546+
471547
TEST_F(RenderedTargetTest, Engine)
472548
{
473549
RenderedTarget target;

test/targetpainter/targetpainter_test.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ TEST_F(TargetPainterTest, PaintBitmap)
5757

5858
// Begin painting reference
5959
QNanoPainter refPainter;
60-
QOpenGLFramebufferObject refFbo(100, 100, format);
60+
QOpenGLFramebufferObject refFbo(40, 60, format);
6161
refFbo.bind();
6262
refPainter.beginFrame(refFbo.width(), refFbo.height());
6363

@@ -67,7 +67,7 @@ TEST_F(TargetPainterTest, PaintBitmap)
6767
refPainter.endFrame();
6868

6969
// Begin painting
70-
QOpenGLFramebufferObject fbo(100, 100, format);
70+
QOpenGLFramebufferObject fbo(40, 60, format);
7171
fbo.bind();
7272
painter.beginFrame(fbo.width(), fbo.height());
7373

@@ -79,6 +79,7 @@ TEST_F(TargetPainterTest, PaintBitmap)
7979
EXPECT_CALL(target, bitmapBuffer()).WillOnce(Return(&buffer));
8080
static const QString uniqueKey("abc");
8181
EXPECT_CALL(target, bitmapUniqueKey()).WillOnce(ReturnRef(uniqueKey));
82+
EXPECT_CALL(target, updateHullPoints);
8283
EXPECT_CALL(target, unlockCostume());
8384
targetPainter.paint(&painter);
8485
painter.endFrame();
@@ -110,6 +111,7 @@ TEST_F(TargetPainterTest, PaintSvg)
110111
EXPECT_CALL(target, height()).WillOnce(Return(60));
111112
EXPECT_CALL(target, isSvg()).WillOnce(Return(true));
112113
EXPECT_CALL(target, paintSvg(&painter));
114+
EXPECT_CALL(target, updateHullPoints);
113115
EXPECT_CALL(target, unlockCostume());
114116
targetPainter.paint(&painter);
115117

0 commit comments

Comments
 (0)