Skip to content

Commit 8b62687

Browse files
authored
Merge pull request #72 from scratchcpp/pen_api
Add pen API
2 parents f73f192 + e3f691e commit 8b62687

23 files changed

+898
-1
lines changed

src/CMakeLists.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,12 @@ qt_add_qml_module(scratchcpp-render
4949
mouseeventhandler.h
5050
keyeventhandler.cpp
5151
keyeventhandler.h
52+
ipenlayer.h
53+
penlayer.cpp
54+
penlayer.h
55+
penlayerpainter.cpp
56+
penlayerpainter.h
57+
penattributes.h
5258
)
5359

5460
list(APPEND QML_IMPORT_PATH ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR})

src/ProjectPlayer.qml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,12 @@ ProjectScene {
109109
onStageModelChanged: stageModel.renderedTarget = this
110110
}
111111

112+
PenLayer {
113+
id: projectPenLayer
114+
engine: loader.engine
115+
anchors.fill: parent
116+
}
117+
112118
Component {
113119
id: renderedSprite
114120

@@ -121,6 +127,7 @@ ProjectScene {
121127
engine = loader.engine;
122128
spriteModel = modelData;
123129
spriteModel.renderedTarget = this;
130+
spriteModel.penLayer = projectPenLayer;
124131
}
125132
}
126133
}

src/ipenlayer.h

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// SPDX-License-Identifier: LGPL-3.0-or-later
2+
3+
#pragma once
4+
5+
#include <qnanoquickitem.h>
6+
7+
namespace libscratchcpp
8+
{
9+
10+
class IEngine;
11+
12+
}
13+
14+
namespace scratchcpprender
15+
{
16+
17+
struct PenAttributes;
18+
19+
class IPenLayer : public QNanoQuickItem
20+
{
21+
public:
22+
IPenLayer(QNanoQuickItem *parent = nullptr) :
23+
QNanoQuickItem(parent)
24+
{
25+
}
26+
27+
virtual ~IPenLayer() { }
28+
29+
virtual bool antialiasingEnabled() const = 0;
30+
virtual void setAntialiasingEnabled(bool enabled) = 0;
31+
32+
virtual libscratchcpp::IEngine *engine() const = 0;
33+
virtual void setEngine(libscratchcpp::IEngine *newEngine) = 0;
34+
35+
virtual void clear() = 0;
36+
virtual void drawPoint(const PenAttributes &penAttributes, double x, double y) = 0;
37+
virtual void drawLine(const PenAttributes &penAttributes, double x0, double y0, double x1, double y1) = 0;
38+
39+
virtual QOpenGLFramebufferObject *framebufferObject() const = 0;
40+
};
41+
42+
} // namespace scratchcpprender

src/penattributes.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// SPDX-License-Identifier: LGPL-3.0-or-later
2+
3+
#pragma once
4+
5+
#include <QColor>
6+
7+
namespace scratchcpprender
8+
{
9+
10+
struct PenAttributes
11+
{
12+
QColor color = QColor(0, 0, 255);
13+
double diameter = 1;
14+
};
15+
16+
} // namespace scratchcpprender

src/penlayer.cpp

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
// SPDX-License-Identifier: LGPL-3.0-or-later
2+
3+
#include "penlayer.h"
4+
#include "penlayerpainter.h"
5+
#include "penattributes.h"
6+
7+
using namespace scratchcpprender;
8+
9+
std::unordered_map<libscratchcpp::IEngine *, IPenLayer *> PenLayer::m_projectPenLayers;
10+
11+
PenLayer::PenLayer(QNanoQuickItem *parent) :
12+
IPenLayer(parent)
13+
{
14+
m_fboFormat.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
15+
m_fboFormat.setSamples(m_antialiasingEnabled ? 4 : 0);
16+
}
17+
18+
PenLayer::~PenLayer()
19+
{
20+
if (m_engine)
21+
m_projectPenLayers.erase(m_engine);
22+
}
23+
24+
bool PenLayer::antialiasingEnabled() const
25+
{
26+
return m_antialiasingEnabled;
27+
}
28+
29+
void PenLayer::setAntialiasingEnabled(bool enabled)
30+
{
31+
m_antialiasingEnabled = enabled;
32+
m_fboFormat.setSamples(enabled ? 4 : 0);
33+
}
34+
35+
libscratchcpp::IEngine *PenLayer::engine() const
36+
{
37+
return m_engine;
38+
}
39+
40+
void PenLayer::setEngine(libscratchcpp::IEngine *newEngine)
41+
{
42+
if (m_engine == newEngine)
43+
return;
44+
45+
if (m_engine)
46+
m_projectPenLayers.erase(m_engine);
47+
48+
m_engine = newEngine;
49+
50+
if (m_engine) {
51+
m_projectPenLayers[m_engine] = this;
52+
m_fbo = std::make_unique<QOpenGLFramebufferObject>(m_engine->stageWidth(), m_engine->stageHeight(), m_fboFormat);
53+
Q_ASSERT(m_fbo->isValid());
54+
55+
m_paintDevice = std::make_unique<QOpenGLPaintDevice>(m_fbo->size());
56+
clear();
57+
}
58+
59+
emit engineChanged();
60+
}
61+
62+
void scratchcpprender::PenLayer::clear()
63+
{
64+
if (!m_fbo)
65+
return;
66+
67+
m_fbo->bind();
68+
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
69+
glClear(GL_COLOR_BUFFER_BIT);
70+
m_fbo->release();
71+
72+
update();
73+
}
74+
75+
void scratchcpprender::PenLayer::drawPoint(const PenAttributes &penAttributes, double x, double y)
76+
{
77+
drawLine(penAttributes, x, y, x, y);
78+
}
79+
80+
void scratchcpprender::PenLayer::drawLine(const PenAttributes &penAttributes, double x0, double y0, double x1, double y1)
81+
{
82+
if (!m_fbo || !m_paintDevice || !m_engine)
83+
return;
84+
85+
// Begin painting
86+
m_fbo->bind();
87+
QPainter painter(m_paintDevice.get());
88+
painter.beginNativePainting();
89+
painter.setRenderHint(QPainter::Antialiasing, m_antialiasingEnabled);
90+
painter.setRenderHint(QPainter::SmoothPixmapTransform, false);
91+
92+
// Translate to Scratch coordinate system
93+
double stageWidthHalf = m_engine->stageWidth() / 2;
94+
double stageHeightHalf = m_engine->stageHeight() / 2;
95+
x0 += stageWidthHalf;
96+
y0 = stageHeightHalf - y0;
97+
x1 += stageWidthHalf;
98+
y1 = stageHeightHalf - y1;
99+
100+
// Set pen attributes
101+
QPen pen(penAttributes.color);
102+
pen.setWidthF(penAttributes.diameter);
103+
pen.setCapStyle(Qt::RoundCap);
104+
painter.setPen(pen);
105+
106+
// If the start and end coordinates are the same, draw a point, otherwise draw a line
107+
if (x0 == x1 && y0 == y1)
108+
painter.drawPoint(x0, y0);
109+
else
110+
painter.drawLine(x0, y0, x1, y1);
111+
112+
// End painting
113+
painter.endNativePainting();
114+
painter.end();
115+
m_fbo->release();
116+
117+
update();
118+
}
119+
120+
QOpenGLFramebufferObject *PenLayer::framebufferObject() const
121+
{
122+
return m_fbo.get();
123+
}
124+
125+
IPenLayer *PenLayer::getProjectPenLayer(libscratchcpp::IEngine *engine)
126+
{
127+
auto it = m_projectPenLayers.find(engine);
128+
129+
if (it != m_projectPenLayers.cend())
130+
return it->second;
131+
132+
return nullptr;
133+
}
134+
135+
QNanoQuickItemPainter *PenLayer::createItemPainter() const
136+
{
137+
return new PenLayerPainter;
138+
}

src/penlayer.h

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// SPDX-License-Identifier: LGPL-3.0-or-later
2+
3+
#pragma once
4+
5+
#include <ipenlayer.h>
6+
#include <QOpenGLFramebufferObject>
7+
#include <QOpenGLPaintDevice>
8+
#include <QPainter>
9+
#include <scratchcpp/iengine.h>
10+
11+
namespace scratchcpprender
12+
{
13+
14+
class PenLayer : public IPenLayer
15+
{
16+
Q_OBJECT
17+
QML_ELEMENT
18+
Q_PROPERTY(libscratchcpp::IEngine *engine READ engine WRITE setEngine NOTIFY engineChanged)
19+
20+
public:
21+
PenLayer(QNanoQuickItem *parent = nullptr);
22+
~PenLayer();
23+
24+
bool antialiasingEnabled() const override;
25+
void setAntialiasingEnabled(bool enabled) override;
26+
27+
libscratchcpp::IEngine *engine() const override;
28+
void setEngine(libscratchcpp::IEngine *newEngine) override;
29+
30+
void clear() override;
31+
void drawPoint(const PenAttributes &penAttributes, double x, double y) override;
32+
void drawLine(const PenAttributes &penAttributes, double x0, double y0, double x1, double y1) override;
33+
34+
QOpenGLFramebufferObject *framebufferObject() const override;
35+
36+
static IPenLayer *getProjectPenLayer(libscratchcpp::IEngine *engine);
37+
38+
signals:
39+
void engineChanged();
40+
41+
protected:
42+
QNanoQuickItemPainter *createItemPainter() const override;
43+
44+
private:
45+
static std::unordered_map<libscratchcpp::IEngine *, IPenLayer *> m_projectPenLayers;
46+
bool m_antialiasingEnabled = true;
47+
libscratchcpp::IEngine *m_engine = nullptr;
48+
std::unique_ptr<QOpenGLFramebufferObject> m_fbo;
49+
std::unique_ptr<QOpenGLPaintDevice> m_paintDevice;
50+
QOpenGLFramebufferObjectFormat m_fboFormat;
51+
};
52+
53+
} // namespace scratchcpprender

src/penlayerpainter.cpp

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// SPDX-License-Identifier: LGPL-3.0-or-later
2+
3+
#include "penlayerpainter.h"
4+
#include "penlayer.h"
5+
6+
using namespace scratchcpprender;
7+
8+
PenLayerPainter::PenLayerPainter(QOpenGLFramebufferObject *fbo)
9+
{
10+
m_targetFbo = fbo;
11+
}
12+
13+
void PenLayerPainter::paint(QNanoPainter *painter)
14+
{
15+
if (QThread::currentThread() != qApp->thread()) {
16+
qFatal("Error: Rendering must happen in the GUI thread to work correctly. Please disable threaded render loop using qputenv(\"QSG_RENDER_LOOP\", \"basic\") before constructing your "
17+
"application object.");
18+
}
19+
20+
QOpenGLContext *context = QOpenGLContext::currentContext();
21+
Q_ASSERT(context);
22+
23+
if (!context || !m_fbo)
24+
return;
25+
26+
// Custom FBO - only used for testing
27+
QOpenGLFramebufferObject *targetFbo = m_targetFbo ? m_targetFbo : framebufferObject();
28+
29+
QOpenGLFramebufferObjectFormat format;
30+
format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
31+
32+
// Blit the FBO to a temporary FBO first (multisampled FBOs can only be blitted to FBOs with the same size)
33+
QOpenGLFramebufferObject tmpFbo(m_fbo->size(), format);
34+
QOpenGLFramebufferObject::blitFramebuffer(&tmpFbo, m_fbo);
35+
QOpenGLFramebufferObject::blitFramebuffer(targetFbo, &tmpFbo);
36+
}
37+
38+
void PenLayerPainter::synchronize(QNanoQuickItem *item)
39+
{
40+
IPenLayer *penLayer = dynamic_cast<IPenLayer *>(item);
41+
Q_ASSERT(penLayer);
42+
43+
if (penLayer)
44+
m_fbo = penLayer->framebufferObject();
45+
}

src/penlayerpainter.h

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// SPDX-License-Identifier: LGPL-3.0-or-later
2+
3+
#pragma once
4+
5+
#include <qnanoquickitempainter.h>
6+
7+
#include "texture.h"
8+
9+
namespace scratchcpprender
10+
{
11+
12+
class PenLayerPainter : public QNanoQuickItemPainter
13+
{
14+
public:
15+
PenLayerPainter(QOpenGLFramebufferObject *fbo = nullptr);
16+
17+
void paint(QNanoPainter *painter) override;
18+
void synchronize(QNanoQuickItem *item) override;
19+
20+
private:
21+
QOpenGLFramebufferObject *m_targetFbo = nullptr;
22+
QOpenGLFramebufferObject *m_fbo = nullptr;
23+
};
24+
25+
} // namespace scratchcpprender

0 commit comments

Comments
 (0)