From 7221fecff3beace917ac8767d6e79a1e01c0effa Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Sun, 21 Jan 2024 15:37:06 +0100 Subject: [PATCH 01/11] Add PenAttributes struct --- src/CMakeLists.txt | 1 + src/penattributes.h | 16 ++++++++++++++++ test/CMakeLists.txt | 1 + test/penattributes/CMakeLists.txt | 14 ++++++++++++++ test/penattributes/penattributes_test.cpp | 15 +++++++++++++++ 5 files changed, 47 insertions(+) create mode 100644 src/penattributes.h create mode 100644 test/penattributes/CMakeLists.txt create mode 100644 test/penattributes/penattributes_test.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index bb1993a..dd491d3 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -49,6 +49,7 @@ qt_add_qml_module(scratchcpp-render mouseeventhandler.h keyeventhandler.cpp keyeventhandler.h + penattributes.h ) list(APPEND QML_IMPORT_PATH ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) diff --git a/src/penattributes.h b/src/penattributes.h new file mode 100644 index 0000000..900991f --- /dev/null +++ b/src/penattributes.h @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later + +#pragma once + +#include + +namespace scratchcpprender +{ + +struct PenAttributes +{ + QColor color = QColor(0, 0, 255); + double diameter = 1; +}; + +} // namespace scratchcpprender diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 7be095e..005e28c 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -31,3 +31,4 @@ add_subdirectory(scenemousearea) add_subdirectory(monitor_models) add_subdirectory(texture) add_subdirectory(skins) +add_subdirectory(penattributes) diff --git a/test/penattributes/CMakeLists.txt b/test/penattributes/CMakeLists.txt new file mode 100644 index 0000000..520ad62 --- /dev/null +++ b/test/penattributes/CMakeLists.txt @@ -0,0 +1,14 @@ +add_executable( + penattributes_test + penattributes_test.cpp +) + +target_link_libraries( + penattributes_test + GTest::gtest_main + scratchcpp-render + qnanopainter +) + +add_test(penattributes_test) +gtest_discover_tests(penattributes_test) diff --git a/test/penattributes/penattributes_test.cpp b/test/penattributes/penattributes_test.cpp new file mode 100644 index 0000000..a6dc0b9 --- /dev/null +++ b/test/penattributes/penattributes_test.cpp @@ -0,0 +1,15 @@ +#include + +#include "../common.h" + +using namespace scratchcpprender; + +TEST(PenAttributesTest, DefaultPenAttributes) +{ + PenAttributes attr; + ASSERT_EQ(attr.color.redF(), 0); + ASSERT_EQ(attr.color.greenF(), 0); + ASSERT_EQ(attr.color.blueF(), 1); + ASSERT_EQ(attr.color.alphaF(), 1); + ASSERT_EQ(attr.diameter, 1); +} From 9b05d1a3d4fa2b2abac5d63501ceeec4d8d92a6c Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Sun, 21 Jan 2024 19:30:15 +0100 Subject: [PATCH 02/11] Add PenLayer and PenLayerPainter classes --- src/CMakeLists.txt | 5 + src/ipenlayer.h | 39 +++ src/penlayer.cpp | 132 ++++++++++ src/penlayer.h | 49 ++++ src/penlayerpainter.cpp | 45 ++++ src/penlayerpainter.h | 25 ++ test/CMakeLists.txt | 2 + test/lines.png | Bin 0 -> 7090 bytes test/mocks/penlayermock.h | 27 ++ test/penlayer/CMakeLists.txt | 16 ++ test/penlayer/penlayer_test.cpp | 248 ++++++++++++++++++ test/penlayerpainter/CMakeLists.txt | 17 ++ test/penlayerpainter/penlayerpainter_test.cpp | 81 ++++++ test/points.png | Bin 0 -> 2000 bytes 14 files changed, 686 insertions(+) create mode 100644 src/ipenlayer.h create mode 100644 src/penlayer.cpp create mode 100644 src/penlayer.h create mode 100644 src/penlayerpainter.cpp create mode 100644 src/penlayerpainter.h create mode 100644 test/lines.png create mode 100644 test/mocks/penlayermock.h create mode 100644 test/penlayer/CMakeLists.txt create mode 100644 test/penlayer/penlayer_test.cpp create mode 100644 test/penlayerpainter/CMakeLists.txt create mode 100644 test/penlayerpainter/penlayerpainter_test.cpp create mode 100644 test/points.png diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index dd491d3..d2b4c85 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -49,6 +49,11 @@ qt_add_qml_module(scratchcpp-render mouseeventhandler.h keyeventhandler.cpp keyeventhandler.h + ipenlayer.h + penlayer.cpp + penlayer.h + penlayerpainter.cpp + penlayerpainter.h penattributes.h ) diff --git a/src/ipenlayer.h b/src/ipenlayer.h new file mode 100644 index 0000000..ba94b0c --- /dev/null +++ b/src/ipenlayer.h @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later + +#pragma once + +#include + +namespace libscratchcpp +{ + +class IEngine; + +} + +namespace scratchcpprender +{ + +struct PenAttributes; + +class IPenLayer : public QNanoQuickItem +{ + public: + IPenLayer(QNanoQuickItem *parent = nullptr) : + QNanoQuickItem(parent) + { + } + + virtual ~IPenLayer() { } + + virtual libscratchcpp::IEngine *engine() const = 0; + virtual void setEngine(libscratchcpp::IEngine *newEngine) = 0; + + virtual void clear() = 0; + virtual void drawPoint(const PenAttributes &penAttributes, double x, double y) = 0; + virtual void drawLine(const PenAttributes &penAttributes, double x0, double y0, double x1, double y1) = 0; + + virtual QOpenGLFramebufferObject *framebufferObject() const = 0; +}; + +} // namespace scratchcpprender diff --git a/src/penlayer.cpp b/src/penlayer.cpp new file mode 100644 index 0000000..5f4aa6f --- /dev/null +++ b/src/penlayer.cpp @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include "penlayer.h" +#include "penlayerpainter.h" +#include "penattributes.h" + +using namespace scratchcpprender; + +std::unordered_map PenLayer::m_projectPenLayers; + +PenLayer::PenLayer(QNanoQuickItem *parent) : + IPenLayer(parent) +{ + m_fboFormat.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil); + m_fboFormat.setSamples(4); +} + +PenLayer::~PenLayer() +{ + if (m_engine) + m_projectPenLayers.erase(m_engine); + + if (m_painter && m_painter->isActive()) + m_painter->end(); +} + +libscratchcpp::IEngine *PenLayer::engine() const +{ + return m_engine; +} + +void PenLayer::setEngine(libscratchcpp::IEngine *newEngine) +{ + if (m_engine == newEngine) + return; + + if (m_engine) + m_projectPenLayers.erase(m_engine); + + m_engine = newEngine; + + if (m_engine) { + m_projectPenLayers[m_engine] = this; + m_fbo = std::make_unique(m_engine->stageWidth(), m_engine->stageHeight(), m_fboFormat); + Q_ASSERT(m_fbo->isValid()); + + if (m_painter && m_painter->isActive()) + m_painter->end(); + + m_paintDevice = std::make_unique(m_fbo->size()); + m_painter = std::make_unique(m_paintDevice.get()); + clear(); + } + + emit engineChanged(); +} + +void scratchcpprender::PenLayer::clear() +{ + if (!m_fbo) + return; + + m_fbo->bind(); + glClearColor(0.0f, 0.0f, 0.0f, 0.0f); + glClear(GL_COLOR_BUFFER_BIT); + m_fbo->release(); + + update(); +} + +void scratchcpprender::PenLayer::drawPoint(const PenAttributes &penAttributes, double x, double y) +{ + drawLine(penAttributes, x, y, x, y); +} + +void scratchcpprender::PenLayer::drawLine(const PenAttributes &penAttributes, double x0, double y0, double x1, double y1) +{ + if (!m_fbo || !m_painter || !m_engine) + return; + + // Begin painting + m_fbo->bind(); + m_painter->beginNativePainting(); + m_painter->setRenderHint(QPainter::Antialiasing); + m_painter->setRenderHint(QPainter::SmoothPixmapTransform, false); + + // Translate to Scratch coordinate system + double stageWidthHalf = m_engine->stageWidth() / 2; + double stageHeightHalf = m_engine->stageHeight() / 2; + x0 += stageWidthHalf; + y0 = stageHeightHalf - y0; + x1 += stageWidthHalf; + y1 = stageHeightHalf - y1; + + // Set pen attributes + QPen pen(penAttributes.color); + pen.setWidthF(penAttributes.diameter); + pen.setCapStyle(Qt::RoundCap); + m_painter->setPen(pen); + + // If the start and end coordinates are the same, draw a point, otherwise draw a line + if (x0 == x1 && y0 == y1) + m_painter->drawPoint(x0, y0); + else + m_painter->drawLine(x0, y0, x1, y1); + + // End painting + m_painter->endNativePainting(); + m_fbo->release(); + + update(); +} + +QOpenGLFramebufferObject *PenLayer::framebufferObject() const +{ + return m_fbo.get(); +} + +IPenLayer *PenLayer::getProjectPenLayer(libscratchcpp::IEngine *engine) +{ + auto it = m_projectPenLayers.find(engine); + + if (it != m_projectPenLayers.cend()) + return it->second; + + return nullptr; +} + +QNanoQuickItemPainter *PenLayer::createItemPainter() const +{ + return new PenLayerPainter; +} diff --git a/src/penlayer.h b/src/penlayer.h new file mode 100644 index 0000000..6c83ac3 --- /dev/null +++ b/src/penlayer.h @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later + +#pragma once + +#include +#include +#include +#include + +namespace scratchcpprender +{ + +class PenLayer : public IPenLayer +{ + Q_OBJECT + QML_ELEMENT + Q_PROPERTY(libscratchcpp::IEngine *engine READ engine WRITE setEngine NOTIFY engineChanged) + + public: + PenLayer(QNanoQuickItem *parent = nullptr); + ~PenLayer(); + + libscratchcpp::IEngine *engine() const override; + void setEngine(libscratchcpp::IEngine *newEngine) override; + + void clear() override; + void drawPoint(const PenAttributes &penAttributes, double x, double y) override; + void drawLine(const PenAttributes &penAttributes, double x0, double y0, double x1, double y1) override; + + QOpenGLFramebufferObject *framebufferObject() const override; + + static IPenLayer *getProjectPenLayer(libscratchcpp::IEngine *engine); + + signals: + void engineChanged(); + + protected: + QNanoQuickItemPainter *createItemPainter() const override; + + private: + static std::unordered_map m_projectPenLayers; + libscratchcpp::IEngine *m_engine = nullptr; + std::unique_ptr m_fbo; + std::unique_ptr m_paintDevice; + std::unique_ptr m_painter; + QOpenGLFramebufferObjectFormat m_fboFormat; +}; + +} // namespace scratchcpprender diff --git a/src/penlayerpainter.cpp b/src/penlayerpainter.cpp new file mode 100644 index 0000000..1ef6ea0 --- /dev/null +++ b/src/penlayerpainter.cpp @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include "penlayerpainter.h" +#include "penlayer.h" + +using namespace scratchcpprender; + +PenLayerPainter::PenLayerPainter(QOpenGLFramebufferObject *fbo) +{ + m_targetFbo = fbo; +} + +void PenLayerPainter::paint(QNanoPainter *painter) +{ + if (QThread::currentThread() != qApp->thread()) { + 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 " + "application object."); + } + + QOpenGLContext *context = QOpenGLContext::currentContext(); + Q_ASSERT(context); + + if (!context || !m_fbo) + return; + + // Custom FBO - only used for testing + QOpenGLFramebufferObject *targetFbo = m_targetFbo ? m_targetFbo : framebufferObject(); + + QOpenGLFramebufferObjectFormat format; + format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil); + + // Blit the FBO to a temporary FBO first (multisampled FBOs can only be blitted to FBOs with the same size) + QOpenGLFramebufferObject tmpFbo(m_fbo->size(), format); + QOpenGLFramebufferObject::blitFramebuffer(&tmpFbo, m_fbo); + QOpenGLFramebufferObject::blitFramebuffer(targetFbo, &tmpFbo); +} + +void PenLayerPainter::synchronize(QNanoQuickItem *item) +{ + IPenLayer *penLayer = dynamic_cast(item); + Q_ASSERT(penLayer); + + if (penLayer) + m_fbo = penLayer->framebufferObject(); +} diff --git a/src/penlayerpainter.h b/src/penlayerpainter.h new file mode 100644 index 0000000..f6d6444 --- /dev/null +++ b/src/penlayerpainter.h @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later + +#pragma once + +#include + +#include "texture.h" + +namespace scratchcpprender +{ + +class PenLayerPainter : public QNanoQuickItemPainter +{ + public: + PenLayerPainter(QOpenGLFramebufferObject *fbo = nullptr); + + void paint(QNanoPainter *painter) override; + void synchronize(QNanoQuickItem *item) override; + + private: + QOpenGLFramebufferObject *m_targetFbo = nullptr; + QOpenGLFramebufferObject *m_fbo = nullptr; +}; + +} // namespace scratchcpprender diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 005e28c..0dbcc55 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -32,3 +32,5 @@ add_subdirectory(monitor_models) add_subdirectory(texture) add_subdirectory(skins) add_subdirectory(penattributes) +add_subdirectory(penlayer) +add_subdirectory(penlayerpainter) diff --git a/test/lines.png b/test/lines.png new file mode 100644 index 0000000000000000000000000000000000000000..321509f0c3ce4a3cf10ddb5be13bf5eff2665e84 GIT binary patch literal 7090 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 +#include +#include + +using namespace scratchcpprender; + +namespace scratchcpprender +{ + +class PenLayerMock : public IPenLayer +{ + public: + MOCK_METHOD(libscratchcpp::IEngine *, engine, (), (const, override)); + MOCK_METHOD(void, setEngine, (libscratchcpp::IEngine *), (override)); + + MOCK_METHOD(void, clear, (), (override)); + MOCK_METHOD(void, drawPoint, (const PenAttributes &, double, double), (override)); + MOCK_METHOD(void, drawLine, (const PenAttributes &, double, double, double, double), (override)); + + MOCK_METHOD(QOpenGLFramebufferObject *, framebufferObject, (), (const, override)); + + MOCK_METHOD(QNanoQuickItemPainter *, createItemPainter, (), (const, override)); +}; + +} // namespace scratchcpprender diff --git a/test/penlayer/CMakeLists.txt b/test/penlayer/CMakeLists.txt new file mode 100644 index 0000000..5ad79f2 --- /dev/null +++ b/test/penlayer/CMakeLists.txt @@ -0,0 +1,16 @@ +add_executable( + penlayer_test + penlayer_test.cpp +) + +target_link_libraries( + penlayer_test + GTest::gtest_main + GTest::gmock_main + scratchcpp-render + scratchcpprender_mocks + ${QT_LIBS} +) + +add_test(penlayer_test) +gtest_discover_tests(penlayer_test) diff --git a/test/penlayer/penlayer_test.cpp b/test/penlayer/penlayer_test.cpp new file mode 100644 index 0000000..b0ef3c1 --- /dev/null +++ b/test/penlayer/penlayer_test.cpp @@ -0,0 +1,248 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../common.h" + +using namespace scratchcpprender; +using namespace libscratchcpp; + +using ::testing::Return; + +class PenLayerTest : public testing::Test +{ + public: + void SetUp() override + { + m_context.create(); + ASSERT_TRUE(m_context.isValid()); + + m_surface.setFormat(m_context.format()); + m_surface.create(); + Q_ASSERT(m_surface.isValid()); + m_context.makeCurrent(&m_surface); + } + + void TearDown() override + { + ASSERT_EQ(m_context.surface(), &m_surface); + m_context.doneCurrent(); + } + + QOpenGLContext m_context; + QOffscreenSurface m_surface; +}; + +TEST_F(PenLayerTest, Constructors) +{ + PenLayer penLayer1; + PenLayer penLayer2(&penLayer1); + ASSERT_EQ(penLayer2.parent(), &penLayer1); + ASSERT_EQ(penLayer2.parentItem(), &penLayer1); +} + +TEST_F(PenLayerTest, Engine) +{ + PenLayer penLayer; + ASSERT_EQ(penLayer.engine(), nullptr); + + EngineMock engine1, engine2; + EXPECT_CALL(engine1, stageWidth()).WillOnce(Return(480)); + EXPECT_CALL(engine1, stageHeight()).WillOnce(Return(360)); + penLayer.setEngine(&engine1); + ASSERT_EQ(penLayer.engine(), &engine1); + + EXPECT_CALL(engine2, stageWidth()).WillOnce(Return(500)); + EXPECT_CALL(engine2, stageHeight()).WillOnce(Return(400)); + penLayer.setEngine(&engine2); + ASSERT_EQ(penLayer.engine(), &engine2); + + penLayer.setEngine(nullptr); + ASSERT_EQ(penLayer.engine(), nullptr); +} + +TEST_F(PenLayerTest, FramebufferObject) +{ + PenLayer penLayer; + EngineMock engine1, engine2; + EXPECT_CALL(engine1, stageWidth()).WillOnce(Return(480)); + EXPECT_CALL(engine1, stageHeight()).WillOnce(Return(360)); + penLayer.setEngine(&engine1); + + QOpenGLFramebufferObject *fbo = penLayer.framebufferObject(); + ASSERT_EQ(fbo->width(), 480); + ASSERT_EQ(fbo->height(), 360); + ASSERT_EQ(fbo->format().attachment(), QOpenGLFramebufferObject::CombinedDepthStencil); + ASSERT_EQ(fbo->format().samples(), 4); + + EXPECT_CALL(engine2, stageWidth()).WillOnce(Return(500)); + EXPECT_CALL(engine2, stageHeight()).WillOnce(Return(400)); + penLayer.setEngine(&engine2); + + fbo = penLayer.framebufferObject(); + ASSERT_EQ(fbo->width(), 500); + ASSERT_EQ(fbo->height(), 400); + ASSERT_EQ(fbo->format().attachment(), QOpenGLFramebufferObject::CombinedDepthStencil); + ASSERT_EQ(fbo->format().samples(), 4); +} + +TEST_F(PenLayerTest, GetProjectPenLayer) +{ + PenLayer penLayer; + ASSERT_EQ(penLayer.getProjectPenLayer(nullptr), nullptr); + + EngineMock engine1, engine2; + ASSERT_EQ(penLayer.getProjectPenLayer(&engine1), nullptr); + ASSERT_EQ(penLayer.getProjectPenLayer(&engine2), nullptr); + + EXPECT_CALL(engine1, stageWidth()).WillOnce(Return(1)); + EXPECT_CALL(engine1, stageHeight()).WillOnce(Return(1)); + penLayer.setEngine(&engine1); + ASSERT_EQ(penLayer.getProjectPenLayer(&engine1), &penLayer); + ASSERT_EQ(penLayer.getProjectPenLayer(&engine2), nullptr); + + EXPECT_CALL(engine2, stageWidth()).WillOnce(Return(1)); + EXPECT_CALL(engine2, stageHeight()).WillOnce(Return(1)); + penLayer.setEngine(&engine2); + ASSERT_EQ(penLayer.getProjectPenLayer(&engine1), nullptr); + ASSERT_EQ(penLayer.getProjectPenLayer(&engine2), &penLayer); + + penLayer.setEngine(nullptr); + ASSERT_EQ(penLayer.getProjectPenLayer(&engine1), nullptr); + ASSERT_EQ(penLayer.getProjectPenLayer(&engine2), nullptr); +} + +TEST_F(PenLayerTest, Clear) +{ + PenLayer penLayer; + EngineMock engine; + EXPECT_CALL(engine, stageWidth()).WillOnce(Return(480)); + EXPECT_CALL(engine, stageHeight()).WillOnce(Return(360)); + penLayer.setEngine(&engine); + + QOpenGLExtraFunctions glF(&m_context); + glF.initializeOpenGLFunctions(); + + QOpenGLFramebufferObject *fbo = penLayer.framebufferObject(); + QImage image1 = fbo->toImage(); + + // The initial texture must contain only fully transparent pixels + for (int y = 0; y < image1.height(); y++) { + for (int x = 0; x < image1.width(); x++) + ASSERT_EQ(QColor::fromRgba(image1.pixel(x, y)).alphaF(), 0); + } + + // Paint something first to test clear() + fbo->bind(); + QNanoPainter painter; + painter.beginFrame(fbo->width(), fbo->height()); + painter.setStrokeStyle(QNanoColor(0, 0, 0)); + painter.moveTo(0, 0); + painter.lineTo(fbo->width(), fbo->height()); + painter.stroke(); + painter.endFrame(); + fbo->release(); + + penLayer.clear(); + QImage image2 = fbo->toImage(); + + // The image must contain only fully transparent pixels + for (int y = 0; y < image2.height(); y++) { + for (int x = 0; x < image2.width(); x++) + ASSERT_EQ(QColor::fromRgba(image2.pixel(x, y)).alphaF(), 0); + } +} + +TEST_F(PenLayerTest, DrawPoint) +{ + PenLayer penLayer; + EngineMock engine; + EXPECT_CALL(engine, stageWidth()).WillOnce(Return(480)); + EXPECT_CALL(engine, stageHeight()).WillOnce(Return(360)); + penLayer.setEngine(&engine); + + PenAttributes attr; + attr.color = QColor(255, 0, 0); + attr.diameter = 3; + + EXPECT_CALL(engine, stageWidth()).Times(3).WillRepeatedly(Return(480)); + EXPECT_CALL(engine, stageHeight()).Times(3).WillRepeatedly(Return(360)); + penLayer.drawPoint(attr, 63, 164); + penLayer.drawPoint(attr, -56, 93); + penLayer.drawPoint(attr, 130, 77); + + attr.color = QColor(0, 128, 0, 128); + attr.diameter = 10; + + EXPECT_CALL(engine, stageWidth()).Times(3).WillRepeatedly(Return(480)); + EXPECT_CALL(engine, stageHeight()).Times(3).WillRepeatedly(Return(360)); + penLayer.drawPoint(attr, 152, -158); + penLayer.drawPoint(attr, -228, 145); + penLayer.drawPoint(attr, -100, 139); + + attr.color = QColor(255, 50, 200, 185); + attr.diameter = 25.6; + + EXPECT_CALL(engine, stageWidth()).Times(3).WillRepeatedly(Return(480)); + EXPECT_CALL(engine, stageHeight()).Times(3).WillRepeatedly(Return(360)); + penLayer.drawPoint(attr, -11, 179); + penLayer.drawPoint(attr, 90, -48); + penLayer.drawPoint(attr, -54, 21); + + QOpenGLFramebufferObject *fbo = penLayer.framebufferObject(); + QImage image = fbo->toImage(); + QBuffer buffer; + image.save(&buffer, "png"); + QFile ref("points.png"); + ref.open(QFile::ReadOnly); + buffer.open(QFile::ReadOnly); + ASSERT_EQ(ref.readAll(), buffer.readAll()); +} + +TEST_F(PenLayerTest, DrawLine) +{ + PenLayer penLayer; + EngineMock engine; + EXPECT_CALL(engine, stageWidth()).WillOnce(Return(480)); + EXPECT_CALL(engine, stageHeight()).WillOnce(Return(360)); + penLayer.setEngine(&engine); + + PenAttributes attr; + attr.color = QColor(255, 0, 0); + attr.diameter = 3; + + EXPECT_CALL(engine, stageWidth()).Times(2).WillRepeatedly(Return(480)); + EXPECT_CALL(engine, stageHeight()).Times(2).WillRepeatedly(Return(360)); + penLayer.drawLine(attr, 63, 164, -56, 93); + penLayer.drawLine(attr, 130, 77, 125, -22); + + attr.color = QColor(0, 128, 0, 128); + attr.diameter = 10; + + EXPECT_CALL(engine, stageWidth()).Times(2).WillRepeatedly(Return(480)); + EXPECT_CALL(engine, stageHeight()).Times(2).WillRepeatedly(Return(360)); + penLayer.drawLine(attr, 152, -158, -228, 145); + penLayer.drawLine(attr, -100, 139, 20, 72); + + attr.color = QColor(255, 50, 200, 185); + attr.diameter = 25.6; + + EXPECT_CALL(engine, stageWidth()).Times(2).WillRepeatedly(Return(480)); + EXPECT_CALL(engine, stageHeight()).Times(2).WillRepeatedly(Return(360)); + penLayer.drawLine(attr, -11, 179, 90, -48); + penLayer.drawLine(attr, -54, 21, 88, -6); + + QOpenGLFramebufferObject *fbo = penLayer.framebufferObject(); + QImage image = fbo->toImage(); + QBuffer buffer; + image.save(&buffer, "png"); + QFile ref("lines.png"); + ref.open(QFile::ReadOnly); + buffer.open(QFile::ReadOnly); + ASSERT_EQ(ref.readAll(), buffer.readAll()); +} diff --git a/test/penlayerpainter/CMakeLists.txt b/test/penlayerpainter/CMakeLists.txt new file mode 100644 index 0000000..7de3cd4 --- /dev/null +++ b/test/penlayerpainter/CMakeLists.txt @@ -0,0 +1,17 @@ +add_executable( + penlayerpainter_test + penlayerpainter_test.cpp +) + +target_link_libraries( + penlayerpainter_test + GTest::gtest_main + GTest::gmock_main + scratchcpp-render + scratchcpprender_mocks + ${QT_LIBS} + qnanopainter +) + +add_test(penlayerpainter_test) +gtest_discover_tests(penlayerpainter_test) diff --git a/test/penlayerpainter/penlayerpainter_test.cpp b/test/penlayerpainter/penlayerpainter_test.cpp new file mode 100644 index 0000000..eae84a8 --- /dev/null +++ b/test/penlayerpainter/penlayerpainter_test.cpp @@ -0,0 +1,81 @@ +#include +#include + +#include "../common.h" + +using namespace scratchcpprender; + +using ::testing::Return; +using ::testing::ReturnRef; + +class PenLayerPainterTest : public testing::Test +{ + public: + void createContextAndSurface(QOpenGLContext *context, QOffscreenSurface *surface) + { + QSurfaceFormat surfaceFormat; + surfaceFormat.setMajorVersion(4); + surfaceFormat.setMinorVersion(3); + + context->setFormat(surfaceFormat); + context->create(); + ASSERT_TRUE(context->isValid()); + + surface->setFormat(surfaceFormat); + surface->create(); + ASSERT_TRUE(surface->isValid()); + + context->makeCurrent(surface); + ASSERT_EQ(QOpenGLContext::currentContext(), context); + } +}; + +TEST_F(PenLayerPainterTest, Paint) +{ + QOpenGLContext context; + QOffscreenSurface surface; + createContextAndSurface(&context, &surface); + + QOpenGLFramebufferObjectFormat format; + format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil); + + // Begin painting reference + QNanoPainter refPainter; + QOpenGLFramebufferObject refFbo(40, 60, format); + refFbo.bind(); + refPainter.beginFrame(refFbo.width(), refFbo.height()); + + // Paint reference + refPainter.setAntialias(0); + refPainter.setStrokeStyle(QNanoColor(255, 0, 0, 128)); + refPainter.ellipse(refFbo.width() / 2, refFbo.height() / 2, refFbo.width() / 2, refFbo.height() / 2); + refPainter.stroke(); + refPainter.endFrame(); + + // Begin painting + QNanoPainter painter; + QOpenGLFramebufferObject fbo(40, 60, format); + fbo.bind(); + painter.beginFrame(fbo.width(), fbo.height()); + + // Create pen layer painter + PenLayerPainter penLayerPainter(&fbo); + PenLayerMock penLayer; + + EXPECT_CALL(penLayer, framebufferObject()).WillOnce(Return(&refFbo)); + penLayerPainter.synchronize(&penLayer); + + // Paint + Texture texture(refFbo.texture(), refFbo.size()); + penLayerPainter.paint(&painter); + painter.endFrame(); + + // Compare resulting images + ASSERT_EQ(fbo.toImage(), refFbo.toImage()); + + // Release + fbo.release(); + refFbo.release(); + + context.doneCurrent(); +} diff --git a/test/points.png b/test/points.png new file mode 100644 index 0000000000000000000000000000000000000000..228d1f1c8a2a3b5a68aeb2a920dd3fbf85d030d3 GIT binary patch literal 2000 zcmd5-do+}37=OQ+VFnW>mQmBt3~kCXwpmnEm@w{Lh2*O?VvkEy(oBq`rcEST6Pna< z*=U8@lv`got8$q%t76EdBx^*NaZC1_+V-41r_P?Ur$6>R=RNQDyyrQ;=kj~L-+R*2 z-Ep4k0#yLOJhl^SBLGl0y7v;4(VJw;r3UDs67J+334nT?VngSat1koqf0WI#-Fzr( zsMFki%!$nF3A;x0NW7D`jo+ zG3}mvQ{`UIt%Ta^%%^(a4D$uL?*s>?4@`OWh=B6JjT`_3H0b!*h>9W`Ot=6?{%yEg z4v^P4va|E`{&qSz!RB4c9v>_nH+m(buE8!{$Gdc+!&FV!*%wGQhK|=f5^uTUjj@-`-glk6Bg(U3_7C|62 zg`Po4v5YPIwLh-`LcF!ORgS(4_g#=OWM&MesY~KJ`mII#!TsD(r6J^drfyd zNW_8q;Th1y(((`ER{gkI5(gngoEYh>e?&RwQXahmg5ahn7H)9oR z0hn}4cXz`6*HH%;21XT^2xVD9QO%x zRitE}E)#4vK)ByT-4eI?jesfw!+&tJv*;on8mA%Lz*Yt9{yS;YWU)dLOcj*OOV7(q3KGZ*P|;uv%m^xrLuW9 zrj_Jq3fI0ZgbV#)GqfCM53h-M2dgp{)b>Z4)15y$Roh#?n%sBLd!*C?i6t-`cV6eT z(59tl4XYKQH@yG(HX9<8+=*FkBu8FdQX`?8h^1{&RbPsyk2nZFryUC@8J{Q2N(VzM zPWcC8FTci-1(l0xX3lGhGXXYL9zPjl#jbEgE5)9!V-Xk(?+=VMhAL*%)4uvpmWECj zpU3z#lOW3H=9y zWDTY$Um*AanzQ~eoF>_Gx1pV43@I-O` Date: Sun, 21 Jan 2024 19:30:53 +0100 Subject: [PATCH 03/11] ProjectPlayer: Add pen layer --- src/ProjectPlayer.qml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/ProjectPlayer.qml b/src/ProjectPlayer.qml index 908dbf6..0eb1918 100644 --- a/src/ProjectPlayer.qml +++ b/src/ProjectPlayer.qml @@ -109,6 +109,12 @@ ProjectScene { onStageModelChanged: stageModel.renderedTarget = this } + PenLayer { + id: penLayer + engine: loader.engine + anchors.fill: parent + } + Component { id: renderedSprite From cb7a5f9029bc5a23cc027d5ef3a174b156a056c0 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Sun, 21 Jan 2024 19:59:35 +0100 Subject: [PATCH 04/11] Add antialiasingEnabled property to PenLayer --- src/ipenlayer.h | 3 +++ src/penlayer.cpp | 15 +++++++++++++-- src/penlayer.h | 4 ++++ test/lines.png | Bin 7090 -> 2115 bytes test/lines_old.png | Bin 0 -> 2115 bytes test/mocks/penlayermock.h | 3 +++ test/penlayer/penlayer_test.cpp | 11 +++++++++-- test/points.png | Bin 2000 -> 1401 bytes 8 files changed, 32 insertions(+), 4 deletions(-) create mode 100644 test/lines_old.png diff --git a/src/ipenlayer.h b/src/ipenlayer.h index ba94b0c..54377e6 100644 --- a/src/ipenlayer.h +++ b/src/ipenlayer.h @@ -26,6 +26,9 @@ class IPenLayer : public QNanoQuickItem virtual ~IPenLayer() { } + virtual bool antialiasingEnabled() const = 0; + virtual void setAntialiasingEnabled(bool enabled) = 0; + virtual libscratchcpp::IEngine *engine() const = 0; virtual void setEngine(libscratchcpp::IEngine *newEngine) = 0; diff --git a/src/penlayer.cpp b/src/penlayer.cpp index 5f4aa6f..c056ec3 100644 --- a/src/penlayer.cpp +++ b/src/penlayer.cpp @@ -12,7 +12,7 @@ PenLayer::PenLayer(QNanoQuickItem *parent) : IPenLayer(parent) { m_fboFormat.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil); - m_fboFormat.setSamples(4); + m_fboFormat.setSamples(m_antialiasingEnabled ? 4 : 0); } PenLayer::~PenLayer() @@ -24,6 +24,17 @@ PenLayer::~PenLayer() m_painter->end(); } +bool PenLayer::antialiasingEnabled() const +{ + return m_antialiasingEnabled; +} + +void PenLayer::setAntialiasingEnabled(bool enabled) +{ + m_antialiasingEnabled = enabled; + m_fboFormat.setSamples(enabled ? 4 : 0); +} + libscratchcpp::IEngine *PenLayer::engine() const { return m_engine; @@ -81,7 +92,7 @@ void scratchcpprender::PenLayer::drawLine(const PenAttributes &penAttributes, do // Begin painting m_fbo->bind(); m_painter->beginNativePainting(); - m_painter->setRenderHint(QPainter::Antialiasing); + m_painter->setRenderHint(QPainter::Antialiasing, m_antialiasingEnabled); m_painter->setRenderHint(QPainter::SmoothPixmapTransform, false); // Translate to Scratch coordinate system diff --git a/src/penlayer.h b/src/penlayer.h index 6c83ac3..b836bf0 100644 --- a/src/penlayer.h +++ b/src/penlayer.h @@ -20,6 +20,9 @@ class PenLayer : public IPenLayer PenLayer(QNanoQuickItem *parent = nullptr); ~PenLayer(); + bool antialiasingEnabled() const override; + void setAntialiasingEnabled(bool enabled) override; + libscratchcpp::IEngine *engine() const override; void setEngine(libscratchcpp::IEngine *newEngine) override; @@ -39,6 +42,7 @@ class PenLayer : public IPenLayer private: static std::unordered_map m_projectPenLayers; + bool m_antialiasingEnabled = true; libscratchcpp::IEngine *m_engine = nullptr; std::unique_ptr m_fbo; std::unique_ptr m_paintDevice; diff --git a/test/lines.png b/test/lines.png index 321509f0c3ce4a3cf10ddb5be13bf5eff2665e84..3cb9dedcd707910ad027f279590dc94d8b73b155 100644 GIT binary patch literal 2115 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.attachment(), QOpenGLFramebufferObject::CombinedDepthStencil); ASSERT_EQ(fbo->format().samples(), 4); + penLayer.setAntialiasingEnabled(false); + ASSERT_FALSE(penLayer.antialiasingEnabled()); + EXPECT_CALL(engine2, stageWidth()).WillOnce(Return(500)); EXPECT_CALL(engine2, stageHeight()).WillOnce(Return(400)); penLayer.setEngine(&engine2); @@ -88,7 +93,7 @@ TEST_F(PenLayerTest, FramebufferObject) ASSERT_EQ(fbo->width(), 500); ASSERT_EQ(fbo->height(), 400); ASSERT_EQ(fbo->format().attachment(), QOpenGLFramebufferObject::CombinedDepthStencil); - ASSERT_EQ(fbo->format().samples(), 4); + ASSERT_EQ(fbo->format().samples(), 0); } TEST_F(PenLayerTest, GetProjectPenLayer) @@ -161,6 +166,7 @@ TEST_F(PenLayerTest, Clear) TEST_F(PenLayerTest, DrawPoint) { PenLayer penLayer; + penLayer.setAntialiasingEnabled(false); EngineMock engine; EXPECT_CALL(engine, stageWidth()).WillOnce(Return(480)); EXPECT_CALL(engine, stageHeight()).WillOnce(Return(360)); @@ -207,6 +213,7 @@ TEST_F(PenLayerTest, DrawPoint) TEST_F(PenLayerTest, DrawLine) { PenLayer penLayer; + penLayer.setAntialiasingEnabled(false); EngineMock engine; EXPECT_CALL(engine, stageWidth()).WillOnce(Return(480)); EXPECT_CALL(engine, stageHeight()).WillOnce(Return(360)); @@ -238,7 +245,7 @@ TEST_F(PenLayerTest, DrawLine) penLayer.drawLine(attr, -54, 21, 88, -6); QOpenGLFramebufferObject *fbo = penLayer.framebufferObject(); - QImage image = fbo->toImage(); + QImage image = fbo->toImage().scaled(240, 180); QBuffer buffer; image.save(&buffer, "png"); QFile ref("lines.png"); diff --git a/test/points.png b/test/points.png index 228d1f1c8a2a3b5a68aeb2a920dd3fbf85d030d3..03a886abb9248ff7c38fe847919ac23ce8bcfdde 100644 GIT binary patch literal 1401 zcmeAS@N?(olHy`uVBq!ia0y~yV0-|?860dtk$!Ux1t7&);1OBOz`%C|gc+x5^GP!> zuxfj{IEGZrd3)E<@3n!5L*VjX+DS+FBEn8@y1}TtZHm3av_%{qvyRji)|^|zv2vQj zu07Ky@o;LS)%^Uk;-lT+>#w7Gzccm~ciS;AeDP>tR5&0|%dlhDrRa88G_`SgtzEcw{=!(b1O|=-rZ%s>c}>3iFPJg5b@g>^_~G=MMMCSgbmGU>Y=H!h z4W@@4abH(FAdr#U(JH>qz2TwCGefahpch`;opH%xnTgwc=5Gbcd_V#0V8OELswLG` zF7nI_QRX^z$Ab#)vr00|ds=ybLBOE_=*aupFScH`Os^OJbB3|( zw|Fgs-@E1Z2bRSPCpY-6SpZc1iN$7a!5Rg|2Lh+BH@uwge~?)unU$A;(dWRQv&$ba zb{Q}-J1{V^F<6-PFJ{5+BCHFBd{*@A;@@5g=7}sbPDe=Jvs7eL;xL%L!F1nD4(6?#24`hGLL zXJ%En7$aK)!#c*xD*xM9wwz<(VPIqdrl<(V%R1ki*rL*yfU;@{vz`^UIIL!w@k~}U z@gr-NP=a1!e#=qu6}}BSd)5ky#Va&16kBFH>+Eq*VSIK8nvkd(N(&sFzc4bK%L3+! z8T;h+#ob?Y?$B2bg9ArwcI_8T{w9>ru*GgqvD29_CZ3Y~>>6XiG;T(=UE9oo!Sz`O z$SMs1ianDA%D#N@BTeZ{6ezMC`h5;KlzaK^pQ$;+)CZby8$?YmOuh8_`5CEMs-T3c z029v!s{JD^l%4M528M=Ue5DD*@h>jd|5mQmBt3~kCXwpmnEm@w{Lh2*O?VvkEy(oBq`rcEST6Pna< z*=U8@lv`got8$q%t76EdBx^*NaZC1_+V-41r_P?Ur$6>R=RNQDyyrQ;=kj~L-+R*2 z-Ep4k0#yLOJhl^SBLGl0y7v;4(VJw;r3UDs67J+334nT?VngSat1koqf0WI#-Fzr( zsMFki%!$nF3A;x0NW7D`jo+ zG3}mvQ{`UIt%Ta^%%^(a4D$uL?*s>?4@`OWh=B6JjT`_3H0b!*h>9W`Ot=6?{%yEg z4v^P4va|E`{&qSz!RB4c9v>_nH+m(buE8!{$Gdc+!&FV!*%wGQhK|=f5^uTUjj@-`-glk6Bg(U3_7C|62 zg`Po4v5YPIwLh-`LcF!ORgS(4_g#=OWM&MesY~KJ`mII#!TsD(r6J^drfyd zNW_8q;Th1y(((`ER{gkI5(gngoEYh>e?&RwQXahmg5ahn7H)9oR z0hn}4cXz`6*HH%;21XT^2xVD9QO%x zRitE}E)#4vK)ByT-4eI?jesfw!+&tJv*;on8mA%Lz*Yt9{yS;YWU)dLOcj*OOV7(q3KGZ*P|;uv%m^xrLuW9 zrj_Jq3fI0ZgbV#)GqfCM53h-M2dgp{)b>Z4)15y$Roh#?n%sBLd!*C?i6t-`cV6eT z(59tl4XYKQH@yG(HX9<8+=*FkBu8FdQX`?8h^1{&RbPsyk2nZFryUC@8J{Q2N(VzM zPWcC8FTci-1(l0xX3lGhGXXYL9zPjl#jbEgE5)9!V-Xk(?+=VMhAL*%)4uvpmWECj zpU3z#lOW3H=9y zWDTY$Um*AanzQ~eoF>_Gx1pV43@I-O` Date: Sun, 21 Jan 2024 20:45:02 +0100 Subject: [PATCH 05/11] Add pen properties to SpriteModel --- src/spritemodel.cpp | 36 +++++++++++++++++++++ src/spritemodel.h | 18 +++++++++++ test/target_models/spritemodel_test.cpp | 43 +++++++++++++++++++++++++ 3 files changed, 97 insertions(+) diff --git a/src/spritemodel.cpp b/src/spritemodel.cpp index 9064dda..34ac0ef 100644 --- a/src/spritemodel.cpp +++ b/src/spritemodel.cpp @@ -5,6 +5,7 @@ #include "spritemodel.h" #include "renderedtarget.h" +#include "ipenlayer.h" namespace scratchcpprender { @@ -115,6 +116,41 @@ void SpriteModel::setRenderedTarget(IRenderedTarget *newRenderedTarget) emit renderedTargetChanged(); } +IPenLayer *SpriteModel::penLayer() const +{ + return m_penLayer; +} + +void SpriteModel::setPenLayer(IPenLayer *newPenLayer) +{ + if (m_penLayer == newPenLayer) + return; + + m_penLayer = newPenLayer; + emit penLayerChanged(); +} + +PenAttributes &SpriteModel::penAttributes() +{ + return m_penAttributes; +} + +bool SpriteModel::penDown() const +{ + return m_penDown; +} + +void SpriteModel::setPenDown(bool newPenDown) +{ + if (m_penDown == newPenDown) + return; + + m_penDown = newPenDown; + + if (m_penDown && m_penLayer && m_sprite) + m_penLayer->drawPoint(m_penAttributes, m_sprite->x(), m_sprite->y()); +} + SpriteModel *SpriteModel::cloneRoot() const { if (m_cloneRoot == this) diff --git a/src/spritemodel.h b/src/spritemodel.h index 8be730b..2bc0974 100644 --- a/src/spritemodel.h +++ b/src/spritemodel.h @@ -6,12 +6,16 @@ #include #include +#include "penattributes.h" + Q_MOC_INCLUDE("renderedtarget.h"); +Q_MOC_INCLUDE("ipenlayer.h"); namespace scratchcpprender { class IRenderedTarget; +class IPenLayer; class SpriteModel : public QObject @@ -20,6 +24,7 @@ class SpriteModel Q_OBJECT QML_ELEMENT Q_PROPERTY(IRenderedTarget *renderedTarget READ renderedTarget WRITE setRenderedTarget NOTIFY renderedTargetChanged) + Q_PROPERTY(IPenLayer *penLayer READ penLayer WRITE setPenLayer NOTIFY penLayerChanged) public: SpriteModel(QObject *parent = nullptr); @@ -49,16 +54,29 @@ class SpriteModel IRenderedTarget *renderedTarget() const; void setRenderedTarget(IRenderedTarget *newRenderedTarget); + IPenLayer *penLayer() const; + void setPenLayer(IPenLayer *newPenLayer); + + PenAttributes &penAttributes(); + void setPenAttributes(const PenAttributes &newPenAttributes); + + bool penDown() const; + void setPenDown(bool newPenDown); + SpriteModel *cloneRoot() const; signals: void renderedTargetChanged(); + void penLayerChanged(); void cloned(SpriteModel *cloneModel); void cloneDeleted(SpriteModel *clone); private: libscratchcpp::Sprite *m_sprite = nullptr; IRenderedTarget *m_renderedTarget = nullptr; + IPenLayer *m_penLayer = nullptr; + PenAttributes m_penAttributes; + bool m_penDown = false; SpriteModel *m_cloneRoot = nullptr; }; diff --git a/test/target_models/spritemodel_test.cpp b/test/target_models/spritemodel_test.cpp index f5527d1..f903461 100644 --- a/test/target_models/spritemodel_test.cpp +++ b/test/target_models/spritemodel_test.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include "../common.h" @@ -9,6 +10,9 @@ using namespace scratchcpprender; using namespace libscratchcpp; using ::testing::Return; +using ::testing::WithArgs; +using ::testing::Invoke; +using ::testing::_; TEST(SpriteModelTest, Constructors) { @@ -204,3 +208,42 @@ TEST(SpriteModelTest, RenderedTarget) ASSERT_EQ(spy.count(), 1); ASSERT_EQ(model.renderedTarget(), &renderedTarget); } + +TEST(SpriteModelTest, PenLayer) +{ + SpriteModel model; + ASSERT_EQ(model.penLayer(), nullptr); + + PenLayerMock penLayer; + QSignalSpy spy(&model, &SpriteModel::penLayerChanged); + model.setPenLayer(&penLayer); + ASSERT_EQ(spy.count(), 1); + ASSERT_EQ(model.penLayer(), &penLayer); +} + +TEST(SpriteModelTest, PenDown) +{ + SpriteModel model; + Sprite sprite; + sprite.setX(24.6); + sprite.setY(-48.8); + model.init(&sprite); + ASSERT_FALSE(model.penDown()); + + PenLayerMock penLayer; + model.setPenLayer(&penLayer); + + PenAttributes &attr = model.penAttributes(); + + EXPECT_CALL(penLayer, drawPoint(_, 24.6, -48.8)).WillOnce(WithArgs<0>(Invoke([&attr](const PenAttributes &attrArg) { ASSERT_EQ(&attr, &attrArg); }))); + model.setPenDown(true); + ASSERT_TRUE(model.penDown()); + + EXPECT_CALL(penLayer, drawPoint).Times(0); + model.setPenDown(true); + ASSERT_TRUE(model.penDown()); + + EXPECT_CALL(penLayer, drawPoint).Times(0); + model.setPenDown(false); + ASSERT_FALSE(model.penDown()); +} From ede2fa0fc3b0bd06068fa9bafb107ccd3c21e877 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Sun, 21 Jan 2024 22:40:53 +0100 Subject: [PATCH 06/11] Update libscratchcpp to latest master --- libscratchcpp | 2 +- src/spritemodel.cpp | 4 ++++ src/spritemodel.h | 1 + 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/libscratchcpp b/libscratchcpp index d7e05e2..1964cce 160000 --- a/libscratchcpp +++ b/libscratchcpp @@ -1 +1 @@ -Subproject commit d7e05e2c01f15ecfad78e8a427b7cea9ce4253ec +Subproject commit 1964cce49460f2d6a1a9102e280362d225ac46df diff --git a/src/spritemodel.cpp b/src/spritemodel.cpp index 34ac0ef..01c03ca 100644 --- a/src/spritemodel.cpp +++ b/src/spritemodel.cpp @@ -60,6 +60,10 @@ void SpriteModel::onYChanged(double y) m_renderedTarget->updateY(y); } +void SpriteModel::onMoved(double oldX, double oldY, double newX, double newY) +{ +} + void SpriteModel::onSizeChanged(double size) { if (m_renderedTarget) diff --git a/src/spritemodel.h b/src/spritemodel.h index 2bc0974..969d540 100644 --- a/src/spritemodel.h +++ b/src/spritemodel.h @@ -39,6 +39,7 @@ class SpriteModel void onVisibleChanged(bool visible) override; void onXChanged(double x) override; void onYChanged(double y) override; + void onMoved(double oldX, double oldY, double newX, double newY) override; void onSizeChanged(double size) override; void onDirectionChanged(double direction) override; void onRotationStyleChanged(libscratchcpp::Sprite::RotationStyle rotationStyle) override; From 42113b74b7d7479bd3b82deb889fecd97b7634ff Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Sun, 21 Jan 2024 23:10:02 +0100 Subject: [PATCH 07/11] Draw pen lines in SpriteModel::onMoved() --- src/spritemodel.cpp | 2 ++ test/target_models/spritemodel_test.cpp | 17 +++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/src/spritemodel.cpp b/src/spritemodel.cpp index 01c03ca..ad3bdb7 100644 --- a/src/spritemodel.cpp +++ b/src/spritemodel.cpp @@ -62,6 +62,8 @@ void SpriteModel::onYChanged(double y) void SpriteModel::onMoved(double oldX, double oldY, double newX, double newY) { + if (m_penDown && m_penLayer) + m_penLayer->drawLine(m_penAttributes, oldX, oldY, newX, newY); } void SpriteModel::onSizeChanged(double size) diff --git a/test/target_models/spritemodel_test.cpp b/test/target_models/spritemodel_test.cpp index f903461..c33fd54 100644 --- a/test/target_models/spritemodel_test.cpp +++ b/test/target_models/spritemodel_test.cpp @@ -135,6 +135,23 @@ TEST(SpriteModelTest, OnYChanged) model.onYChanged(-46.1); } +TEST(SpriteModelTest, OnMoved) +{ + SpriteModel model; + + PenLayerMock penLayer; + model.setPenLayer(&penLayer); + + EXPECT_CALL(penLayer, drawLine).Times(0); + model.onMoved(-15.6, 54.9, 159.04, -2.5); + + model.setPenDown(true); + PenAttributes &attr = model.penAttributes(); + + EXPECT_CALL(penLayer, drawLine(_, -15.6, 54.9, 159.04, -2.5)).WillOnce(WithArgs<0>(Invoke([&attr](const PenAttributes &attrArg) { ASSERT_EQ(&attr, &attrArg); }))); + model.onMoved(-15.6, 54.9, 159.04, -2.5); +} + TEST(SpriteModelTest, OnSizeChanged) { SpriteModel model; From 0e799fa3fc07db9bfaaba40abe81b9c3bee772c5 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Sun, 21 Jan 2024 23:37:20 +0100 Subject: [PATCH 08/11] ProjectPlayer: Set pen layer of sprites --- src/ProjectPlayer.qml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ProjectPlayer.qml b/src/ProjectPlayer.qml index 0eb1918..0627a42 100644 --- a/src/ProjectPlayer.qml +++ b/src/ProjectPlayer.qml @@ -110,7 +110,7 @@ ProjectScene { } PenLayer { - id: penLayer + id: projectPenLayer engine: loader.engine anchors.fill: parent } @@ -127,6 +127,7 @@ ProjectScene { engine = loader.engine; spriteModel = modelData; spriteModel.renderedTarget = this; + spriteModel.penLayer = projectPenLayer; } } } From a8f460dac4d2e1e38e66ed54ee6631b259a953c9 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Sun, 21 Jan 2024 23:47:05 +0100 Subject: [PATCH 09/11] SpriteModel: Remove pen attributes setter --- src/spritemodel.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/spritemodel.h b/src/spritemodel.h index 969d540..64473f0 100644 --- a/src/spritemodel.h +++ b/src/spritemodel.h @@ -59,7 +59,6 @@ class SpriteModel void setPenLayer(IPenLayer *newPenLayer); PenAttributes &penAttributes(); - void setPenAttributes(const PenAttributes &newPenAttributes); bool penDown() const; void setPenDown(bool newPenDown); From b126800b7a72008fd2a1981a1b75cc93b2d1459e Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Sun, 21 Jan 2024 23:47:44 +0100 Subject: [PATCH 10/11] Make clones inherit pen information --- src/spritemodel.cpp | 3 +++ test/target_models/spritemodel_test.cpp | 11 +++++++++++ 2 files changed, 14 insertions(+) diff --git a/src/spritemodel.cpp b/src/spritemodel.cpp index ad3bdb7..95a0f8a 100644 --- a/src/spritemodel.cpp +++ b/src/spritemodel.cpp @@ -32,6 +32,9 @@ void SpriteModel::onCloned(libscratchcpp::Sprite *clone) SpriteModel *cloneModel = new SpriteModel(m_cloneRoot); cloneModel->m_cloneRoot = m_cloneRoot; + cloneModel->m_penLayer = m_penLayer; + cloneModel->m_penAttributes = m_penAttributes; + cloneModel->m_penDown = m_penDown; clone->setInterface(cloneModel); emit cloned(cloneModel); } diff --git a/test/target_models/spritemodel_test.cpp b/test/target_models/spritemodel_test.cpp index c33fd54..819aa7e 100644 --- a/test/target_models/spritemodel_test.cpp +++ b/test/target_models/spritemodel_test.cpp @@ -74,9 +74,16 @@ TEST(SpriteModelTest, OnCloned) ASSERT_EQ(cloneModel->parent(), &model); ASSERT_EQ(cloneModel->sprite(), &clone2); ASSERT_EQ(cloneModel->cloneRoot(), &model); + ASSERT_FALSE(cloneModel->penDown()); Sprite clone3; QSignalSpy spy2(cloneModel, &SpriteModel::cloned); + PenLayerMock penLayer; + cloneModel->setPenLayer(&penLayer); + cloneModel->penAttributes().color = QColor(255, 0, 0); + cloneModel->penAttributes().diameter = 20.3; + EXPECT_CALL(penLayer, drawPoint); + cloneModel->setPenDown(true); cloneModel->onCloned(&clone3); ASSERT_EQ(spy2.count(), 1); @@ -87,6 +94,10 @@ TEST(SpriteModelTest, OnCloned) ASSERT_EQ(cloneModel->parent(), &model); ASSERT_EQ(cloneModel->sprite(), &clone3); ASSERT_EQ(cloneModel->cloneRoot(), &model); + ASSERT_EQ(cloneModel->penLayer(), &penLayer); + ASSERT_EQ(cloneModel->penAttributes().color, QColor(255, 0, 0)); + ASSERT_EQ(cloneModel->penAttributes().diameter, 20.3); + ASSERT_TRUE(cloneModel->penDown()); } TEST(SpriteModelTest, OnCostumeChanged) From e3f691e316bad6dcc99c702b3a1998b3aa6ac671 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Mon, 22 Jan 2024 15:50:40 +0100 Subject: [PATCH 11/11] PenLayer: Fix a crash when destroying the painter --- src/penlayer.cpp | 25 ++++++++++--------------- src/penlayer.h | 2 +- 2 files changed, 11 insertions(+), 16 deletions(-) diff --git a/src/penlayer.cpp b/src/penlayer.cpp index c056ec3..bf85be4 100644 --- a/src/penlayer.cpp +++ b/src/penlayer.cpp @@ -19,9 +19,6 @@ PenLayer::~PenLayer() { if (m_engine) m_projectPenLayers.erase(m_engine); - - if (m_painter && m_painter->isActive()) - m_painter->end(); } bool PenLayer::antialiasingEnabled() const @@ -55,11 +52,7 @@ void PenLayer::setEngine(libscratchcpp::IEngine *newEngine) m_fbo = std::make_unique(m_engine->stageWidth(), m_engine->stageHeight(), m_fboFormat); Q_ASSERT(m_fbo->isValid()); - if (m_painter && m_painter->isActive()) - m_painter->end(); - m_paintDevice = std::make_unique(m_fbo->size()); - m_painter = std::make_unique(m_paintDevice.get()); clear(); } @@ -86,14 +79,15 @@ void scratchcpprender::PenLayer::drawPoint(const PenAttributes &penAttributes, d void scratchcpprender::PenLayer::drawLine(const PenAttributes &penAttributes, double x0, double y0, double x1, double y1) { - if (!m_fbo || !m_painter || !m_engine) + if (!m_fbo || !m_paintDevice || !m_engine) return; // Begin painting m_fbo->bind(); - m_painter->beginNativePainting(); - m_painter->setRenderHint(QPainter::Antialiasing, m_antialiasingEnabled); - m_painter->setRenderHint(QPainter::SmoothPixmapTransform, false); + QPainter painter(m_paintDevice.get()); + painter.beginNativePainting(); + painter.setRenderHint(QPainter::Antialiasing, m_antialiasingEnabled); + painter.setRenderHint(QPainter::SmoothPixmapTransform, false); // Translate to Scratch coordinate system double stageWidthHalf = m_engine->stageWidth() / 2; @@ -107,16 +101,17 @@ void scratchcpprender::PenLayer::drawLine(const PenAttributes &penAttributes, do QPen pen(penAttributes.color); pen.setWidthF(penAttributes.diameter); pen.setCapStyle(Qt::RoundCap); - m_painter->setPen(pen); + painter.setPen(pen); // If the start and end coordinates are the same, draw a point, otherwise draw a line if (x0 == x1 && y0 == y1) - m_painter->drawPoint(x0, y0); + painter.drawPoint(x0, y0); else - m_painter->drawLine(x0, y0, x1, y1); + painter.drawLine(x0, y0, x1, y1); // End painting - m_painter->endNativePainting(); + painter.endNativePainting(); + painter.end(); m_fbo->release(); update(); diff --git a/src/penlayer.h b/src/penlayer.h index b836bf0..fc88ae2 100644 --- a/src/penlayer.h +++ b/src/penlayer.h @@ -5,6 +5,7 @@ #include #include #include +#include #include namespace scratchcpprender @@ -46,7 +47,6 @@ class PenLayer : public IPenLayer libscratchcpp::IEngine *m_engine = nullptr; std::unique_ptr m_fbo; std::unique_ptr m_paintDevice; - std::unique_ptr m_painter; QOpenGLFramebufferObjectFormat m_fboFormat; };