Skip to content

Commit 4a1a084

Browse files
authored
Merge pull request #44 from scratchcpp/svg
Add SVG support
2 parents 89a478d + 43daf69 commit 4a1a084

File tree

12 files changed

+315
-195
lines changed

12 files changed

+315
-195
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,8 +125,8 @@ Button {
125125
- [x] Everything related to displaying sprites (position, rotation, size, etc.)
126126
- [x] Loading projects in another thread
127127
- [x] API for engine properties (FPS, turbo mode, etc.)
128-
- [ ] Loading projects from URL
129-
- [ ] SVG rendering
128+
- [x] Loading projects from URL
129+
- [x] SVG rendering
130130
- [ ] Mouse position
131131
- [ ] Key press events
132132
- [ ] Mouse press events

ScratchCPPGui/irenderedtarget.h

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include <qnanoquickitem.h>
66

77
class QBuffer;
8+
class QNanoPainter;
89

910
namespace libscratchcpp
1011
{
@@ -52,20 +53,16 @@ class IRenderedTarget : public QNanoQuickItem
5253
virtual qreal height() const = 0;
5354
virtual void setHeight(qreal width) = 0;
5455

55-
virtual double costumeWidth() const = 0;
56-
virtual void setCostumeWidth(double width) = 0;
57-
58-
virtual double costumeHeight() const = 0;
59-
virtual void setCostumeHeight(double width) = 0;
60-
61-
virtual unsigned char *svgBitmap() const = 0;
6256
virtual QBuffer *bitmapBuffer() = 0;
6357
virtual const QString &bitmapUniqueKey() const = 0;
6458

6559
virtual void lockCostume() = 0;
6660
virtual void unlockCostume() = 0;
6761

6862
virtual bool mirrorHorizontally() const = 0;
63+
64+
virtual bool isSvg() const = 0;
65+
virtual void paintSvg(QNanoPainter *painter) = 0;
6966
};
7067

7168
} // namespace scratchcppgui

ScratchCPPGui/renderedtarget.cpp

Lines changed: 88 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
#include <scratchcpp/iengine.h>
44
#include <scratchcpp/costume.h>
5+
#include <QtSvg/QSvgRenderer>
6+
#include <qnanopainter.h>
57

68
#include "renderedtarget.h"
79
#include "targetpainter.h"
@@ -16,12 +18,6 @@ RenderedTarget::RenderedTarget(QNanoQuickItem *parent) :
1618
{
1719
}
1820

19-
RenderedTarget::~RenderedTarget()
20-
{
21-
if (m_svgBitmap)
22-
free(m_svgBitmap);
23-
}
24-
2521
void RenderedTarget::loadProperties()
2622
{
2723
Q_ASSERT(!(m_spriteModel && m_stageModel));
@@ -59,21 +55,21 @@ void RenderedTarget::loadProperties()
5955

6056
// Coordinates
6157
double size = sprite->size() / 100;
62-
m_x = static_cast<double>(m_engine->stageWidth()) / 2 + sprite->x() - m_costume->rotationCenterX() * size / 2 * (m_newMirrorHorizontally ? -1 : 1);
63-
m_y = static_cast<double>(m_engine->stageHeight()) / 2 - sprite->y() - m_costume->rotationCenterY() * size / 2;
64-
m_originX = m_costume->rotationCenterX() * size / 2.0;
65-
m_originY = m_costume->rotationCenterY() * size / 2.0;
58+
m_x = static_cast<double>(m_engine->stageWidth()) / 2 + sprite->x() - m_costume->rotationCenterX() * size / m_costume->bitmapResolution() * (m_newMirrorHorizontally ? -1 : 1);
59+
m_y = static_cast<double>(m_engine->stageHeight()) / 2 - sprite->y() - m_costume->rotationCenterY() * size / m_costume->bitmapResolution();
60+
m_originX = m_costume->rotationCenterX() * size / m_costume->bitmapResolution();
61+
m_originY = m_costume->rotationCenterY() * size / m_costume->bitmapResolution();
6662

6763
// Layer
6864
m_z = sprite->layerOrder();
6965
}
7066

7167
mutex.unlock();
7268
} else if (m_stageModel) {
73-
m_x = static_cast<double>(m_engine->stageWidth()) / 2 - m_costume->rotationCenterX() / 2.0;
74-
m_y = static_cast<double>(m_engine->stageHeight()) / 2 - m_costume->rotationCenterY() / 2.0;
75-
m_originX = m_costume->rotationCenterX() / 2.0;
76-
m_originY = m_costume->rotationCenterY() / 2.0;
69+
m_x = static_cast<double>(m_engine->stageWidth()) / 2 - m_costume->rotationCenterX() / m_costume->bitmapResolution();
70+
m_y = static_cast<double>(m_engine->stageHeight()) / 2 - m_costume->rotationCenterY() / m_costume->bitmapResolution();
71+
m_originX = m_costume->rotationCenterX() / m_costume->bitmapResolution();
72+
m_originY = m_costume->rotationCenterY() / m_costume->bitmapResolution();
7773
}
7874
}
7975

@@ -83,30 +79,14 @@ void RenderedTarget::loadCostume(Costume *costume)
8379
return;
8480

8581
m_costumeMutex.lock();
86-
Target *target = scratchTarget();
87-
m_costume = costume;
8882
m_imageChanged = true;
8983

9084
if (costume->dataFormat() == "svg") {
91-
// TODO: Load SVG here
92-
// In case of rasterizing, write the bitmap to m_svgBitmap
93-
} else {
94-
if (m_svgBitmap) {
95-
free(m_svgBitmap);
96-
m_svgBitmap = nullptr;
97-
}
98-
99-
m_bitmapBuffer.open(QBuffer::WriteOnly);
100-
m_bitmapBuffer.write(static_cast<const char *>(costume->data()), costume->dataSize());
101-
m_bitmapBuffer.close();
102-
m_bitmapUniqueKey = QString::fromStdString(costume->id());
103-
104-
QImageReader reader(&m_bitmapBuffer);
105-
QSize size = reader.size();
106-
calculateSize(target, size.width(), size.height());
107-
m_bitmapBuffer.close();
85+
if (costume != m_costume)
86+
m_svgRenderer.load(QByteArray::fromRawData(static_cast<const char *>(costume->data()), costume->dataSize()));
10887
}
10988

89+
m_costume = costume;
11090
m_costumeMutex.unlock();
11191
}
11292

@@ -116,8 +96,12 @@ void RenderedTarget::updateProperties()
11696
setVisible(m_visible);
11797

11898
if (m_visible) {
119-
setWidth(m_width);
120-
setHeight(m_height);
99+
if (m_imageChanged) {
100+
doLoadCostume();
101+
update();
102+
m_imageChanged = false;
103+
}
104+
121105
setX(m_x);
122106
setY(m_y);
123107
setZ(m_z);
@@ -128,11 +112,6 @@ void RenderedTarget::updateProperties()
128112
m_mirrorHorizontally = m_newMirrorHorizontally;
129113
emit mirrorHorizontallyChanged();
130114
}
131-
132-
if (m_imageChanged) {
133-
update();
134-
m_imageChanged = false;
135-
}
136115
}
137116

138117
mutex.unlock();
@@ -210,38 +189,73 @@ void RenderedTarget::setHeight(qreal height)
210189
QNanoQuickItem::setHeight(height);
211190
}
212191

213-
double RenderedTarget::costumeWidth() const
192+
QNanoQuickItemPainter *RenderedTarget::createItemPainter() const
214193
{
215-
return m_width;
194+
return new TargetPainter();
216195
}
217196

218-
void RenderedTarget::setCostumeWidth(double width)
197+
void RenderedTarget::doLoadCostume()
219198
{
220-
mutex.lock();
221-
m_width = width;
222-
mutex.unlock();
223-
}
199+
m_costumeMutex.lock();
224200

225-
double RenderedTarget::costumeHeight() const
226-
{
227-
return m_height;
228-
}
201+
if (!m_costume) {
202+
m_costumeMutex.unlock();
203+
return;
204+
}
229205

230-
void RenderedTarget::setCostumeHeight(double height)
231-
{
232-
mutex.lock();
233-
m_height = height;
234-
mutex.unlock();
235-
}
206+
Target *target = scratchTarget();
236207

237-
unsigned char *RenderedTarget::svgBitmap() const
238-
{
239-
return m_svgBitmap;
208+
if (m_costume->dataFormat() == "svg") {
209+
QRectF rect = m_svgRenderer.viewBoxF();
210+
calculateSize(target, rect.width(), rect.height());
211+
} else {
212+
m_bitmapBuffer.open(QBuffer::WriteOnly);
213+
m_bitmapBuffer.write(static_cast<const char *>(m_costume->data()), m_costume->dataSize());
214+
m_bitmapBuffer.close();
215+
m_bitmapUniqueKey = QString::fromStdString(m_costume->id());
216+
217+
QImageReader reader(&m_bitmapBuffer);
218+
QSize size = reader.size();
219+
calculateSize(target, size.width(), size.height());
220+
m_bitmapBuffer.close();
221+
}
222+
223+
m_costumeMutex.unlock();
240224
}
241225

242-
QNanoQuickItemPainter *RenderedTarget::createItemPainter() const
226+
void RenderedTarget::paintSvg(QNanoPainter *painter)
243227
{
244-
return new TargetPainter();
228+
Q_ASSERT(painter);
229+
QOpenGLContext *context = QOpenGLContext::currentContext();
230+
Q_ASSERT(context);
231+
232+
if (!context)
233+
return;
234+
235+
QOffscreenSurface surface;
236+
surface.setFormat(context->format());
237+
surface.create();
238+
Q_ASSERT(surface.isValid());
239+
240+
QSurface *oldSurface = context->surface();
241+
context->makeCurrent(&surface);
242+
243+
const QRectF drawRect(0, 0, width(), height());
244+
const QSize drawRectSize = drawRect.size().toSize();
245+
246+
/*QOpenGLFramebufferObjectFormat fboFormat;
247+
fboFormat.setSamples(16);
248+
fboFormat.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);*/
249+
250+
QOpenGLPaintDevice device(drawRectSize);
251+
QPainter qPainter;
252+
qPainter.begin(&device);
253+
qPainter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform);
254+
m_svgRenderer.render(&qPainter, drawRect);
255+
qPainter.end();
256+
257+
context->doneCurrent();
258+
context->makeCurrent(oldSurface);
245259
}
246260

247261
void RenderedTarget::calculateSize(Target *target, double costumeWidth, double costumeHeight)
@@ -252,11 +266,11 @@ void RenderedTarget::calculateSize(Target *target, double costumeWidth, double c
252266

253267
if (sprite) {
254268
double size = sprite->size();
255-
m_width = costumeWidth * size / 100 / bitmapRes;
256-
m_height = costumeHeight * size / 100 / bitmapRes;
269+
setWidth(costumeWidth * size / 100 / bitmapRes);
270+
setHeight(costumeHeight * size / 100 / bitmapRes);
257271
} else {
258-
m_width = costumeWidth / bitmapRes;
259-
m_height = costumeHeight / bitmapRes;
272+
setWidth(costumeWidth / bitmapRes);
273+
setHeight(costumeHeight / bitmapRes);
260274
}
261275
}
262276
}
@@ -285,3 +299,11 @@ bool RenderedTarget::mirrorHorizontally() const
285299
{
286300
return m_mirrorHorizontally;
287301
}
302+
303+
bool RenderedTarget::isSvg() const
304+
{
305+
if (!m_costume)
306+
return false;
307+
308+
return (m_costume->dataFormat() == "svg");
309+
}

ScratchCPPGui/renderedtarget.h

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include <qnanoquickitem.h>
66
#include <QBuffer>
77
#include <QMutex>
8+
#include <QtSvg/QSvgRenderer>
89

910
#include "irenderedtarget.h"
1011

@@ -25,7 +26,6 @@ class RenderedTarget : public IRenderedTarget
2526

2627
public:
2728
RenderedTarget(QNanoQuickItem *parent = nullptr);
28-
~RenderedTarget();
2929

3030
Q_INVOKABLE void loadProperties() override;
3131
void loadCostume(libscratchcpp::Costume *costume) override;
@@ -48,13 +48,6 @@ class RenderedTarget : public IRenderedTarget
4848
qreal height() const override;
4949
void setHeight(qreal height) override;
5050

51-
double costumeWidth() const override;
52-
void setCostumeWidth(double width) override;
53-
54-
double costumeHeight() const override;
55-
void setCostumeHeight(double height) override;
56-
57-
unsigned char *svgBitmap() const override;
5851
QBuffer *bitmapBuffer() override;
5952
const QString &bitmapUniqueKey() const override;
6053

@@ -63,6 +56,9 @@ class RenderedTarget : public IRenderedTarget
6356

6457
bool mirrorHorizontally() const override;
6558

59+
bool isSvg() const override;
60+
void paintSvg(QNanoPainter *painter) override;
61+
6662
signals:
6763
void engineChanged();
6864
void stageModelChanged();
@@ -74,21 +70,20 @@ class RenderedTarget : public IRenderedTarget
7470
QNanoQuickItemPainter *createItemPainter() const override;
7571

7672
private:
73+
void doLoadCostume();
7774
void calculateSize(libscratchcpp::Target *target, double costumeWidth, double costumeHeight);
7875

7976
libscratchcpp::IEngine *m_engine = nullptr;
8077
libscratchcpp::Costume *m_costume = nullptr;
8178
StageModel *m_stageModel = nullptr;
8279
SpriteModel *m_spriteModel = nullptr;
83-
unsigned char *m_svgBitmap = nullptr;
80+
QSvgRenderer m_svgRenderer;
8481
QBuffer m_bitmapBuffer;
8582
QString m_bitmapUniqueKey;
8683
QMutex m_costumeMutex;
8784
QMutex mutex;
8885
bool m_imageChanged = false;
8986
bool m_visible = true;
90-
double m_width = 0;
91-
double m_height = 0;
9287
double m_x = 0;
9388
double m_y = 0;
9489
double m_z = 0;

ScratchCPPGui/targetpainter.cpp

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -20,27 +20,12 @@ TargetPainter::~TargetPainter()
2020
void TargetPainter::paint(QNanoPainter *painter)
2121
{
2222
m_target->lockCostume();
23-
unsigned char *svgBitmap = m_target->svgBitmap();
2423
double width = m_target->width();
2524
double height = m_target->height();
2625

27-
if (svgBitmap) {
28-
// TODO: Paint shapes from SVG directly instead of painting rasterized SVG
29-
for (int i = 0; i < width * height; ++i) {
30-
int pixelIndex = i * 4; // Each pixel has four values (R, G, B, A)
31-
32-
uchar red = svgBitmap[pixelIndex];
33-
uchar green = svgBitmap[pixelIndex + 1];
34-
uchar blue = svgBitmap[pixelIndex + 2];
35-
uchar alpha = svgBitmap[pixelIndex + 3];
36-
37-
int x = i % static_cast<int>(width);
38-
int y = i / static_cast<int>(width);
39-
40-
painter->setFillStyle(QNanoColor(red, green, blue, alpha));
41-
painter->fillRect(x, y, 1, 1);
42-
}
43-
} else {
26+
if (m_target->isSvg())
27+
m_target->paintSvg(painter);
28+
else {
4429
QNanoImage image = QNanoImage::fromCache(painter, m_target->bitmapBuffer(), m_target->bitmapUniqueKey());
4530
painter->drawImage(image, 0, 0, width, height);
4631
}

build/FindQt.cmake

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
find_package(Qt6 6.6 COMPONENTS Gui Qml Quick REQUIRED)
2-
set(QT_LIBS Qt6::Gui Qt6::Qml Qt6::Quick)
1+
find_package(Qt6 6.6 COMPONENTS Gui Qml Quick Svg REQUIRED)
2+
set(QT_LIBS Qt6::Gui Qt6::Qml Qt6::Quick Qt6::Svg)
33

44
if (SCRATCHCPPGUI_BUILD_UNIT_TESTS)
55
find_package(Qt6 6.6 COMPONENTS Test REQUIRED)

0 commit comments

Comments
 (0)