Skip to content

Commit 270ebd9

Browse files
committed
Add texture data methods to PenLayer
1 parent 13dd9a6 commit 270ebd9

File tree

5 files changed

+195
-1
lines changed

5 files changed

+195
-1
lines changed

src/ipenlayer.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#pragma once
44

55
#include <qnanoquickitem.h>
6+
#include <scratchcpp/rect.h>
67

78
namespace libscratchcpp
89
{
@@ -37,6 +38,9 @@ class IPenLayer : public QNanoQuickItem
3738
virtual void drawLine(const PenAttributes &penAttributes, double x0, double y0, double x1, double y1) = 0;
3839

3940
virtual QOpenGLFramebufferObject *framebufferObject() const = 0;
41+
virtual QRgb colorAtScratchPoint(double x, double y) const = 0;
42+
43+
virtual const libscratchcpp::Rect &getBounds() const = 0;
4044
};
4145

4246
} // namespace scratchcpprender

src/penlayer.cpp

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ void scratchcpprender::PenLayer::clear()
7676
m_glF->glEnable(GL_SCISSOR_TEST);
7777
m_fbo->release();
7878

79+
m_textureDirty = true;
80+
m_boundsDirty = true;
7981
update();
8082
}
8183

@@ -121,6 +123,8 @@ void scratchcpprender::PenLayer::drawLine(const PenAttributes &penAttributes, do
121123
painter.end();
122124
m_fbo->release();
123125

126+
m_textureDirty = true;
127+
m_boundsDirty = true;
124128
update();
125129
}
126130

@@ -129,6 +133,84 @@ QOpenGLFramebufferObject *PenLayer::framebufferObject() const
129133
return m_fbo.get();
130134
}
131135

136+
QRgb PenLayer::colorAtScratchPoint(double x, double y) const
137+
{
138+
if (m_textureDirty)
139+
const_cast<PenLayer *>(this)->updateTexture();
140+
141+
if (!m_texture.isValid())
142+
return qRgba(0, 0, 0, 0);
143+
144+
const double width = m_texture.width();
145+
const double height = m_texture.height();
146+
147+
// Translate the coordinates
148+
// TODO: Apply scale
149+
x = std::floor(x + width / 2.0);
150+
y = std::floor(-y + height / 2.0);
151+
152+
// If the point is outside the texture, return fully transparent color
153+
if ((x < 0 || x >= width) || (y < 0 || y >= height))
154+
return qRgba(0, 0, 0, 0);
155+
156+
GLubyte *data = m_textureManager.getTextureData(m_texture);
157+
const int index = (y * width + x) * 4; // RGBA channels
158+
Q_ASSERT(index >= 0 && index < width * height * 4);
159+
return qRgba(data[index], data[index + 1], data[index + 2], data[index + 3]);
160+
}
161+
162+
const libscratchcpp::Rect &PenLayer::getBounds() const
163+
{
164+
if (m_textureDirty)
165+
const_cast<PenLayer *>(this)->updateTexture();
166+
167+
if (m_boundsDirty) {
168+
if (!m_texture.isValid()) {
169+
m_bounds = libscratchcpp::Rect();
170+
return m_bounds;
171+
}
172+
173+
m_boundsDirty = false;
174+
double left = std::numeric_limits<double>::infinity();
175+
double top = -std::numeric_limits<double>::infinity();
176+
double right = -std::numeric_limits<double>::infinity();
177+
double bottom = std::numeric_limits<double>::infinity();
178+
const double width = m_texture.width();
179+
const double height = m_texture.height();
180+
const std::vector<QPoint> &points = m_textureManager.getTextureConvexHullPoints(m_texture);
181+
182+
if (points.empty()) {
183+
m_bounds = libscratchcpp::Rect();
184+
return m_bounds;
185+
}
186+
187+
for (const QPointF &point : points) {
188+
// TODO: Apply scale
189+
double x = point.x() - width / 2;
190+
double y = -point.y() + height / 2;
191+
192+
if (x < left)
193+
left = x;
194+
195+
if (x > right)
196+
right = x;
197+
198+
if (y > top)
199+
top = y;
200+
201+
if (y < bottom)
202+
bottom = y;
203+
}
204+
205+
m_bounds.setLeft(left);
206+
m_bounds.setTop(top);
207+
m_bounds.setRight(right + 1);
208+
m_bounds.setBottom(bottom - 1);
209+
}
210+
211+
return m_bounds;
212+
}
213+
132214
IPenLayer *PenLayer::getProjectPenLayer(libscratchcpp::IEngine *engine)
133215
{
134216
auto it = m_projectPenLayers.find(engine);
@@ -148,3 +230,21 @@ QNanoQuickItemPainter *PenLayer::createItemPainter() const
148230
{
149231
return new PenLayerPainter;
150232
}
233+
234+
void PenLayer::updateTexture()
235+
{
236+
if (!m_fbo)
237+
return;
238+
239+
m_textureDirty = false;
240+
m_textureManager.removeTexture(m_texture);
241+
242+
if (!m_resolvedFbo || m_resolvedFbo->size() != m_fbo->size()) {
243+
QOpenGLFramebufferObjectFormat format;
244+
format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
245+
m_resolvedFbo = std::make_unique<QOpenGLFramebufferObject>(m_fbo->size(), format);
246+
}
247+
248+
QOpenGLFramebufferObject::blitFramebuffer(m_resolvedFbo.get(), m_fbo.get());
249+
m_texture = Texture(m_resolvedFbo->texture(), m_resolvedFbo->size());
250+
}

src/penlayer.h

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,16 @@
22

33
#pragma once
44

5-
#include <ipenlayer.h>
65
#include <QOpenGLFramebufferObject>
76
#include <QOpenGLPaintDevice>
87
#include <QOpenGLFunctions>
98
#include <QPainter>
109
#include <scratchcpp/iengine.h>
1110

11+
#include "ipenlayer.h"
12+
#include "texture.h"
13+
#include "cputexturemanager.h"
14+
1215
namespace scratchcpprender
1316
{
1417

@@ -33,6 +36,9 @@ class PenLayer : public IPenLayer
3336
void drawLine(const PenAttributes &penAttributes, double x0, double y0, double x1, double y1) override;
3437

3538
QOpenGLFramebufferObject *framebufferObject() const override;
39+
QRgb colorAtScratchPoint(double x, double y) const override;
40+
41+
const libscratchcpp::Rect &getBounds() const override;
3642

3743
static IPenLayer *getProjectPenLayer(libscratchcpp::IEngine *engine);
3844
static void addPenLayer(libscratchcpp::IEngine *engine, IPenLayer *penLayer); // for tests
@@ -44,13 +50,21 @@ class PenLayer : public IPenLayer
4450
QNanoQuickItemPainter *createItemPainter() const override;
4551

4652
private:
53+
void updateTexture();
54+
4755
static std::unordered_map<libscratchcpp::IEngine *, IPenLayer *> m_projectPenLayers;
4856
bool m_antialiasingEnabled = true;
4957
libscratchcpp::IEngine *m_engine = nullptr;
5058
std::unique_ptr<QOpenGLFramebufferObject> m_fbo;
59+
std::unique_ptr<QOpenGLFramebufferObject> m_resolvedFbo;
5160
std::unique_ptr<QOpenGLPaintDevice> m_paintDevice;
5261
QOpenGLFramebufferObjectFormat m_fboFormat;
5362
std::unique_ptr<QOpenGLFunctions> m_glF;
63+
Texture m_texture;
64+
bool m_textureDirty = true;
65+
mutable CpuTextureManager m_textureManager;
66+
mutable bool m_boundsDirty = true;
67+
mutable libscratchcpp::Rect m_bounds;
5468
};
5569

5670
} // namespace scratchcpprender

test/mocks/penlayermock.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ class PenLayerMock : public IPenLayer
2323
MOCK_METHOD(void, drawLine, (const PenAttributes &, double, double, double, double), (override));
2424

2525
MOCK_METHOD(QOpenGLFramebufferObject *, framebufferObject, (), (const, override));
26+
MOCK_METHOD(QRgb, colorAtScratchPoint, (double, double), (const, override));
27+
28+
MOCK_METHOD(const libscratchcpp::Rect &, getBounds, (), (const, override));
2629

2730
MOCK_METHOD(QNanoQuickItemPainter *, createItemPainter, (), (const, override));
2831
};

test/penlayer/penlayer_test.cpp

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,3 +257,76 @@ TEST_F(PenLayerTest, DrawLine)
257257
buffer.open(QFile::ReadOnly);
258258
ASSERT_EQ(ref.readAll(), buffer.readAll());
259259
}
260+
261+
TEST_F(PenLayerTest, TextureData)
262+
{
263+
PenLayer penLayer;
264+
penLayer.setAntialiasingEnabled(false);
265+
EngineMock engine;
266+
EXPECT_CALL(engine, stageWidth()).WillRepeatedly(Return(6));
267+
EXPECT_CALL(engine, stageHeight()).WillRepeatedly(Return(4));
268+
penLayer.setEngine(&engine);
269+
270+
PenAttributes attr;
271+
attr.color = QColor(255, 0, 0);
272+
attr.diameter = 1;
273+
penLayer.drawLine(attr, -3, 2, 3, -2);
274+
ASSERT_EQ(penLayer.colorAtScratchPoint(-3, 2), qRgb(255, 0, 0));
275+
ASSERT_EQ(penLayer.colorAtScratchPoint(0, 2), qRgba(0, 0, 0, 0));
276+
ASSERT_EQ(penLayer.colorAtScratchPoint(-1, 1), qRgb(255, 0, 0));
277+
278+
Rect bounds = penLayer.getBounds();
279+
ASSERT_EQ(bounds.left(), -3);
280+
ASSERT_EQ(bounds.top(), 2);
281+
ASSERT_EQ(bounds.right(), 3);
282+
ASSERT_EQ(bounds.bottom(), -2);
283+
284+
attr.color = QColor(0, 128, 0, 128);
285+
attr.diameter = 2;
286+
penLayer.drawLine(attr, -3, -2, 3, 2);
287+
ASSERT_EQ(penLayer.colorAtScratchPoint(-3, 2), qRgb(255, 0, 0));
288+
ASSERT_EQ(penLayer.colorAtScratchPoint(0, 2), qRgba(0, 64, 0, 128));
289+
ASSERT_EQ(penLayer.colorAtScratchPoint(-1, 1), qRgb(127, 64, 0));
290+
291+
bounds = penLayer.getBounds();
292+
ASSERT_EQ(bounds.left(), -3);
293+
ASSERT_EQ(bounds.top(), 2);
294+
ASSERT_EQ(bounds.right(), 3);
295+
ASSERT_EQ(bounds.bottom(), -2);
296+
297+
penLayer.clear();
298+
ASSERT_EQ(penLayer.colorAtScratchPoint(-3, 2), 0);
299+
ASSERT_EQ(penLayer.colorAtScratchPoint(0, 2), 0);
300+
ASSERT_EQ(penLayer.colorAtScratchPoint(-1, 1), 0);
301+
302+
bounds = penLayer.getBounds();
303+
ASSERT_EQ(bounds.left(), 0);
304+
ASSERT_EQ(bounds.top(), 0);
305+
ASSERT_EQ(bounds.right(), 0);
306+
ASSERT_EQ(bounds.bottom(), 0);
307+
308+
attr.color = QColor(0, 255, 0, 255);
309+
attr.diameter = 1;
310+
penLayer.drawLine(attr, 0, -1, 1, 1);
311+
ASSERT_EQ(penLayer.colorAtScratchPoint(0, 1), qRgb(0, 255, 0));
312+
ASSERT_EQ(penLayer.colorAtScratchPoint(0, 0), qRgb(0, 255, 0));
313+
ASSERT_EQ(penLayer.colorAtScratchPoint(-3, 1), qRgba(0, 0, 0, 0));
314+
315+
bounds = penLayer.getBounds();
316+
ASSERT_EQ(bounds.left(), 0);
317+
ASSERT_EQ(bounds.top(), 1);
318+
ASSERT_EQ(bounds.right(), 1);
319+
ASSERT_EQ(bounds.bottom(), -1);
320+
321+
attr.diameter = 2;
322+
penLayer.drawPoint(attr, -2, 0);
323+
ASSERT_EQ(penLayer.colorAtScratchPoint(0, 1), qRgb(0, 255, 0));
324+
ASSERT_EQ(penLayer.colorAtScratchPoint(0, 0), qRgb(0, 255, 0));
325+
ASSERT_EQ(penLayer.colorAtScratchPoint(-3, 1), qRgb(0, 255, 0));
326+
327+
bounds = penLayer.getBounds();
328+
ASSERT_EQ(bounds.left(), -3);
329+
ASSERT_EQ(bounds.top(), 1);
330+
ASSERT_EQ(bounds.right(), 1);
331+
ASSERT_EQ(bounds.bottom(), -1);
332+
}

0 commit comments

Comments
 (0)